Data Authority Module Pattern
A Data Authority Module is the primary pattern for implementing modules that own and manage a set of entities. It has a well-defined four-layer structure and a standard pattern for exposing CRUDQ operations.
Layer Structure
Section titled “Layer Structure”A Data Authority Module consists of four distinct layers:
1. Protocol Adaptor Layer
Section titled “1. Protocol Adaptor Layer”Responsible for:
- Handling communication protocols that expose the module’s API Endpoints
- Protocol data marshalling and unmarshalling
- Entity identity extraction from requests
- Routing requests to the appropriate operations in the Service Layer
- Conversion between wire timing/concurrency models and internal Service concurrency model (synchronous vs. asynchronous, multipart messages)
2. Service Layer
Section titled “2. Service Layer”Responsible for:
- Definition of data structure, validation, and behavior of individual entities and value objects managed by the service
- All business logic implementable by traversing the object graph reachable from an entity instance
- Transactional boundary of the module — individual operations typically represent transactional updates
- Coordination of persistence operations across multiple Universes (e.g., referential integrity between separate entities)
- Coordination of proxy operations that invoke other modules’ services, including distributed commit/rollback (saga patterns)
- Generation of entity identities and ADNs; interpretation of ADNs to resolve entity values
- Generation and emitting of Notifications in response to changes
3. Persistence Layer
Section titled “3. Persistence Layer”Responsible for:
- Storage and retrieval of entity values from persistent storage
- Management of bitemporal changes to entity values
- Validation and management of entity collections (concept of
Universe) including uniqueness and ordering - Defining
EntityServiceConfigurationper entity type to build structured locator translators that map JSON field names to database columns for the Query DSL
4. Proxy Layer
Section titled “4. Proxy Layer”Responsible for:
- Providing access to API Endpoints of other modules
- Allowing the Service Layer to interact with other modules without knowing their implementation or runtime location
Endpoints
Section titled “Endpoints”API Endpoints expose module capabilities to other modules and external systems. Each module may expose multiple API Endpoints.
Request/Response Endpoints
Section titled “Request/Response Endpoints”Specify a set of operations, each defined by a Pair<Request, Response>. Operations always specify a response (which may be a simple acknowledgement). May be synchronous or asynchronous.
Implemented using:
- HTTP/REST for
Request/Response - gRPC for
Request/Response - SQS, WebSockets, Kinesis, or Kafka for
Streaming
Streaming Endpoints
Section titled “Streaming Endpoints”Produce or consume a stream of Notifications. Usually asynchronous.
Layer Responsibilities Summary
Section titled “Layer Responsibilities Summary”| Layer | Key Responsibilities |
|---|---|
| Protocol Adaptor | Wire serialization/deserialization, protocol-specific concerns, request routing |
| Service | Business logic, transaction boundaries, multi-universe coordination, notifications |
| Persistence | Bitemporal storage, collection management (Universe), scoping |
| Proxy | Abstracted access to other modules’ endpoints |
Code Structure
Section titled “Code Structure”Arda’s Ktor-based implementation follows this wiring pattern (see Ktor Module Wiring):
fun Application.myModule( inComponent: ComponentConfiguration, locator: EndpointLocator.Rest, cfg: ModuleConfig, authentication: Authentication, injectedUniverse: MyUniverse? = null, injectedService: MyService? = null,): MyService { val db: Database = DataSource(cfg.dataSource!!.db, cfg.dataSource!!.pool) .newDb(cfg.dataSource!!.flywayConfig) val service = injectedService ?: MyService.Impl(injectedUniverse ?: MyUniverse(), db) val endpoint = MyEndpoint.Impl(cfg, locator, service) MultiEndpointKtorModule(inComponent, cfg, authentication, listOf(endpoint)) .configureServer(this) return service}See examples in the item-data-authority repository.
Universe Definition
Section titled “Universe Definition”The Universe concept represents the complete collection of entities managed by the persistence layer for a module. Its design is covered in Universe Design.
The
universe-definition.mdsource document is marked “To be added” — content is available in the universe-design.md page.
Cross-Service Isolation
Section titled “Cross-Service Isolation”Two services co-located in the same component must not share persistence. The boundary between services is enforced at four levels:
- Each service owns its own
Universeinstance. No service reads from or writes to another service’s universe. The sharedDatabaseconnection pool is reused, but the universes are independent. - Each service interface method establishes its own transaction via
inTransaction(db) { ... }. A cross-service interface call crosses the transaction boundary — the calling service’s transaction does not extend across the call; the called service starts its own. - No SQL
FOREIGN KEYconstraints across service boundaries. Cross-service references are stored as soft references in the holder’s table — either as a UUID-only column or as an Arda Domain Name (ADN) string when the holder needs to address an entity in another universe without depending on the foreign service’s schema. Within a single universe (e.g., the bitemporalpreviouschain, or a parent-child child-to-parent reference), foreign keys are fine and encouraged. - Resolution at runtime goes through the owning service’s interface. The calling service does not query the other service’s tables or universe directly; it calls the interface method (
itemService.getAsOf(itemEId, asOf), etc.), which returns through that service’s transaction.
Concrete examples in the operations repo:
OrderLine.itemEId: UUIDreferencesItemwith no FK; resolution viaitemService.getAsOf(...).KanbanCard.ITEM_REFERENCE_*referencesItem(flattened reference value object) with no FK; resolution viaitemService.getAsOf(...).- The
V001__order.sqlmigration explicitly omits theorder_line.item_eidFK to theitemtable.
This isolation supports independent service evolution, single-service-scoped transactional invariants, and clean operational boundaries (e.g., one service’s deployment cannot break another’s writes mid-transaction).
For the soft-reference value-object pattern in Kotlin, see Entity References § Implementation in Kotlin. For the design playbook covering when and how to apply these rules to a new entity, see Information Model Design.
See Also
Section titled “See Also”- Information Model Design — design-time playbook for defining new entities with the right shape, naming, and reference patterns.
Copyright: © Arda Systems 2025-2026, All rights reserved