Ontology as API: Why Ontology Models Beat REST APIs as Contracts
Every engineer who has built a large-scale platform has lived through this nightmare:
Ontology as API: Why Ontology Models Beat REST APIs as Contracts
“Series: S10 Design Patterns · Article 1 | Level: Advanced | Reading Time: 18 min
#TL;DR
- Traditional REST APIs center on "endpoints." Every new business scenario requires a new set of endpoints, leading to endpoint explosion and version hell.
- The Ontology-as-API pattern uses ontology models as the contract layer. All operations target "object-relation-property" triples rather than hardcoded endpoint paths.
- coomia-dip auto-generates gRPC services, SDK clients, and permission policies from Ontology Schema — model once, use everywhere.
#Introduction: The API Design Dilemma
Every engineer who has built a large-scale platform has lived through this nightmare:
/api/v1/users
/api/v1/users/{id}/orders
/api/v1/users/{id}/orders/{orderId}/items
/api/v2/users/{id}/orders/{orderId}/items # v2 added discount field
/api/v1/users/{id}/recommendations # new requirement
/api/v1/users/{id}/risk-score # yet another requirement
Every new business requirement means a new endpoint. Every field change means a new version. You think you are doing API design, but you are actually doing endpoint operations.
And this is just one microservice. When you have 20 microservices, each with 30 endpoints, and 3 concurrent versions, you are staring at 1,800 endpoints. No documentation can keep up, and no team can guarantee their consistency.
The root cause is not that REST is bad — it is that endpoint-centric contract models are structurally unfit for complex business semantics.
#Part 1: Three Structural Flaws of REST APIs
#1.1 Coupling Between Endpoints and Business Logic
REST's design philosophy is "resource + verb." This works beautifully for simple CRUD:
GET /users -> List users
POST /users -> Create user
GET /users/{id} -> Get user
PUT /users/{id} -> Update user
DELETE /users/{id} -> Delete user
But real business is not CRUD. When you need to "transfer a user from Department A to Department B, simultaneously updating permissions, notifying their manager, and recording an audit log," which endpoint do you call?
PUT /users/{id}/department?to=B # Option 1
POST /users/{id}/transfer # Option 2
POST /operations/user-transfer # Option 3
All three are compromises. REST's "resource + verb" model simply lacks the expressive power for business operations. You end up inventing new endpoints beyond standard verbs — that is why REST APIs inevitably degenerate into RPC-over-HTTP.
#1.2 Insufficient Expressiveness for Cross-Entity Queries
Business analysts frequently ask: "Find all users who placed orders in the last 30 days with a risk score above 80, group them by department, and show average order value per department."
Answering this with REST requires multiple endpoint calls plus client-side aggregation — the infamous N+1 query problem. GraphQL partially solves it but introduces query parsing, depth limits, and cache invalidation complexity.
#1.3 The Bottomless Pit of Version Management
Adding a field to a response forces every consumer to handle the change. You face three undesirable choices: breaking changes, version explosion, or shifting complexity to callers. With 8 Layers, 63 proto files, and hundreds of services, traditional REST versioning would require a dedicated team.
#Part 2: The Core Idea of Ontology-as-API
#2.1 The Key Insight
“API contracts should not be endpoint lists — they should be business object models.
- Traditional REST: Contract = URL paths + request/response JSON Schema
- Ontology-as-API: Contract = Object types + property definitions + relationship definitions + action definitions
You no longer design endpoints — you design ontology models:
ObjectType: TransferOrder
Properties:
- sourceWarehouse: Link<Warehouse>
- targetWarehouse: Link<Warehouse>
- items: List<Link<InventoryItem>>
- status: Enum<Draft, Pending, Approved, Executed, Cancelled>
Actions:
- approve(approver: User, comment: String) -> TransferOrder
- execute(executor: User) -> TransferOrder
- cancel(reason: String) -> TransferOrder
All platform capabilities — query, mutation, subscription, permissions — are automatically generated from this model.
#2.2 Automatic Derivation from Model to API
Once you define an Ontology Schema in coomia-dip, the following are auto-generated:
ObjectType Definition
|
v
gRPC Service -> Typed CRUD + Action interfaces
Python SDK -> client.TransferOrder.get(id)
TypeScript SDK -> await client.TransferOrder.get(id)
Permission Rules -> ABAC policies based on object types
Subscription -> Real-time object change notifications
Audit Trail -> Automatic operation logging
The key: generated APIs inherently understand business semantics. When you define an approve Action on TransferOrder, the system knows this requires permission checks, triggers state transitions, and needs audit logging.
#2.3 Comparison with Palantir Foundry
Palantir Foundry pioneered Ontology-as-API. coomia-dip improves on it with:
- Open-source transparency: Schema definitions and derivation logic are fully open source
- gRPC core: All internal communication uses gRPC for better performance than Foundry's REST core
- Multi-language SDK: Auto-generates typed SDKs for Python, TypeScript, and Java
#Part 3: Implementation Architecture in coomia-dip
#3.1 Schema Registry: The Metadata Hub
The Control Layer (Control Layer) contains the Schema Registry, which stores and manages all Ontology Schema definitions. It maintains a complete type relationship graph — which types are related, which properties are derived, which Actions trigger cascading operations.
Schema Registry
|-- ObjectTypes: User, Department, Order, Product, ...
|-- RelationTypes: User->Department, User->Order, ...
|-- Property Schemas
|-- Action Definitions
|
v
API Gateway -> Dynamic routing based on Schema
SDK Generator -> Generates typed clients from Schema
Permission -> Generates ABAC policies from Schema
#3.2 Auto-Generated gRPC Services
When a new ObjectType is registered, the Control Layer auto-generates corresponding gRPC service definitions:
service TransferOrderService {
rpc Get(GetTransferOrderRequest) returns (TransferOrder);
rpc List(ListTransferOrdersRequest) returns (ListTransferOrdersResponse);
rpc Create(CreateTransferOrderRequest) returns (TransferOrder);
rpc Update(UpdateTransferOrderRequest) returns (TransferOrder);
rpc Delete(DeleteTransferOrderRequest) returns (Empty);
rpc Approve(ApproveTransferOrderRequest) returns (TransferOrder);
rpc Execute(ExecuteTransferOrderRequest) returns (TransferOrder);
rpc Cancel(CancelTransferOrderRequest) returns (TransferOrder);
rpc Subscribe(SubscribeTransferOrderRequest) returns (stream TransferOrderEvent);
rpc GetRelated(GetRelatedRequest) returns (GetRelatedResponse);
}
Key: Actions are first-class citizens, subscriptions are built-in, relationship queries are built-in.
#3.3 Type-Safe SDK Clients
from ontology_sdk import OntoPlatform
platform = OntoPlatform(endpoint="grpc://control-Layer:9090")
# Type-safe object operations
order = platform.objects.TransferOrder.get("order-123")
print(order.source_warehouse.name)
# Type-safe Action invocation
approved_order = order.approve(approver=current_user, comment="Approved")
# Type-safe querying
high_value_orders = (
platform.objects.TransferOrder
.where(lambda o: o.total_value > 100000)
.where(lambda o: o.status == "Pending")
.order_by(lambda o: o.created_at, desc=True)
.limit(20)
.list()
)
Every method call has full type hints. IDEs auto-complete, and mypy catches errors at compile time.
#3.4 Automatic Permission Binding
Permissions are part of the Schema, not manually bound:
ObjectType: TransferOrder
Actions:
approve:
permissions:
- role: WarehouseManager
condition: object.sourceWarehouse.manager == caller
- role: SupplyChainDirector
condition: always
Permission checks happen automatically when gRPC endpoints are called, fundamentally eliminating "forgot to add permission check" vulnerabilities.
#Part 4: Solving REST's Three Flaws
#4.1 Goodbye Endpoint Explosion
Adding business capabilities = adding ObjectTypes or Actions to Schema. The system auto-generates all interfaces. Growing from 10 to 100 entity types means managing 100 Schema definitions, not 3,000 endpoints.
#4.2 Goodbye N+1 Queries
Cross-entity queries are first-class citizens. Queries are translated into optimized execution plans. The Ontology layer knows entity relationships and selects optimal query paths automatically.
#4.3 Goodbye Version Hell
Schema version changes are auto-analyzed as "backward compatible" or "breaking." Compatible changes auto-deploy; breaking changes require explicit migration plans.
#Part 5: Practical Guide
#5.1 Start from Domain Models
Traditional: Requirements -> API design -> Implementation -> Docs
OaaA: Requirements -> Domain modeling -> Ontology Schema -> Auto-generate everything
Design principles: derived properties auto-compute, state machines define valid transitions, relationships are bidirectional.
#5.2 Action Design Best Practices
- Express business intent, not technical operations
- Atomic — all-or-nothing
- Declare side effects — notifications, auditing, workflow triggers happen automatically
#5.3 Schema Evolution Strategies
- Adding properties: Always safe
- Removing properties: Two-step — deprecate first, remove after deadline
- Modifying relationships: Use migration tools for validation and rollback
#Part 6: Performance Considerations
#6.1 Three-Level Caching
L1: In-process cache (HashMap, TTL=60s)
L2: Distributed cache (Redis, TTL=5min)
L3: Schema Registry (PostgreSQL)
Schema changes are broadcast via event bus as invalidation notifications.
#6.2 Query Optimization
The query optimizer selects optimal execution plans based on data distribution and indexing — potentially SQL JOINs or graph traversals.
#6.3 Synergy with gRPC
Protobuf definitions map 1:1 to Ontology Schema. Server Streaming supports subscriptions. Binary serialization is 3-10x smaller than JSON.
#Part 7: Synergy with Other Patterns
| Pattern | Synergy |
|---|---|
| State Machine (S10-05) | Action state transitions defined in Schema |
| Cascade (S10-08) | Derived property dependencies declared in Schema |
| Query Rewrite (S10-09) | Permission rules derived from Schema |
| Contract-First (S10-12) | Schema is upstream source for proto files |
| Multi-Tenancy (S10-14) | Tenant isolation policies defined in Schema |
#Part 8: Anti-Patterns and Pitfalls
#8.1 Over-Modeling
Not all data needs to enter the Ontology. Rule of thumb: if business users operate on it directly, it is an ObjectType. If only developers use it in code, it is an internal data structure.
#8.2 Everything as an Action
Simple property modifications should use standard Update. Actions are reserved for business-meaningful operations involving state transitions, permission checks, and side effects.
#8.3 Neglecting Relationship Design
Relationships are the soul of Ontology. Few inter-ObjectType relationships suggest REST-style thinking in Ontology design.
#Part 9: Case Study — Supply Chain Decision Platform
A manufacturing company's supply chain decision platform:
ObjectTypes:
Supplier:
properties: { name: String, rating: Decimal, leadTime: Duration }
derived:
onTimeRate: Decimal = orders.count(onTime) / orders.count()
Warehouse:
properties: { name: String, location: GeoPoint, capacity: Integer }
derived:
utilizationRate: Decimal = inventory.sum(quantity) / capacity
criticalItems: List<InventoryItem> = inventory.filter(needsReorder)
actions:
requestRestock: { items, urgency } -> List<PurchaseOrder>
InventoryItem:
properties: { quantity: Integer, reorderPoint: Integer }
derived:
needsReorder: Boolean = quantity <= reorderPoint
SDK usage:
platform = OntoPlatform(endpoint="grpc://control-Layer:9090")
critical = platform.objects.Warehouse.where(
lambda w: w.critical_items.count() > 0
).list()
for wh in critical:
wh.request_restock(items=wh.critical_items, urgency="Urgent")
Zero REST calls. Fully type-safe. Automatic permission checks.
#Part 10: Summary
Ontology-as-API is coomia-dip's most fundamental design decision:
- Endpoint explosion -> Schema definitions replace endpoint lists
- N+1 queries -> Relationship-aware query engine
- Version hell -> Schema evolution replaces API version management
You no longer design APIs — you design business models. APIs are projections of models.
#References
- Palantir Foundry Ontology Documentation↗
- coomia-dip Architecture Overview
- gRPC Official Documentation↗
- Eric Evans, Domain-Driven Design, Addison-Wesley, 2003