The atomised entity: a central row and its parts
Table of Contents
1. Summary
An entity is one keyed central row plus its parts. The central row carries the key, the owning party, the tenant, the market-data address, the change audit, and the entity's version. A part carries the fields that change for a reason of their own, and reaches the central row by its key.
No aggregate type and no single row holds the whole entity. A write to any part bumps the central row's version in the same transaction, so the central row's validity windows partition time into spans in which the parts are stable. Reading the entity at a version, or at an instant, is therefore a temporal join rather than the lookup of one stored object. The wire carries one typed array per part, and the screen assembles them for display.
This is the storage and wire shape behind Data-Oriented Design in ORE Studio's rule that there is no entity.
2. Detail
2.1. What an entity is
An entity is a set of keyed facts, not an object. The facts that answer to one key are the entity. Nothing owns them, and nothing versions as a whole except the central row, whose version stands for the state of the whole.
The rule this page applies is in Data-Oriented Design in ORE Studio: the components are separate types, no aggregate type represents an entity, and no entity object travels. See also the book it cites, section 5.4 of Richard Fabian's Data-Oriented Design (2013), There is no Entity.
2.2. When a field set becomes a part
A field set earns a part when it has at least one of these.
- Its own multiplicity, so a row is absent, one, or many for one entity.
- Its own change reason, so it moves without the rest.
- Its own reader, so a consumer needs it and not the rest.
A field set with none of them stays a column on the central row. One row per entity, one change reason and one reader make it a column group in a table's clothes, and a part would buy a join and a message it does not need.
The split is by access, not by real-world taxonomy. Two fields belong together when the same write touches both and the same read needs both.
2.3. The central row
- The key is a UUID. One key shape serves every family, so a part and its central row join on one column.
tenant_idandparty_id. Isolation is by tenant and ownership is by party, and both are stamped from the session rather than from the caller.- The natural key. The identifier the domain carries, such as the ORE id of a convention, is unique per tenant and party and is not the row key.
- The market-data reference. One address per entity, where the entity binds to market data.
- The change audit:
modified_by,performed_by,change_reason_code,change_commentaryandrecorded_at. Every entity carries it, and Data-Oriented Design in ORE Studio's regime 2 says so. - The version. It is the entity's own and no two entities share one.
The version is the optimistic-concurrency token, so the rules in Optimistic Concurrency Versioning govern who may move it and what a writer must claim to be allowed to.
2.4. The parts
- A part is named for its family and its own concern, such as
tenor_basis_swap_pay_receive. The family comes first, so the owner of a table or a type is readable from its name. - A part reaches its central row by the central key and by nothing else.
- A part is itself an entity. It carries its own audit record and its own version.
A part's version counts the changes to that part. The central version counts the changes to the entity, which is every change to any part plus the entity's own. The two are different sequences on different things and are never compared. A screen that shows both must say which is which. This is the distinction Temporal Composite Entity Versioning: Target State draws between a child's version and its parent's.
2.5. The composition rule
A write to any part bumps the central entity's version, in the same transaction. The transaction closes the central row's window and opens a new one at the same instant, closes the changed part's window and opens a new one, and leaves every unchanged part open.
Because the bump happens in the same transaction as the part write, no instant exists at which the parts and the central version disagree. That is what lets a read assemble the entity from the timeline alone, with no mapping table and no stored aggregate.
The row lock that serialises concurrent writers, the soft-delete symmetry so that removing a part bumps the version too, and the multi-level case are in Temporal Composite Entity Versioning: Target State.
2.6. Reading a composition from the timeline
Time is the link. The central row's validity windows partition time into spans in which no part changed, because any part change would have closed the window. Within a span every part's window covers the instant, so the parts that composed a version are exactly the parts current at that version's instant.
The read has two steps.
- Take the central row, either by version or by instant. Its window gives the instant to resolve at.
- For each part, take the row with the greatest version whose window covers that instant.
No link table is needed, because the windows already record which part versions composed which central version. The interval test generalises the as-of point query in Time and Timestamps: Architecture and Conventions from an instant to a window.
A date question resolves the central row at the instant first, and then the parts at the same instant. A cross-entity question resolves each entity at the one instant. Version numbers do not cross an entity boundary, so the instant is what aligns several entities.
2.7. The wire
The wire carries one typed array per part, aligned over one page. A part's fields never appear as nested objects inside another part.
The server keeps filtering, ordering, paging and the total, so the arrays stay aligned and the screen zips them for display. That is a projection, not a join. A screen whose rows span several entities is a read model, never a client join. See NATS entity protocol specification for the reads and the read model, and Polymorphic payloads over NATS for the one-array-per-type shape.
The list read carries the central row and the audit alone. A list that also carried every part would carry the whole entity population in one reply. The detail read is the one that pulls the parts.
2.8. Limits accepted
2.8.1. Two commits in the same microsecond
PostgreSQL timestamps have microsecond resolution. The version guard serialises writers and does not make their timestamps unique, so two sequential commits can share an instant.
This is accepted and needs no tiebreaker, because the windows are
half-open. When the older version's valid_to equals the newer version's
valid_from, the older version's window is empty and covers no instant, so
an as-of read resolves to the newer version alone. The older version keeps
a zero-length window. It is readable by version and its audit stands, but
no date resolves to it. See
Time and Timestamps: Architecture and Conventions for the interval
convention.
2.8.2. Filtering and ordering reach the central row only
A list reads the central row, so it can filter and order by the central row's columns alone. A screen that must find an entity by a part's value needs a read model, per NATS entity protocol specification.
2.8.3. The timeline costs storage and a lookup per part
Every change writes one central row. The part that changed writes one part row, and an unchanged part is not copied. A read pays one temporal lookup per part.
2.8.4. Two versions on one entity
The central row and each part each carry a version. They are different sequences and the names must not be read as one number.
3. See also
- Data-Oriented Design in ORE Studio — the rule this page applies, and the C1202 ceiling the atomised shape relieves.
- Codegen patterns: choosing a model shape — the composite pattern that builds this shape, and the field-group patterns it replaces.
- Temporal Composite Entity Versioning: Target State — the bump mechanism, the row lock and the window join this page assumes.
- Time and Timestamps: Architecture and Conventions — the bitemporal model, the infinity sentinel, and the as-of query.
- Optimistic Concurrency Versioning — the guard a part write must satisfy.
- NATS entity protocol specification — the reads, the as-of guarantee and the read model.
- Polymorphic payloads over NATS — one typed array per concrete type, joined by key.
- Trade Structures and Deal Composition — the domain vocabulary for a deal built from its parts.
- Trade Versioning — the precedent for two version numbers on one thing.
- Entity-table writes go through the repositories — the write path an entity table expects.
- Data storage — the structure note that orders this cluster, and where to read this page in it.