Back to Blog

Our Ontology Kernel: Everything Must Go Through the Ontology Layer

TL;DR

CoomiaPublished on June 27, 202521 min read
Share this articleTwitter / X

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:

Code
❌ 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."

Code
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:

Code
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:

ConceptDescriptionAnalogy
ObjectTypeObject type definition (properties, constraints, indexes)Database table DDL
RelationTypeRelation type definition (direction, cardinality, properties)Foreign key + junction table
ActionTypeAction type definition (input, output, side effects)Stored procedure signature
PropertyProperty definition (type, validation rules, derivation rules)Column definition + CHECK constraint
DerivedPropertyDerived 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.

PROTOBUF
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

Code
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

Code
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:

Code
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

Code
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:

TopicPurposeRetention
audit.data-accessData read operations90 days
audit.data-mutationData write operations365 days
audit.admin-operationAdmin 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

Code
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:

Code
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

BenefitDirect DB AccessThrough OntologyExtra Effort
Schema consistencyManual guaranteeAutomatic0 lines of code
Property-level permissionsMust implement yourselfBuilt-inSaves hundreds of lines
Audit logsMust integrate yourselfAutomaticSaves all audit middleware
Data lineageImpossibleAutomaticSaves a dedicated lineage system
Derived property cascadeMust trigger yourselfAutomaticSaves all trigger logic
Search index syncMust sync yourselfAutomaticSaves sync pipeline
Version rollbackMust record yourselfNessie automaticSaves 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)

Code
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

Code
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

Code
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

Code
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

Code
┌─────────────────────────────────────────────────────┐
│              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
Code
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)

Code
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)

Code
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:

Code
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

Code
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

Code
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

Code
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

Code
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

Code
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

Code
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

Code
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:

PROTOBUF
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:

DimensionPalantir Foundrycoomia-dip
Data access entry pointOntology API (REST)OntologyRuntimeService (gRPC)
Schema managementOntology Manager UISchemaRegistry (gRPC + UI)
Permission modelMarking-based ABACRBAC + ABAC hybrid
AuditAudit ServiceKafka audit topics
VersioningDataset-level versionsNessie branches + Iceberg snapshots
Derived propertiesTypeScript OSDK computation7 computation strategy routing
Multi-tenancyEnrollment-level isolation5-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:

Code
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:

Code
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:

Code
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

Python
# ❌ 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

Code
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

YAML
# 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-sdk or OntologyRuntimeService
  • Valid WorldContext provided
  • No hardcoded ObjectType names in code (use constants or configuration)
  • Batch operations use BatchGetObjects instead of looping GetObject

#Key Takeaways

  1. 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.

  2. 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.

  3. 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