Skip to content

Identity

Every record written by rhizodat carries service fields:

Field Type Meaning
dat_id UUIDv7 Entity identity. Generated by the layer, never by a store. Every part of one entity has the same dat_id in every store.
dat_type string Entity type, for stores that hold several types in one table or graph.
dat_txn UUIDv7 The meta-transaction that wrote this version. Used for idempotent apply and for filtering uncommitted data.
dat_tx_time HLC timestamp Transaction time of this version (see Time).

UUIDv7 is time-ordered, which suits ORDER BY in ClickHouse, sorting in Iceberg and B-tree locality in PostgreSQL.

UUIDv7 follows RFC 9562 with the 12-bit field used as a counter (section 6.2, method 1): ids from one generator are strictly increasing, also within one millisecond and when the wall clock steps back.

Storage per store: PostgreSQL uuid, ClickHouse UUID, drevo node property, Iceberg uuid (falls back to fixed[16] where the engine lacks it).

Links between entities are references by dat_id. Foreign keys cannot cross stores, so the Unit of Work checks referential integrity before commit.

Entity mapping

Which part of an entity lives in which store is declared per entity type; there is no central registry to look up.

from rhizodat import entity, Ref

@entity(stores={
    "pg":         ["status", "amount", "customer"],
    "drevo":      ["related"],          # links as graph edges
    "clickhouse": ["events"],           # append-only facts
    "iceberg":    ["snapshots"],        # long-term history
})
class Order:
    status: str
    amount: Decimal
    customer: Ref["Customer"]
    related: list[Ref["Order"]]
    events: list[str]
    snapshots: list[bytes]

The narrow case is one store; the wide case is all four. Both use the same API.

  • Every declared field maps to exactly one store, and every mapped name is a declared field. ClassVars are not fields.
  • dat_* names are reserved; so are valid_from / valid_to when valid time is on (@entity(..., valid_time=True)).
  • The type name stored as dat_type defaults to the class name; set name="tracker.Issue" to keep it stable across renames and language ports.
  • @entity makes the class a keyword-only dataclass with a dat_id field (a new UUIDv7 unless given).
  • A reference is Ref[T], a value holding the target's dat_id, also inside containers or | None. One field refers to one target type; a union of several is rejected in v0.1.