Standalone Quickstart
This guide is for services that wire DSM directly in Java code without Spring Boot auto-configuration.
Use this path when your service already owns:
- its own bootstrap sequence
- membership implementation selection
- runtime lifecycle control
- collection registration in application code
Integration Checklist
Before writing code, decide these four inputs:
- what
clusterIdandserviceIdwill be in production - which membership implementation the service will use
- which collections will be registered during bootstrap
- whether registration is fixed at startup or dynamic later
1. Add The Core Module
<dependency>
<groupId>com.leanowtech.dsm</groupId>
<artifactId>dsm-core</artifactId>
<version>${dsm.version}</version>
</dependency>2. Create The Membership Component
For local development or tests, StandaloneClusterMembership is the shortest path.
NodeInfo self = new NodeInfo("node-a", "127.0.0.1", 9090);
ClusterMembership membership = new StandaloneClusterMembership(self);In production you will usually use multicast or unicast gossip membership from dsm-cluster, but the runtime API stays the same.
3. Build The Runtime
DsmRuntime runtime = DsmRuntimeBuilder.builder()
.clusterId("runtime-example")
.serviceId("gateway-service")
.membership(membership)
.allowDynamicRegistration(false)
.build();DsmRuntimeBuilder also allows advanced customization such as a custom collection registry, custom collection store, and custom clock, but most integrations should start with defaults.
4. Register Collections During Bootstrap
Register collection handles before start() when the service has a fixed collection set.
Register Collection
DsmRegister<RouteHint> routeHints = runtime.register(
CollectionSpecBuilder.<RouteHint>register(
"shared",
"gateway",
"route-hints")
.schemaId("route-hints/v1")
.codec(new RouteHintCodec())
.build());Lease Collection
DsmLeaseRegister<ShardOwner> shardOwners = runtime.leaseRegister(
LeaseCollectionSpecBuilder.<ShardOwner>lease(
"shared",
"worker",
"shard-owner")
.schemaId("shard-owner/v1")
.codec(new ShardOwnerCodec())
.entityFactory(ShardOwner::blank)
.build());CRDT Collection
DsmCrdtCollection<PnCounterUpdate, PnCounterState> counters = runtime.crdt(
CrdtCollectionSpecBuilder.<PnCounterUpdate, PnCounterState>crdt(
"shared",
"billing",
"request-counter")
.schemaId("request-counter/v1")
.codec(new PnCounterUpdateCodec())
.stateCodec(new PnCounterStateCodec())
.initialState(PnCounterState.empty())
.build(),
new PnCounterStateMerger());5. Start The Runtime
runtime.start();The runtime is not serving local work until lifecycle services are started.
6. Expose The Handles Through Your Service Boundary
Most services should not let every layer call runtime.register(...) directly. Prefer a thin integration layer that owns DSM handles and exposes domain operations.
public final class RouteHintRepository {
private final DsmRegister<RouteHint> routeHints;
public RouteHintRepository(DsmRegister<RouteHint> routeHints) {
this.routeHints = routeHints;
}
public void upsert(String key, String address) {
routeHints.put(new RouteHint(key, null, address));
}
public Optional<RouteHint> find(String key) {
return routeHints.get(key);
}
}This keeps collection locators and DSM-specific semantics out of unrelated application layers.
7. Use Diagnostics And Lifecycle State
These APIs are the first place to look when a node appears live but DSM behavior is wrong.
RuntimeDiagnostics diagnostics = runtime.diagnostics();
System.out.println(runtime.state());
System.out.println(runtime.isReady());
System.out.println(runtime.info());
System.out.println(runtime.clusterView());
System.out.println(diagnostics.totalEntries());Recommended Registration Patterns
Fixed Registration
Use bootstrap registration when every node should expose the same collection set from startup. This is the safest default.
Dynamic Registration
Set allowDynamicRegistration(true) only if your service actually needs to add collections after runtime construction. Dynamic registration changes operational expectations and should not be enabled casually.
Tenant/Application Scoping
If you have multiple tenants or application domains, use runtime.tenant(...).application(...) to organize registration code. That keeps locator assembly consistent.
ApplicationRuntime gateway = runtime.tenant("shared").application("gateway");
DsmRegister<RouteHint> routeHints = gateway.register(
CollectionSpecBuilder.<RouteHint>register(
"shared",
"gateway",
"route-hints")
.schemaId("route-hints/v1")
.codec(new RouteHintCodec())
.build());Common Standalone Mistakes
Starting The Service Before Calling runtime.start()
Collection handles may exist, but lifecycle-dependent behavior is not fully initialized until the runtime starts.
Hiding Membership Choice Too Late
Membership affects how the runtime participates in the cluster. Decide the membership implementation as part of service bootstrap design, not as a late operational detail.
Mixing Domain Identity With Locator Identity
Your entity keys are domain keys. The collection locator is infrastructure identity. Both must be designed explicitly.