ID Generator
Wow uses CosId through two ID paths: global IDs for runtime envelopes and aggregate IDs selected per named aggregate. They share infrastructure but have different selection and cache boundaries.
Two paths
| Path | API | Main use | Selection key |
|---|---|---|---|
| Global ID | generateGlobalId() / GlobalIdGenerator | Message IDs for commands, domain events, event streams, waits, and compensation records | System property wow.cosid, defaulting to CosId's cosid name |
| Aggregate ID | NamedAggregate.generateId() / AggregateIdGeneratorRegistrar | Create an aggregate ID when the command does not supply one | Aggregate metadata id, otherwise aggregate name |
Do not use message IDs and business aggregate IDs interchangeably. A domain with an existing natural ID can supply it explicitly; Wow does not require every aggregate to use one generated identifier format.
Global generator
GlobalIdGenerator is a lazy singleton. On first access it loads GlobalIdGeneratorFactory implementations through Java ServiceLoader, sorts them with Wow @Order, and selects the first factory returning a non-null CosIdGenerator. If none can create one, access fails with NotInitializedGlobalIdGeneratorError.
The built-in CosIdGlobalIdGeneratorFactory looks up its configured name in IdGeneratorProvider. A custom factory implements the interface and registers through:
META-INF/services/me.ahoo.wow.id.GlobalIdGeneratorFactoryDo not create a new global generator per request; selection happens once at lazy initialization.
Aggregate generator
AggregateIdGeneratorRegistrar caches a generator by materialized NamedAggregate. On first use it calls ordered AggregateIdGeneratorFactory instances and selects the first non-null result.
The built-in CosIdAggregateIdGeneratorFactory selects in this order:
- read the aggregate's generator
idfromMETA-INF/wow-metadata.json; - use aggregate name when metadata has no
id; - return the same-named generator from
IdGeneratorProviderwhen present; - otherwise create
Radix62CosIdGeneratorwith the global generator's machine ID and wrap it inClockSyncCosIdGenerator.
That fallback depends on an available global generator and its machine ID. It does not allocate machine IDs for a deployment.
Deployment responsibility
The selected CosId configuration owns algorithm details, clock handling, and machine-ID allocation. Wow only selects and invokes a generator. A multi-instance deployment must prove:
- every instance receives a machine ID valid for the selected CosId generator;
- restart, scale-out, and lease reclamation do not reuse an active instance's machine ID;
- clock rollback, provider unavailability, and missing configuration have acceptable startup/generation behavior;
- ID length and character set fit downstream database and API contracts.
A unit test showing non-blank or increasing IDs in one JVM does not prove cross-node uniqueness, global ordering, or production capacity. Use Configuration and metadata for the selected CosId version for production setup.
Custom selection
Add an AggregateIdGeneratorFactory only when aggregates genuinely need different formats or providers. A factory may return null for aggregates it does not own, allowing later factories to participate. Do not reproduce metadata's id mapping as a global branch table.
@Order(100)
class InvoiceIdGeneratorFactory : AggregateIdGeneratorFactory {
override fun create(namedAggregate: NamedAggregate): IdGenerator? =
if (namedAggregate.aggregateName == "invoice") invoiceGenerator else null
}Register it through:
META-INF/services/me.ahoo.wow.id.AggregateIdGeneratorFactoryFactories and the registrar can be accessed concurrently; a custom generator must satisfy the concurrency contract its callers need.
Verification
./gradlew :wow-core:test --tests "me.ahoo.wow.id.GlobalIdGeneratorTest"
./gradlew :wow-core:test --tests "me.ahoo.wow.id.AggregateIdGeneratorRegistrarTest"
./gradlew :wow-core:test --tests "me.ahoo.wow.id.CosIdAggregateIdGeneratorFactoryTest"The application must also test multi-instance collisions and restarts with a production-like machine-ID allocator. Repository unit tests do not provide that evidence.
Source and related pages
GlobalIdGeneratorAggregateIdGeneratorRegistrarCosIdAggregateIdGeneratorFactory- Compiler: source of aggregate
idmetadata - Core Concepts: complete AggregateId boundary