Chapter 3 — Stable Identity for Shared State
Chapter objective: After completing this chapter, a developer can define a
RouteHintthat nodes can identify, encode, and evolve consistently.
Learning objectives
- Distinguish collection locator from entry key.
- Explain how
DsmEntity,EntityMetadata, and a codec cooperate. - Use
schemaIdto protect the wire-format compatibility boundary. - Detect identity drift and schema mismatch.
Prerequisites
- Ability to start the single-node Runtime from Chapter 2.
- Familiarity with Java records and serialization.
Case progress
Chapter 2 stored an address. This chapter gives it a stable identity:
the collection is demo/gateway/route-hints, the entry key
is the service name, and the entity value carries the address and health
hint.
Five identity layers
public record RouteHint(
String entryKey,
EntityMetadata metadata,
String address)
implements DsmEntity<RouteHint> {
@Override
public RouteHint withMetadata(EntityMetadata next) {
return new RouteHint(entryKey, next, address);
}
}entryKey is the identity inside a collection and remains
stable as content changes. metadata contains lineage, HLC,
epoch, fencing token, and optional expiry. Business code leaves these
protocol fields to the Runtime instead of forging them to win a
conflict.
A collection locator consists of
tenantId/applicationId/collectionId. Identically named keys
under different locators are isolated entries.
clusterId/serviceId defines the runtime communication
domain and does not replace the locator. Chapter 17 tests both
layers.
Codec and schema form a protocol boundary
Both nodes need to interpret the same bytes as the same entity.
recordCodec(RouteHint.class) supplies record encoding.
schemaId("route-hints/v1") records the wire-format intent
in the collection contract. If one node treats address as a
string and another changes it to an incompatible structure, rejection is
safer than guessed conversion.
A safe evolution commonly deploys readers for old and new formats, switches writers, and then removes the old reader. Renaming a Java class does not complete a distributed schema migration.
Counterexample and fault injection
Create two nodes with the same locator and schemaId but
incompatible codec meanings. Registration may succeed while remote
decoding later fails. A rejected schema fingerprint protects visible
state from incompatible data.
Another error uses a random UUID as the entryKey for
every health publication. Each update then creates a new entry, so the
Register never replaces the same logical service.
Experiment
Validate the identity card and run the fixed schema-mismatch integration test:
node --test tests/chapter-assets.test.mjs
cd submodule/dsm
mvn -q -pl dsm-integration-test -am \
-Dtest=TwoNodeIntegrationTest#schemaFingerPrintMismatchIsRejectedGracefullyBetweenTwoNodes \
-Dsurefire.failIfNoSpecifiedTests=false testThe first check covers the book asset. The integration test proves explicit rejection between two incompatible nodes.
Experiment acceptance card
| Field | Content |
|---|---|
| Command | Asset test plus the focused Maven command above |
| Input or fault | Schema fingerprint mismatch on the same route collection |
| Observable result | Asset passes; incompatible replication is rejected |
| Evidence level | E3: controlled two-node integration test |
| Not proven | Automatic schema migration, rolling cross-version upgrade, or production data repair |
Review
- Locator identifies a collection; entry key identifies a logical entry.
- Business fields and Runtime metadata have different owners.
- Codec defines byte meaning; schema ID/fingerprint protects compatibility.
- Identity drift creates new state, while schema drift fails closed.
Next
Chapter 4 connects a second node and observes the same
RouteHint moving from local commit to remote
visibility.