cosid-manual-integration · git:20261029.b6d8370 · 2026-10-29 · sha256 c5001a08281b4ba4

cosid-manual-integration git:20261029.b6d8370A

Immutable. This exact content never changes and is served at /api/v1/blob/c5001a08281b4ba4.

---
name: cosid-manual-integration
description: Integrate CosId 5 programmatically into Java or Kotlin applications without Spring Boot. Use for building a RuntimeSpec and starting a CosIdRuntime, StoreSpec / MachineSpec / GeneratorSpec / CodecSpec records, RuntimeOptions (clients, listeners, executor, time source), direct TimeBasedGenerator or SegmentGenerator construction in tests, Layout and IdCodec use, IdFiller, lifecycle and close order, custom StoreProvider or JdbcDialect, and tests with cosid-store-tck's InMemoryStore. Do not use for cosid-spring-boot-starter YAML.
---

# Integrate CosId Programmatically

Provide code that compiles against the user's CosId version. In CosId 5 **`CosIdRuntime` is the one assembly path**:
Spring, cosid-server and hand-written code all call `CosIdRuntime.start(spec, options)`. Build a spec; do not wire
generators, leases and stores by hand in production code.

## Workflow

1. Confirm the target version (5.x needs Java 21) and the chosen strategy. Use `$cosid-strategy-guide` when the strategy
   is undecided.
2. Identify the uniqueness authority: a leased slot from a store, a static slot the deployment guarantees, or a
   segment store.
3. Build the smallest `RuntimeSpec` that satisfies the requirement.
4. Own the runtime for its full life (`try`-with-resources, or close it on shutdown).
5. Add one focused check: uniqueness, monotonicity, decoding, restart, or clock regression.

In a CosId checkout, verify signatures in `core/src/main/java/me/ahoo/cosid/runtime/` (builders; codec specs are records) and use
`core/src/test/java/me/ahoo/cosid/runtime/CosIdRuntimeTest.java` as the example.

## A Runtime

```java
RuntimeSpec spec = RuntimeSpec.builder("shop")
    .store("db", StoreSpec.builder("jdbc").timeout(Duration.ofSeconds(2)).option("initSchema", "true").build())
    .machine(MachineSpec.Leased.builder("db")                 // also preferredSlot(int), ownerKey(String)
        .bits(10).ttl(Duration.ofSeconds(30)).tolerance(Duration.ofSeconds(1)).build())
    .generator("default", GeneratorSpec.Snowflake.builder()
        .layout(Layout.SNOWFLAKE_MS).regression(ClockRegressionPolicy.DEFAULT)
        .codec(List.of(new CodecSpec.Radix(62, 11, true))).build())
    .generator("order", GeneratorSpec.Segment.builder("db")
        .batch(BatchPolicy.DEFAULT).codec(List.of(new CodecSpec.Prefix("O"))).build())
    .build();

RuntimeOptions options = RuntimeOptions.builder()
    .client("db", dataSource)                                 // existing clients by store name
    .build();

try (CosIdRuntime runtime = CosIdRuntime.start(spec, options)) {
    long id = runtime.generator("default").generate();
    String order = runtime.stringGenerator("order").generate();
}
```

- Store types: `jdbc` (`DataSource`, or `options.url`), `redis` (Lettuce client or `options.uri`), `mongo`
  (`MongoClient` or `options.uri`), `zookeeper` (`CuratorFramework` or `options.connectString`), `remote`
  (`options.baseUrl`, `token`). Providers come from `ServiceLoader`; `RuntimeOptions.storeProviders` adds more.
- A store given a client never closes it; one it built itself, it closes.
- `MachineSpec.Static.builder(slot).bits(10).stateFile(path).build()` for a slot the deployment keeps unique; without
  a state file there is no protection against a clock set back across restarts.
- A named machine (D37): `.machine("global", MachineSpec.Leased.builder("db").namespace("global-ids").slotOffset(0).build())`
  on the `RuntimeSpec.Builder`, and `.machine("global")` on a `Snowflake` or `CosId80` builder.
- `GeneratorSpec.CosId80.builder().codec(new PartsCodecSpec.Radix(62)).build()`,
  `GeneratorSpec.PeriodicSegment.builder("db", Period.YEAR_MONTH_DAY, zone)
  .codec(new GroupedCodecSpec.DatePrefix("-", List.of(new CodecSpec.Radix(10, 6, true)))).build()`.
- `GeneratorSpec.UuidV7.builder().build()` (D46): no machine nor store; `runtime.text(name)` and
  `runtime.get(name, UuidV7Generator.class)`.
- `GeneratorSpec.Custom.builder(reference)` / `GeneratorSpec.Instance.builder(ids)`: a `long` generator of your own.
- `GeneratorSpec.Disabled(spec)` declares a generator without creating it.
- A segment's `BatchPolicy.builder().step(..).initialOffset(..).lookahead(..).maxBatch(..).reserveTimeout(..)`;
  `BatchPolicy.DEFAULT` starts a new key at 1, as 4.x did.

## Reading Ids

- `runtime.generator(name)` returns an `IdGenerator` (`snowflake`, `segment`); `runtime.stringGenerator(name)` returns
  ids and codec.
- `runtime.get(name, TimeBasedGenerator.class)` gives `layout()`, `slot()`, `nextParts()`;
  `runtime.get(name, PartsCodec.class)` for `cosid80`; `runtime.periodic(name)` and `runtime.get(name, GroupedCodec.class)`
  for periodic segments; `runtime.get(name, IntIdGenerator.class)` is range checked.
- `layout.unpack(id)` gives `IdParts(time, node, seq)`; `parts.instant(layout)` the time. Every codec has `decode`.
- `runtime.filler().fill(entity)` fills `@CosId` fields.
- `IdGenerators.toInt`, `.uncertain`, `.local()`, `.uuid()`, `.lazy(...)` replace the small 4.x generator classes.

## Lifecycle

- Start validates everything first and reports all problems in one `CosIdException`. It then opens the used stores,
  acquires the slot, and creates the generators; a failure closes what was opened.
- `close()`: generators stop and report their exact last timestamp, the slot is released with it, then the stores and the
  runtime's own executor close. Always close: a released slot is reusable at once, an abandoned one after `ttl` +
  `tolerance`.
- A JVM may run several runtimes; each has its own incarnation and slot. There is no global state.

## Tests

- `cosid-store-tck`: `InMemoryStore` (passes every contract) for tests without a database, `ManualClock`, fault
  injection, and the `RangeStoreContract` / `SlotLeaseStoreContract` base classes for a custom store.
- A custom `JdbcDialect` is registered through `META-INF/services/me.ahoo.cosid.store.jdbc.JdbcDialect` and must pass the
  contracts on its database.

## Boundaries and Checks

- Do not construct `LeaseKeeper` or `TimeBasedGenerator` directly in application code; it bypasses the start-up checks
  (node bits vs slot bits, store timeout vs reserve timeout).
- Uniqueness of time-based ids rests on the store's window invariant (D22), never on clocks; there is no way to keep
  issuing after a lost lease.
- 4.x classes (`SnowflakeId`, `SegmentChainId`, `MachineIdDistributor`, `IdGeneratorProvider`, `IdConverter`) do not exist
  in 5.x.

## Response Contract

Return:

1. the dependencies (`cosid-core` + the store artifacts);
2. the `RuntimeSpec` and `RuntimeOptions`;
3. the lifecycle owner and close point;
4. one focused test.