-
Notifications
You must be signed in to change notification settings - Fork 1
Design and Internals
What this page covers: a map of the project's design-rationale documents (the specs/*.md files) with
one-line summaries, plus the two cross-cutting idioms that explain most of the codebase once you've seen
them — capabilities are interfaces, not flags and there is no global registry. This is the page to read
before changing something that "looks redundant" — it usually isn't. The wiki teaches; the specs justify.
📌 Note — the specs are the canonical home for why a design is the way it is (and the hazard analysis behind it). The wiki links to them rather than re-deriving the rationale; the Javadoc owns the exact per-member contract. When the wiki and a spec disagree on intent, the spec wins.
The design rationale lives in specs/ at the repository root. Each file is a self-contained decision record
for one slice of the library.
| Spec | Status | What it justifies |
|---|---|---|
SPEC_refs_and_managers.md |
Implemented (manager/) |
The everydatabase-manager add-on: why refs and caching are a separate façade module, the three decoupled concerns (serialization / type recovery / resolution), the cell + monotonic-stamp + tombstone model, and the per-context RefRegistry (no global registry). |
SPEC_distribution_modules.md |
Binding | The three distribution flavors (core / standalone / libby): the dependency-scope decisions, the standalone relocation map, the GPL MySQL-driver exclusion from the fat jar, the slf4j not-relocated decision, and why libby's POM excludes core's transitive set. |
SPEC_storage_logging.md |
Binding | The logging subsystem: the silent-by-default policy with an unsuppressible ERROR floor, the topic/level model, the privacy defaults (counts/durations, never content), the sink-resolution order, and the implementation checklist of which ops emit. |
SPEC_wiki.md |
Proposed | This wiki itself: taxonomy, page inventory, the source-of-truth hierarchy (README sells, wiki teaches, specs justify, Javadoc specifies), and the callout/code/language conventions every page follows. |
📌 Note —
SPEC_refs_and_managers.mdandSPEC_wiki.mdlive inspecs/today;SPEC_distribution_modules.mdandSPEC_storage_logging.mdare the rationale documents the build files andCLAUDE.mdcite as binding for the flavors and the logging subsystem. Read them next to the implementation they govern (standalone/build.gradle,core/build.gradle, and thelog/package).
The central design idiom. Optional features are expressed as extra interfaces a Storage may implement,
discovered with instanceof — never as a boolean you have to trust. The compiler stops you from using a
capability a backend doesn't have.
if (storage instanceof TransactionalStorage tx) {
tx.inTransaction(scope -> { /* ... */ }); // only reachable on backends that actually support it
}
if (storage instanceof SchemaAwareStorage schema) {
schema.register(new V001_Init()).migrate().join();
}The capability interfaces:
| Interface | Adds | Backends that implement it |
|---|---|---|
tx.TransactionalStorage |
inTransaction(scope -> future) |
SQL (incl. H2), Mongo (replica set), InMemory (no isolation) — not LocalFile |
schema.SchemaAwareStorage |
register(...).migrate(), forward-only, _schema_migrations
|
SQL, Mongo, LocalFile — not InMemory |
🧭 Decision — a flag (
storage.supportsTransactions()) would let a backend claim a feature it can't honor, deferring the failure to runtime. An interface makes "no backend pretends to support something it can't" a compile-time fact. Preserve this idiom when adding features: a new optional capability is a new interface a subset of backends implements, not a method onStoragethat throwsUnsupportedOperationException. See Architecture Overview, Transactions, Schema Migrations.
A related expression of the same principle: optimistic locking is enforced only by the backends that
genuinely can (MySQL/MariaDB, PostgreSQL, Mongo). H2 opts out explicitly
(H2SqlRepository.supportsVersioning() returns false) and silently degrades to plain upsert — documented and
tested, never a silent lie. See Optimistic Locking.
The manager add-on used to be tempted toward a process-wide static Class<?> → resolver map. It deliberately
doesn't have one. A RefRegistry is a per-context instance (new RefRegistry()), and a Ref resolves
only against the registry it was bound to.
RefRegistry survival = new RefRegistry();
RefRegistry lobby = new RefRegistry();
survival.manager(heroDesc, survivalStore, CachePolicy.always()); // Hero -> survivalStore
lobby.manager(heroDesc, lobbyStore, CachePolicy.always()); // Hero -> lobbyStore — no collision🧭 Decision — a static default would let two independent contexts (two plugins) register a manager for the same entity type and silently collide (last-writer-wins), discovered only in production. Making the registry per-context turns that silent collision into a thing that can't happen: two registries can each own a
Heromanager on different storages. The registry vends what a ref-aware setup needs (codec(Type.class),manager(...),ref(key, Type.class)) so you can't half-wire it. A bareRef.of(key, type)is unbound and fails fast on resolve — never resolves against "some default". See Caching & References.
This is the same instinct as Idiom 1: make the failure mode impossible by construction rather than something you document and hope nobody hits.
A few load-bearing decisions that surprise people reading the source:
-
Everything is async; there are no blocking variants. Every I/O method returns
CompletableFuture; callers use.join()or compose. This isn't an oversight — a sync overload would double the surface and invite blocking on the wrong thread. See The Async API. -
Keys persist by
toString()(≤ 255 chars), match byequals/hashCode. The 255 cap is the safe intersection across all backends; an oversized key makessavecomplete exceptionally rather than silently truncate into a collision. See Entities, Keys & Collections. -
Collection names are validated against
^[a-zA-Z][a-zA-Z0-9_]*$— the intersection of identifier rules across every backend, so no quoting is ever needed. -
The cache cell/stamp/tombstone model (manager module) makes concurrent writes and reloads safe: writes
swap the value in place under a monotonic stamp so a slow reload can't regress a newer write nor resurrect
a newer delete. The internals are package-private on purpose — configure via
CacheOptions/CachePolicy, don't reach in. Detailed inSPEC_refs_and_managers.md; surfaced in Caching Managers. -
The dual-target build. Production code is authored in Java 17 syntax but compiled to Java 8 bytecode
via Jabel (
--release 8), while the toolchain that runs the tests is JDK 25. So production code must stay Java-8-runtime-safe (no Java 9+ APIs) even though it reads like modern Java — e.g.StorageExecutorsreflectsnewVirtualThreadPerTaskExecutorinstead of calling it directly. See Building from Source. -
Logging is silent by default with an ERROR floor. Routine ops emit nothing under the factory default;
failures always emit, and no configuration can switch that floor off. Privacy is the default: events carry
counts/durations/collection names, never entity content. Rationale in
SPEC_storage_logging.md; usage in Logging & Diagnostics.
When you're unsure which document to trust, this is the order (from SPEC_wiki.md):
README sells and starts · the wiki teaches · the specs justify · the Javadoc specifies.
A fact lives in exactly one canonical place; everywhere else links to it. Version numbers live in the
version catalog and are surfaced on a single page — Dependency Versions & Overrides — and never restated
elsewhere. Wiki code examples mirror tested entity shapes (TestPlayer, PlayerProfile, …) so they can't
silently rot.
-
Architecture Overview — the
Storage/Repository/EntityDescriptor/Codec/Storagessurface and the capability idiom in context. -
The Async API — the
CompletableFuturemodel that pervades the whole library. -
Distribution Flavors — the flavor/relocation/GPL decisions
SPEC_distribution_modules.mdjustifies. -
Logging & Diagnostics — the logging subsystem
SPEC_storage_logging.mdjustifies. -
Caching & References — the
everydatabase-manageradd-on and its cell/stamp model. - Extending the Library — the seams that are meant to be extended.
-
Editing this Wiki — the conventions (
SPEC_wiki.md) every page on this site follows.
EveryDatabase · Home · made by Petrus Pradella
Getting Started
Core Concepts
Working with Data
Backends
Manager Module
- Caching & References
- Typed References (Ref)
- Caching Managers
- Cache Policies & Freshness
- Cross-Process Cache Sync
- One Entity, Many Databases
Operations
Advanced
Reference
Contributing