Reladomo → DynamoDB
How a Java ORM built for relational databases persists to DynamoDB without reimplementing a single rule about time.
Reladomo is a bitemporal ORM: every row knows both when a fact was true and when the system learned it. The instinct when porting that to DynamoDB is to rebuild those rules on top of a key-value store. That instinct is wrong, and the reason is structural.
Reladomo's temporal logic lives in its TemporalDirector, which sits above
persistence. By the time anything reaches the storage layer, the director has already decided which
rows should exist and handed down plain inserts, updates and deletes — each carrying its own
timestamps.
The practical consequence: bitemporality is inherited, not rewritten. Any temporal reasoning added below that seam would drift from a relational Reladomo — which is precisely what the differential test suite exists to catch.
Most developers meet "bitemporal" as a word before they meet the problem. The problem is ordinary: you learn things late.
A balance was 10 from January. In June you discover it should have been 99 from March. A single-axis store must choose: overwrite history and lose what you reported in April, or keep the old value and lie about the present. Bitemporal storage refuses the choice by recording both — when the fact was true and when you knew it.
setQuantity(99) against a March business date. The director closes the
old belief at June and opens two replacements — so the April report still reproduces exactly,
and today's query returns 99.DynamoDB has no notion of a rectangle. Each corner of that picture becomes one item, addressed by a partition key and a sort key. The sort key is where both axes live:
The clearest demonstration isn't a ledger — it's a decision table where the rules themselves are bitemporal. Classify cars by year, make and body style. What counted as cool in 2012 counts as retro now, and the car never changed:
| Car | 2012 | 2016 | 2021 | today |
|---|---|---|---|---|
| 1985 Toyota MR2 coupe | COOL | EIGHTIES_COOL | RETRO | RETRO |
| 1957 Chevrolet Bel Air coupe | CLASSIC | CLASSIC | CLASSIC | VINTAGE |
| 2018 Tesla Model 3 sedan | — | — | MODERN | MODERN |
That table is real output from a runnable demo, not an illustration. Every column is the same query against the same unchanged rows, asked as of a different date.
Then the second axis earns its keep: correct a rule that was wrong when it was written, re-run a 2012 classification, and it disagrees with the audit row stored in 2012. What we said then versus what we now think we should have said then — two different questions, both answerable, which is the entire reason this machinery exists.
Stated plainly, because discovering these in production is worse than reading them here. The full list is the support contract.
| Transactions | DynamoDB has no interactive transaction. A Reladomo transaction commits as one TransactWriteItems across all entity tables, which caps at 100 actions and 4 MB; wider scopes are refused rather than chunked. |
|---|---|
| Arbitrary predicates | Queries start from an exact partition key. Anything else needs a Scan, which is opt-in — the planner refuses by default rather than running one quietly. |
| Item size | 400 KB per item; 2048-byte partition key; 1024-byte sort key. |
| Decimals | Stored as strings. DynamoDB's numeric type trims trailing zeros, which would silently change the scale of money. |