feat(entity,pekko): carry the journal coordinate into view processors #46

Merged
mph merged 2 commits from feature/projection-event-coordinate-main into main 2026-08-28 16:54:08 +00:00
Owner

Forward-ports the journal-coordinate change from release/0.1.x (released as 0.1.23) onto the 0.2.x line.

Why

A projected event arrives in an EventEnvelope that names its journal position — persistence id and sequence number. ProjectionHandler discarded that position and passed only the payload to the ViewProcessor, so a view had no stable identity to key its read-model records on. Re-running a projection without clearing the read model first re-inserted every record instead of overwriting it.

The alternative considered was hashing the event JSON. The coordinate is better on every axis: it needs no canonical serialization, it survives event-schema changes, and it tells two byte-identical events from one command apart — which no payload hash can do.

What

  • EventCoordinate(persistenceId, sequenceNr) — the journal position, in entity.
  • CoordinatedViewProcessor[Event] — receives the event together with its coordinate.
  • CoordinatedProjectionHandler — reads the coordinate off the envelope; coordinateOf is a public seam so a view's identity can be tested without a projection running.
  • PekkoProjectionSupport.runSingleCoordinated — the coordinate-carrying registration.

Additive only. ViewProcessor, ProjectionHandler and runSingle keep their signatures and behaviour; ProjectionHandler now delegates through CoordinatedViewProcessor.lift, so one implementation serves both paths.

Tests

New test modules for entity (JVM + ScalaJS, shared sources) and pekko-persistence. 12 tests: coordinate identity, replay stability, discrimination by sequence number and by persistence id, payload-independence, and the handler passing the transformed event plus its coordinate onward.

Every test was mutation-checked. Replacing the coordinate in process() with EventCoordinate(envelope.persistenceId, 0L) fails exactly one test — the composition test — proving it covers something the pure tests do not.

Full suite green: 652 tests, 0 failures. Clean compile of all modules, 0 warnings.

Differences from the released branch

  • The version stays at 0.2.2-SNAPSHOT; the 0.1.23 bump is not ported.
  • CoordinatedProjectionHandlerSpec drops a scoping block and passes the Runtime explicitly. The project's scalafmt config rejects the block form, but __.checkFormat does not resolve on test submodules, so neither the local gate nor CI saw it — release/0.1.x still carries the unformatted version.
Forward-ports the journal-coordinate change from `release/0.1.x` (released as `0.1.23`) onto the 0.2.x line. ## Why A projected event arrives in an `EventEnvelope` that names its journal position — persistence id and sequence number. `ProjectionHandler` discarded that position and passed only the payload to the `ViewProcessor`, so a view had no stable identity to key its read-model records on. Re-running a projection without clearing the read model first re-inserted every record instead of overwriting it. The alternative considered was hashing the event JSON. The coordinate is better on every axis: it needs no canonical serialization, it survives event-schema changes, and it tells two byte-identical events from one command apart — which no payload hash can do. ## What - `EventCoordinate(persistenceId, sequenceNr)` — the journal position, in `entity`. - `CoordinatedViewProcessor[Event]` — receives the event together with its coordinate. - `CoordinatedProjectionHandler` — reads the coordinate off the envelope; `coordinateOf` is a public seam so a view's identity can be tested without a projection running. - `PekkoProjectionSupport.runSingleCoordinated` — the coordinate-carrying registration. Additive only. `ViewProcessor`, `ProjectionHandler` and `runSingle` keep their signatures and behaviour; `ProjectionHandler` now delegates through `CoordinatedViewProcessor.lift`, so one implementation serves both paths. ## Tests New test modules for `entity` (JVM + ScalaJS, shared sources) and `pekko-persistence`. 12 tests: coordinate identity, replay stability, discrimination by sequence number and by persistence id, payload-independence, and the handler passing the transformed event plus its coordinate onward. Every test was mutation-checked. Replacing the coordinate in `process()` with `EventCoordinate(envelope.persistenceId, 0L)` fails exactly one test — the composition test — proving it covers something the pure tests do not. Full suite green: 652 tests, 0 failures. Clean compile of all modules, 0 warnings. ## Differences from the released branch - The version stays at `0.2.2-SNAPSHOT`; the `0.1.23` bump is not ported. - `CoordinatedProjectionHandlerSpec` drops a scoping block and passes the `Runtime` explicitly. The project's scalafmt config rejects the block form, but `__.checkFormat` does not resolve on test submodules, so neither the local gate nor CI saw it — `release/0.1.x` still carries the unformatted version.
CI ran only on pull requests targeting main, so a fix landing on a
maintenance branch got no compile, format, lint or test feedback.

Forward-ported from release/0.1.x (3ffbb636).
feat(entity,pekko): carry the journal coordinate into view processors
All checks were successful
CI / Check Formatting (pull_request) Successful in 52s
CI / Compile (pull_request) Successful in 2m11s
CI / Check Linting (pull_request) Successful in 2m11s
CI / Run Tests (pull_request) Successful in 2m14s
77150ea9e7
A projected event arrives in an EventEnvelope that names its journal
position — persistence id and sequence number. The projection handler
discarded that position and passed only the payload onward, so a view
had nothing stable to key its records on: a replay re-inserted every
record instead of overwriting it.

EventCoordinate names that position. CoordinatedViewProcessor receives
it alongside the event, and CoordinatedProjectionHandler reads it off
the envelope. ViewProcessor and ProjectionHandler keep working
unchanged — ProjectionHandler now delegates through
CoordinatedViewProcessor.lift, so one implementation serves both.

The entity and pekko-persistence modules gain test modules; entity
tests run on both JVM and ScalaJS from shared sources.

Forward-ported from release/0.1.x, released as 0.1.23 (a8b99ac6,
72ba96e4). The pekko spec drops a scoping block the project's
scalafmt config rejects — checkFormat does not resolve on test
submodules, so the release branch carries the unformatted version.
mph merged commit 801dc43562 into main 2026-08-28 16:54:08 +00:00
mph deleted branch feature/projection-event-coordinate-main 2026-08-28 16:54:08 +00:00
Sign in to join this conversation.
No description provided.