Our Ontology Kernel: Everything Must Go Through the Ontology Layer
TL;DR
Our Ontology Kernel: Everything Must Go Through the Ontology Layer
“Series: S2 Architecture Overview · Article 4 | Level: Intermediate | Reading Time: 18 min
TL;DR
- The platform strictly prohibits any service from bypassing the Ontology layer to access underlying databases directly. This technical red line may seem to limit development freedom, but it is the cornerstone for guaranteeing data consistency, permission security, and audit traceability.
- OntologyRuntimeService is the platform's "data constitution" -- all data reads and writes must go through its gRPC interface. Each operation executes schema validation, permission checking, audit logging, and event publishing, forming an inescapable 4-layer defense.
- The "Ontology Tax" refers to the approximately 2-5ms of additional latency incurred per data operation by routing through the ontology layer. This cost buys unified data governance, automated lineage tracking, zero-code permission inheritance, and complete audit logs -- an ROI that far exceeds expectations.
#1. Introduction: A Seemingly Unreasonable Red Line
In the coomia-dip project's CLAUDE.md file, there is a technical red line stated very clearly:
❌ Bypass Ontology to query databases directly
Developers encountering this rule for the first time typically raise questions like:
- "I just want to read some data -- why can't I query Doris directly?"
- "Going through the Ontology layer adds another gRPC call. Won't that hurt performance?"
- "Isn't this over-engineering?"
These are all reasonable questions. In fact, we had intense internal debates about this during the early stages of the project. This article will explain in depth: why we ultimately held firm on this red line, and how OntologyRuntimeService became the platform's "data constitution."
#2. What Is the Ontology Kernel
#2.1 Traditional Architecture vs Ontology-Driven Architecture
In traditional microservice architectures, each service directly manages its own database. This pattern follows a widely accepted principle -- "Database per Service."
Traditional Microservice Architecture:
Service A ──── Database A (MySQL)
Service B ──── Database B (PostgreSQL)
Service C ──── Database C (MongoDB)
Service D ──── Database D (Redis)
Problem: data silos, no unified semantics, cross-service queries are difficult
coomia-dip takes a fundamentally different approach -- Ontology-Driven Architecture:
Ontology-Driven Architecture:
┌──────────────────────┐
│ OntologyRuntimeSvc │
Service A ──gRPC──> │ (Schema validation) │
Service B ──gRPC──> │ (Permission check) │──> Doris / Iceberg / ...
Service C ──gRPC──> │ (Audit logging) │
Service D ──gRPC──> │ (Event publishing) │
└──────────────────────┘
The single entry point for all data access
#2.2 Core Concept: Ontology as Schema
In coomia-dip, "Ontology" is not an abstract philosophical concept but a strictly defined runtime data schema. It includes:
| Concept | Description | Analogy |
|---|---|---|
| ObjectType | Object type definition (properties, constraints, indexes) | Database table DDL |
| RelationType | Relation type definition (direction, cardinality, properties) | Foreign key + junction table |
| ActionType | Action type definition (input, output, side effects) | Stored procedure signature |
| Property | Property definition (type, validation rules, derivation rules) | Column definition + CHECK constraint |
| DerivedProperty | Derived property (computation rules, dependencies) | Computed column + materialized view |
The key difference: these definitions are not static DDL but runtime-queryable, versionable, inheritable first-class citizens.
#2.3 OntologyRuntimeService's Position
OntologyRuntimeService is the most critical service in Control Layer (Control Layer). It is not a simple CRUD proxy but the platform's data constitution enforcer.
service OntologyRuntimeService {
// Instance lifecycle
rpc CreateObject(CreateObjectRequest) returns (CreateObjectResponse);
rpc GetObject(GetObjectRequest) returns (GetObjectResponse);
rpc UpdateObject(UpdateObjectRequest) returns (UpdateObjectResponse);
rpc DeleteObject(DeleteObjectRequest) returns (DeleteObjectResponse);
// Batch operations
rpc BatchGetObjects(BatchGetRequest) returns (BatchGetResponse);
rpc SearchObjects(SearchObjectsRequest) returns (SearchObjectsResponse);
// Relation operations
rpc CreateLink(CreateLinkRequest) returns (CreateLinkResponse);
rpc GetLinkedObjects(GetLinkedRequest) returns (GetLinkedResponse);
// Aggregate queries
rpc AggregateObjects(AggregateRequest) returns (AggregateResponse);
// Derived properties
rpc GetDerivedProperty(DerivedPropertyRequest) returns (DerivedPropertyResponse);
}
Behind each gRPC method lies a series of non-skippable checks and processing logic.
#3. The 4-Layer Defense: What Happens on Every Operation
When a service calls OntologyRuntimeService.GetObject(), the request passes through 4 layers of processing. Together, these 4 layers form coomia-dip's "data constitution."
#3.1 Layer 1: Schema Validation
Request arrives → Fetch ObjectType definition from SchemaRegistry
→ Validate requested fields exist in the Schema
→ Validate field types match
→ Validate constraints (required, range, enum values)
→ Validate no unknown/disallowed fields
This layer ensures invalid data can never enter the system. Unlike traditional approaches, schema validation is not dependent on database constraints (like NOT NULL or CHECK) but is driven by ontology definitions at the application layer.
The benefit: when an ontology definition changes (e.g., a new required property is added), all requests through OntologyRuntimeService immediately pick up the change -- no service code modifications or database migrations needed.
#3.2 Layer 2: Permission Checking
Schema validation passes → Extract WorldContext from request context
→ Determine current Tenant/Org/Space/Project/World
→ Fetch RBAC + ABAC rules from PolicyEngine
→ Check read/write permission per property
→ Inject row-level filter conditions
coomia-dip uses a 5-level isolation model (Tenant → Org → Space → Project → World), and permission checking must occur at each level. Querying the database directly means bypassing all permission checks -- an unacceptable security vulnerability in a multi-tenant environment.
Particularly important is property-level access control. For example:
ObjectType: Employee
- name: readable by everyone
- department: readable by everyone
- salary: readable only by HR role
- ssn: readable only by Compliance role
Direct DB query → SELECT * FROM entity_common WHERE type='Employee'
→ Exposes salary and ssn fields
Through Ontology → OntologyRuntimeService filters based on caller's role
→ Automatically strips unauthorized properties
→ Response does not include salary or ssn
#3.3 Layer 3: Audit Logging
Permission check passes → Record operation audit log
→ Who (principal) at what time
→ On which object in which World
→ What operation was performed
→ What was the result
→ Send to Kafka audit topic
coomia-dip uses 3 Kafka audit topics to categorize audit logs:
| Topic | Purpose | Retention |
|---|---|---|
audit.data-access | Data read operations | 90 days |
audit.data-mutation | Data write operations | 365 days |
audit.admin-operation | Admin operations (schema changes, etc.) | Permanent |
Operations that bypass the Ontology layer never appear in audit logs. In regulated industries like finance and healthcare, this means complete compliance failure.
#3.4 Layer 4: Event Publishing
Audit logging complete → Publish data change event to Kafka
→ Trigger subscriber notifications
→ Trigger derived property cascade computation
→ Trigger materialized view incremental updates
→ Trigger search index incremental sync
This layer is the foundation of the platform's reactive capabilities. If a service directly modifies data in Doris, the following features all break:
- Derived properties will not recalculate
- Materialized views will not incrementally update
- Other services' subscriptions will not receive notifications
- Data lineage chains will be broken
- Search indexes will become inconsistent with actual data
#4. The "Ontology Tax": Cost-Benefit Analysis
#4.1 Quantifying the Cost
We call the overhead of routing through the Ontology layer the "Ontology Tax." Let us quantify it precisely:
Direct Doris query:
Network latency: ~0.5ms
Query execution: ~1-5ms (depends on data volume)
Total latency: ~1.5-5.5ms
Through OntologyRuntimeService:
gRPC call overhead: ~0.3ms
Schema validation: ~0.2ms (after caching)
Permission check: ~0.5ms (after caching)
Audit logging (async): ~0.1ms (non-blocking)
Event publishing (async): ~0.1ms (non-blocking)
Doris query: ~1-5ms
gRPC response serialization: ~0.2ms
Total latency: ~2.4-6.4ms
Ontology Tax ≈ 1-2ms, approximately 15-40% additional latency.
#4.2 Why This Cost Is Worth It
| Benefit | Direct DB Access | Through Ontology | Extra Effort |
|---|---|---|---|
| Schema consistency | Manual guarantee | Automatic | 0 lines of code |
| Property-level permissions | Must implement yourself | Built-in | Saves hundreds of lines |
| Audit logs | Must integrate yourself | Automatic | Saves all audit middleware |
| Data lineage | Impossible | Automatic | Saves a dedicated lineage system |
| Derived property cascade | Must trigger yourself | Automatic | Saves all trigger logic |
| Search index sync | Must sync yourself | Automatic | Saves sync pipeline |
| Version rollback | Must record yourself | Nessie automatic | Saves version management code |
If every service implemented these features independently, the code increase would conservatively be 3,000-5,000 lines per service. For a platform with 20+ microservices, that means 60,000-100,000 lines of duplicated code -- and 60,000-100,000 potential bugs.
#4.3 Performance Optimization: Minimizing the Tax
We reduce the Ontology Tax to acceptable levels through several measures:
Schema Caching (local + Redis two-tier cache)
First request:
SchemaRegistry.GetObjectType() → Redis → PostgreSQL
Cache duration: 5 minutes (Redis), 60 seconds (local memory)
Schema changes actively invalidate cache via Kafka events
Subsequent requests:
Local memory cache hit → ~0.01ms
Hit rate: >99.5%
Permission Rule Pre-compilation
PolicyEngine pre-compiles RBAC + ABAC rules into a decision tree
First compilation: ~5ms
Subsequent evaluation: ~0.1ms (binary tree traversal)
Incremental recompilation on rule changes
Async Audit and Event Publishing
Audit logging → Kafka Producer (async, no ACK wait)
Event publishing → Kafka Producer (async, batched)
Main flow blocking: <0.2ms
At-least-once delivery guaranteed via Kafka acks=1
Batch Operation Optimization
Requesting 100 objects in a single call:
Direct DB: 100 * 5ms = 500ms (one by one)
or 5-10ms (WHERE IN batch)
Ontology layer: Schema validation once (shared ObjectType)
Permission check once (shared WorldContext)
Doris query once (WHERE IN)
Audit 1 record (batch operation entry)
Total latency: ~8-15ms
In batch scenarios, the Ontology Tax is amortized to near zero
#5. Internal Architecture of OntologyRuntimeService
#5.1 Component Structure
┌─────────────────────────────────────────────────────┐
│ OntologyRuntimeService │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ gRPC │ │ Schema │ │ Permission │ │
│ │ Endpoint │→ │ Resolver │→ │ Evaluator │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Query Planner │ │
│ │ ┌───────┐ ┌────────┐ ┌──────────────────┐ │ │
│ │ │ Doris │ │Iceberg │ │ DerivedProperty │ │ │
│ │ │ Query │ │ Query │ │ Calculator │ │ │
│ │ └───────┘ └────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Audit │ │ Event │ │ Response │ │
│ │ Logger │ │Publisher │ │ Assembler │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
#5.2 Schema Resolver
The Schema Resolver is responsible for resolving the complete definition of the target ObjectType on every request. Its job goes beyond "looking up a schema" to include:
- Inheritance resolution: If ObjectType A inherits from ObjectType B, resolve the complete property set
- Version matching: Determine which schema version to use based on WorldContext (different Worlds may have different versions)
- Constraint merging: Merge base class and subclass constraints
- Index information: Determine which properties have indexes for query optimization
SchemaResolver.resolve("Equipment", worldContext)
→ Check local cache
→ Miss → Check Redis cache
→ Miss → Query SchemaRegistry (gRPC)
→ Get Equipment ObjectType definition
→ Check inheritance: Equipment extends Asset
→ Recursively resolve Asset definition
→ Merge properties: Asset.properties + Equipment.properties
→ Merge constraints: Asset.constraints + Equipment.constraints
→ Cache result
→ Return complete ResolvedSchema
#5.3 Permission Evaluator
The Permission Evaluator is the core component for permission checking. It works with the Policy Engine to perform two types of authorization checks:
RBAC (Role-Based Access Control)
Check flow:
1. Extract user identity from WorldContext
2. Query user's roles in current Org/Space/Project/World
3. Query role permissions for target ObjectType
4. Permission inheritance: World → Project → Space → Org → Tenant
5. Result: ALLOW / DENY / PARTIAL (property-level filtering)
ABAC (Attribute-Based Access Control)
Check flow:
1. Get ABAC policy rules
2. Evaluate condition expressions:
- User attributes (department, level, clearance)
- Resource attributes (classification, owner, region)
- Environment attributes (time, ip, device)
3. Generate row-level filter conditions for injection into queries
The results of both are merged -- RBAC determines whether the user has basic access rights; ABAC further refines this to "which rows and which columns can be seen."
#5.4 Query Planner
The Query Planner transforms validated and authorized requests into concrete storage queries. Scenarios it handles include:
Scenario 1: Simple property query
→ Generate Doris SQL: SELECT col1, col2 FROM entity_common WHERE ...
→ Inject permission filter conditions
→ Execute
Scenario 2: Includes derived properties
→ Separate regular and derived properties
→ Regular properties → Doris query
→ Derived properties → ComputationCoordinator
→ Merge results
Scenario 3: Includes relation traversal
→ Generate entity_edge table JOIN query
→ Or generate multi-step query plan (breadth-first traversal)
→ Execute permission checks on each traversal step
Scenario 4: Aggregate query
→ Generate Doris aggregate SQL
→ Consider impact of permission filtering on aggregate results
→ If row-level filters exist, filter first then aggregate
#5.5 Response Assembler
The Response Assembler merges results from multiple data sources into a unified gRPC response. It is also responsible for:
- Property filtering: Based on permission check results, remove unauthorized properties
- Format conversion: Convert Doris row data to Protobuf messages
- Null handling: For optional properties without values, distinguish between "no permission to see" and "genuinely no value"
- Pagination: Encapsulate pagination cursors (based on Doris LIMIT/OFFSET or sort-key-based keyset pagination)
#6. A Concrete Example: From API Request to Data Return
Let us trace a specific request to see how it flows through the entire Ontology kernel.
#6.1 Scenario Description
A factory management application needs to query all equipment with status "RUNNING," displaying device name, location, and current utilization (utilization is a derived property requiring real-time computation).
#6.2 External API Entry Point
REST API (external):
GET /api/v1/ontology/objects/Equipment?
filter=status eq 'RUNNING'&
select=name,location,utilization&
pageSize=20
→ API Gateway converts to gRPC call
→ OntologyRuntimeService.SearchObjects()
#6.3 Complete Processing Flow
Step 1: gRPC request arrives (t=0ms)
├── Parse SearchObjectsRequest
├── Extract WorldContext (from gRPC metadata)
└── Record request start time
Step 2: Schema resolution (t=0.1ms)
├── SchemaResolver.resolve("Equipment")
├── Local cache hit ✓
├── Validate: "name" → STRING ✓
├── Validate: "location" → STRING ✓
├── Validate: "utilization" → DERIVED_DOUBLE ✓ (marked as derived)
├── Validate: "status" → ENUM[RUNNING,STOPPED,MAINTENANCE] ✓
└── Validate: filter expression syntax is valid ✓
Step 3: Permission check (t=0.5ms)
├── User role: FactoryOperator
├── RBAC check: FactoryOperator has READ on Equipment ✓
├── ABAC check: User department=Manufacturing
│ ├── Policy: "Manufacturing dept can only see their own factory equipment"
│ └── Generate row filter: AND factory_id IN ('F001', 'F002')
├── Property-level check:
│ ├── name → allowed ✓
│ ├── location → allowed ✓
│ └── utilization → allowed ✓
└── Done
Step 4: Query planning (t=0.8ms)
├── Separate regular properties: name, location (Doris query)
├── Separate derived properties: utilization (needs computation)
├── Generate Doris SQL:
│ SELECT object_id, attributes->>'name' AS name,
│ attributes->>'location' AS location
│ FROM entity_common
│ WHERE type = 'Equipment'
│ AND attributes->>'status' = 'RUNNING'
│ AND factory_id IN ('F001', 'F002') -- ABAC injection
│ AND world_id = 'W-2024-001'
│ ORDER BY object_id
│ LIMIT 20
└── Prepare ComputationCoordinator call
Step 5: Execute queries (t=1.0ms → t=4.5ms)
├── Doris query execution: returns 20 records
└── Call ComputationCoordinator for 20 object_ids
├── utilization strategy routing: EXPRESSION (simple calculation)
├── Formula: running_hours / total_hours * 100
├── Requires running_hours and total_hours → query Doris
└── Computation complete: returns 20 utilization values
Step 6: Result assembly (t=5.0ms)
├── Merge Doris results with derived property results
├── Property filtering: remove unauthorized properties (all allowed this time)
├── Build gRPC Response
└── Set pagination cursor
Step 7: Audit + Events (async, t=5.1ms)
├── Send audit log to Kafka audit.data-access
│ { principal: "user-123", action: "SEARCH",
│ objectType: "Equipment", resultCount: 20,
│ worldId: "W-2024-001", timestamp: "..." }
└── (Search operations do not trigger data change events)
Step 8: Response returned (t=5.2ms)
└── Total latency: ~5.2ms (Ontology Tax ≈ 1.7ms)
#7. What Happens If You Bypass the Ontology: 5 Disaster Scenarios
#7.1 Scenario 1: Permission Leak
Developer A "optimizes for performance" by querying Doris directly:
SELECT * FROM entity_common
WHERE type = 'Employee'
AND attributes->>'department' = 'Engineering'
Result: Returns all properties for all engineering employees
Including salary, ssn, performance_review
Information that should only be visible to HR
Impact: Data leak → Compliance violation → Legal risk
#7.2 Scenario 2: Derived Property Inconsistency
Developer B directly modifies inventory quantity in Doris:
UPDATE entity_common
SET attributes = jsonb_set(attributes, '{quantity}', '100')
WHERE object_id = 'INV-001'
Result: quantity changed, but derived property total_value (= quantity * unit_price)
was not recalculated, still showing the old value
Inventory reports show inconsistent data
Impact: Business decisions based on wrong data → Financial discrepancies → Audit issues
#7.3 Scenario 3: Audit Breach
Developer C directly deletes a sensitive record from Doris:
DELETE FROM entity_common
WHERE object_id = 'CASE-007'
Result: Record is gone, but there's no deletion entry in audit logs
Cannot explain data whereabouts during regulatory review
Nessie version history also has no corresponding change
Impact: Audit non-compliance → Regulatory penalties → Reputation damage
#7.4 Scenario 4: Event Chain Breakage
Developer D directly updates an order status:
UPDATE entity_common
SET attributes = jsonb_set(attributes, '{status}', '"SHIPPED"')
WHERE object_id = 'ORDER-123'
Result: Order status changed, but:
- Downstream "shipment notification" Action was not triggered
- Customer did not receive shipping alert
- Inventory "shipped quantity" derived property was not updated
- Operations dashboard "pending shipment count" was not decremented
- Associated logistics World was not synchronized
Impact: Entire business process breaks → Customer complaints → Operational chaos
#7.5 Scenario 5: Multi-Tenant Data Leakage
Developer E writes a cross-tenant statistics query:
SELECT type, COUNT(*) FROM entity_common
GROUP BY type
Result: Returns statistics across all tenants
Tenant A can infer Tenant B's business scale
Violates data isolation commitments between tenants
Impact: Tenant trust crisis → Contract breach → Customer churn
#8. Design Principles of the Ontology Kernel
#8.1 Single Point of Access Principle
All data operations have one entry point: OntologyRuntimeService. There is no "fast lane for read-only queries," no "backdoor for bulk imports."
This principle borrows from database transaction log design -- all changes must go through the WAL (Write-Ahead Log) to take effect. OntologyRuntimeService is coomia-dip's "WAL."
#8.2 Zero Trust Principle
OntologyRuntimeService trusts no caller. Even for the platform's own internal services, every call still requires:
- A valid WorldContext
- Schema validation
- Permission checking
- Audit logging
This aligns with the "zero trust architecture" concept in network security -- always verify, never trust.
#8.3 Declarative Operations Principle
Callers do not need to know where data is stored (Doris? Iceberg? Redis cache?), nor how permission checks are performed. Callers simply declare:
message SearchObjectsRequest {
string object_type = 1; // What type I want to query
string filter = 2; // Filter conditions
repeated string select_properties = 3; // Which properties I need
int32 page_size = 4; // Items per page
string page_token = 5; // Pagination cursor
WorldContext context = 6; // In which World
}
OntologyRuntimeService handles converting the declarative request into concrete storage operations, permission filtering, and result assembly.
#8.4 Observability Principle
Every operation through the Ontology layer is observable:
- Metrics: Request volume, latency distribution, error rates, cache hit rates
- Traces: Distributed tracing (OpenTelemetry), pinpointing each layer's duration
- Logs: Structured audit logs, supporting post-hoc querying and analysis
Operations that bypass the database are a "black box" to the platform -- you do not know who queried what data when, or where performance bottlenecks lie.
#9. Comparison with Palantir Foundry
Palantir Foundry adopts a similar design philosophy but with different implementation details:
| Dimension | Palantir Foundry | coomia-dip |
|---|---|---|
| Data access entry point | Ontology API (REST) | OntologyRuntimeService (gRPC) |
| Schema management | Ontology Manager UI | SchemaRegistry (gRPC + UI) |
| Permission model | Marking-based ABAC | RBAC + ABAC hybrid |
| Audit | Audit Service | Kafka audit topics |
| Versioning | Dataset-level versions | Nessie branches + Iceberg snapshots |
| Derived properties | TypeScript OSDK computation | 7 computation strategy routing |
| Multi-tenancy | Enrollment-level isolation | 5-level isolation model |
The core consensus is the same: the Ontology is not decoration -- it is the only legitimate path for data access.
Palantir's official documentation explicitly states: "The Ontology is the single source of truth for how your data should be understood, accessed, and acted upon." coomia-dip inherits this philosophy and achieves lower-latency internal communication through gRPC.
#10. When the Ontology Tax Is Unacceptable: Exceptions and Countermeasures
#10.1 Bulk Data Import
When importing millions of records, processing each one through OntologyRuntimeService is too slow. The countermeasure:
Solution: Batch Import Pipeline
1. Data first lands in Staging Area (Parquet files on MinIO)
2. SchemaValidator validates the entire file in bulk
3. PermissionChecker checks import permission once
4. BulkLoader writes directly to Doris (bypassing per-record gRPC)
5. AuditLogger records one bulk import audit entry
6. EventPublisher publishes bulk change event
Key: Not "bypassing Ontology" but "Ontology's batch mode"
Schema validation and permission checks still execute
Only changed from per-record to bulk
#10.2 Data Analysis Queries
Data analysts need to execute complex SQL queries to explore data. The countermeasure:
Solution: Ontology-Aware SQL Gateway
1. Analyst writes SQL query
2. SQL Gateway parses the SQL
3. Injects permission filter conditions (based on user's RBAC/ABAC)
4. Forwards to Doris for execution
5. Records audit log
6. Returns results
Key: Allows SQL flexibility
But permissions and audit are still guaranteed by the Ontology layer
#10.3 Real-Time Stream Processing
When Kafka consumers need to process event streams in real time, routing every event through gRPC becomes a bottleneck. The countermeasure:
Solution: Embedded Ontology Validator
1. Stream processor preloads relevant ObjectType schemas on startup
2. Executes schema validation in-process (avoids network calls)
3. Permission checks via precomputed rule cache
4. Audit logs sent in async batches
5. Schema changes synced in real time to stream processor via Kafka events
Key: Embeds Ontology logic into the stream processor
Eliminates network call overhead
But Ontology's semantic guarantees remain unchanged
#11. Implementation Path: How to Enforce the Ontology Kernel in Your Team
#11.1 Code Level
# ❌ Prohibited: Using Doris client directly
from doris_client import DorisConnection
conn = DorisConnection("doris:9030")
result = conn.query("SELECT * FROM entity_common WHERE type='Equipment'")
# ✅ Correct: Access through Ontology SDK
from ontology_sdk import OntologyClient
client = OntologyClient(world_context=ctx)
result = client.search("Equipment", filter="status eq 'RUNNING'")
#11.2 Architecture Level
Dependency graph for all services:
Service A ──> ontology-sdk ──> OntologyRuntimeService ──> Doris
Service B ──> ontology-sdk ──> OntologyRuntimeService ──> Doris
Service C ──> ontology-sdk ──> OntologyRuntimeService ──> Doris
No service directly depends on a Doris client library
#11.3 CI/CD Level
# Dependency audit in .gitlab-ci.yml
dependency-audit:
script:
- |
# Check if any service directly depends on database clients
if grep -r "doris_client\|mysql.connector\|psycopg2" src/; then
echo "ERROR: Direct database access detected!"
echo "Use ontology-sdk instead."
exit 1
fi
#11.4 Code Review Checklist
During every code review, check the following items:
- No direct database connections or SQL queries
- All data operations through
ontology-sdkorOntologyRuntimeService - Valid WorldContext provided
- No hardcoded ObjectType names in code (use constants or configuration)
- Batch operations use
BatchGetObjectsinstead of loopingGetObject
#Key Takeaways
-
The "Ontology Kernel" is not over-engineering -- it is a necessary architectural constraint. By unifying all data access through OntologyRuntimeService, we trade 1-2ms of latency for automated schema validation, permission checking, audit logging, and event publishing. These 4 layers of defense eliminate tens of thousands of lines of duplicate code and countless security vulnerabilities.
-
The "Ontology Tax" can be optimized to near zero. Through two-tier caching, rule pre-compilation, async audit, and batch operations, the per-request overhead is only 1-2ms. In batch scenarios, the amortized tax approaches zero. The optimization techniques are numerous, while the architectural benefits are constant.
-
The cost of bypassing the Ontology far exceeds the Ontology Tax. Permission leaks, data inconsistency, audit breaches, event chain breakage, multi-tenant data leakage -- any single issue causes far more damage than 1-2ms of latency. This is why "no direct database access" is a red line, not a suggestion.
“Next Article Preview: [S2-05] Multi-Tenant Architecture: 5-Level Isolation Model -- a deep dive into how Tenant → Org → Space → Project → World provides strict data and compute isolation on shared infrastructure.
Tags: #ontology #ontology-kernel #data-governance #access-control #audit #schema-validation #grpc #coomia-dip