ARC 1See Shared StateChapters 1–4

Chapter 3 — Stable Identity for Shared State

Chapter objective: After completing this chapter, a developer can define a RouteHint that nodes can identify, encode, and evolve consistently.

Learning objectives

  1. Distinguish collection locator from entry key.
  2. Explain how DsmEntity, EntityMetadata, and a codec cooperate.
  3. Use schemaId to protect the wire-format compatibility boundary.
  4. Detect identity drift and schema mismatch.

Prerequisites

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

Stable identity of shared state

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 test

The 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

Next

Chapter 4 connects a second node and observes the same RouteHint moving from local commit to remote visibility.