Skip to content

feat: add Event-Carried State Transfer pattern (#2434) - #3602

Open
ylcn91 wants to merge 2 commits into
iluwatar:masterfrom
ylcn91:feat/event-carried-state-transfer
Open

feat: add Event-Carried State Transfer pattern (#2434)#3602
ylcn91 wants to merge 2 commits into
iluwatar:masterfrom
ylcn91:feat/event-carried-state-transfer

Conversation

@ylcn91

@ylcn91 ylcn91 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds the Event-Carried State Transfer (ECST) pattern as a new event-carried-state-transfer module.

  • Problem: with plain event notifications a consumer learns only that something changed and has to call the producer back for the details, which couples the two services and makes the consumer unavailable whenever the producer is.
  • Solution: every CustomerUpdatedEvent carries the full customer state and a version. The consumer keeps its own replica, serves its use cases from that replica alone, never calls the producer back, and keeps working when the producer is offline.
  • Key components:
    • CustomerState (versioned, immutable record) and CustomerUpdatedEvent (carries the complete state).
    • EventBus / EventListener: tiny synchronous in-memory publish/subscribe keyed by event type.
    • CustomerService (producer): owns the authoritative state, publishes a full-state event on every change, can be taken offline to demonstrate consumer autonomy.
    • CustomerReplica (consumer-side store): version-guarded upsert that ignores stale or duplicate events.
    • OrderService (consumer): places orders using only the replica (unknown customer and credit limit checks, shipping address snapshot); it has no reference to CustomerService.
    • App: four logged steps: replica follows each change, order placed while the producer is offline, stale event ignored, business rule enforced from the replica.
    • README.md: intent, real-world example, Fowler quote, sequence diagram, code walkthrough with real program output, applicability, trade-offs (eventual consistency, duplicated data, versioning), and an explicit paragraph on how ECST differs from event notification, event sourcing and pub-sub. PlantUML class diagram under etc/.
  • Tests: 27 JUnit 5 tests (bus, state, producer, replica, consumer, App); the only uncovered line is the implicit App constructor.
  • Module registered in the parent pom.xml. ./mvnw clean verify -pl event-carried-state-transfer passes locally on JDK 21 and inside an eclipse-temurin:21 container; the packaged jar runs end to end.

Fixes #2434

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

PR Summary

Introduces the Event-Carried State Transfer (ECST) pattern as a new module event-carried-state-transfer. The pattern propagates the full state of a changed entity via events so consumers maintain their own replicas and can operate when producers are offline. The module includes a producer (CustomerService), a consumer replica (CustomerReplica), a small in-memory EventBus, and a consumer (OrderService). It comes with 27 JUnit 5 tests and documentation with diagrams showing behavior, trade-offs and comparisons to related patterns. The parent pom.xml is updated to register the module. Build and run via Maven: ./mvnw clean verify -pl event-carried-state-transfer.

Changes

File Summary
event-carried-state-transfer/README.md Adds a comprehensive README describing the ECST pattern: motivation, real-world example, sequence flows, PlantUML diagrams, runnable Java demo, trade-offs, and how ECST differs from event notifications, event sourcing and pub-sub.
event-carried-state-transfer/etc/event-carried-state-transfer.urm.png PNG diagram asset illustrating the ECST class relationships and components used in the demo.
event-carried-state-transfer/etc/event-carried-state-transfer.urm.puml PlantUML source for the ECST class diagram and relationships used in the documentation.
event-carried-state-transfer/pom.xml Module descriptor for the ECST pattern: declares dependencies, build config and main class for runnable demo.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java Implements the end-to-end ECST demonstration: sets up EventBus, CustomerService, and OrderService, performs a sequence of state changes, offline producer scenario, stale event handling, and final credit-limit update propagation.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java In-memory replica that updates on CustomerUpdatedEvents; guards with version checks to ignore stale/duplicate events; exposes find and size.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java Authoritative customer data store. On changes, stores new CustomerState, publishes CustomerUpdatedEvent carrying the full state; supports online/offline behavior.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerState.java Immutable CustomerState with fields: customerId, name, shippingAddress, creditLimit, version and helper methods to create updated copies. Validates inputs.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerUpdatedEvent.java Event carrying the complete CustomerState along with eventId and occurredAt.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java Synchronous pub/sub channel with exact-class dispatch; supports subscribe and publish, including a null-check.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventListener.java Functional interface EventListener<E> with onEvent(E event).
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/Order.java Value object representing an accepted order: orderId, customerId, shippingAddress, and amount.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderRejectedException.java Runtime exception thrown when an order cannot be accepted based on replicated data in the ECST workflow.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java Reads orders from the replicated state; subscribes to CustomerUpdatedEvents; enforces credit limit using the replica.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/AppTest.java JUnit tests ensuring App instantiation and that demo hooks run without exceptions; includes checks around direct lookup while online.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplicaTest.java Tests for CustomerReplica behavior: applying initial and newer events, ignoring stale/duplicate, handling unknown customers.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerServiceTest.java Tests verifying CustomerService publishes full initial state and subsequent full-state updates; offline/online behavior and error scenarios.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerStateTest.java Tests validating CustomerState invariants and version bump semantics when updating address or credit limit.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/EventBusTest.java Tests for EventBus delivering by type, ordering, and null handling; ensures exact-class dispatch semantics.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/OrderServiceTest.java Tests end-to-end ECST using the replica: placing orders, address updates, offline mode, and credit-limit propagation.
pom.xml Root POM updated to register the new event-carried-state-transfer module.

autogenerated by presubmit.ai

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.87%. Comparing base (41625d8) to head (1cb094e).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3602      +/-   ##
============================================
+ Coverage     83.79%   83.87%   +0.08%     
- Complexity     4277     4320      +43     
============================================
  Files          1121     1130       +9     
  Lines         15144    15310     +166     
  Branches        723      729       +6     
============================================
+ Hits          12690    12842     +152     
- Misses         2159     2167       +8     
- Partials        295      301       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ylcn91
ylcn91 force-pushed the feat/event-carried-state-transfer branch from cabcc02 to c92197c Compare September 3, 2026 11:32
@ylcn91

ylcn91 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Coverage follow-up: added AppTest.shouldBeInstantiable for the implicit App constructor, the last uncovered line. JaCoCo now reports 100% instruction, branch and line coverage. Verified locally on JDK 21 and in an eclipse-temurin:21 container.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Pull request needs attention.

Review Summary

Commits Considered (1)
  • c92197c: feat: add Event-Carried State Transfer pattern (#2434)
Files Processed (20)
  • event-carried-state-transfer/README.md (1 hunk)
  • event-carried-state-transfer/etc/event-carried-state-transfer.urm.puml (1 hunk)
  • event-carried-state-transfer/pom.xml (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerState.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerUpdatedEvent.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventListener.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/Order.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderRejectedException.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/AppTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplicaTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerServiceTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerStateTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/EventBusTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/OrderServiceTest.java (1 hunk)
  • pom.xml (1 hunk)
Actionable Comments (5)
  • event-carried-state-transfer/pom.xml [1-70]

    best_practice: "Add JUnit Jupiter API dependency"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java [60-60]

    best_practice: "Logger name mismatch with Lombok"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java [1-145]

    best_practice: "Logger name mismatch with Lombok"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java [49-51]

    best_practice: "Logger name mismatch with Lombok"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java [56-56]

    best_practice: "Logger name mismatch with Lombok"

Skipped Comments (1)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/EventBusTest.java [43-43]

    readability: "Test listener compatibility with EventListener"

Comment thread event-carried-state-transfer/pom.xml

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Pull request needs attention.

Review Summary

Commits Considered (3)
  • 00c006f: docs: embed the rendered class diagram in the event-carried state transfer README

Render etc/event-carried-state-transfer.urm.puml to PNG and embed it in the detailed explanation section, matching the other modules, instead of the inline mermaid block. The producer and consumer snippets now show the plain long sequence counters the code uses.

  • 5bd0fd4: feat: demonstrate a credit limit change flowing through the replica

The demo never exercised changeCreditLimit, so the claim that the replica is
enough to enforce business rules was only shown as a rejection. Add a fifth
step: the customer service comes back online, raises the credit limit, and the
order rejected in step 4 is accepted from the replicated state alone.

Add restart() to CustomerService so the outage can end, make CustomerReplica
use a LinkedHashMap like CustomerService for deterministic log order, cover the
new behaviour in CustomerServiceTest and OrderServiceTest, and refresh the
README demo section, its program output, and the PlantUML diagram.

  • 14c9e5d: fix: align event bus contract and class diagram with the code

  • document that EventBus dispatches by exact runtime class, so a listener
    subscribed to a supertype never receives subclass events

  • reject null events in EventBus.publish with Objects.requireNonNull and cover
    it with a test

  • replace the AtomicLong sequence counters in CustomerService and OrderService
    with plain long fields and note that these classes are not thread-safe

  • drop the implements-relationship between CustomerReplica and EventListener in
    the PlantUML diagram; OrderService merely adapts the replica

  • replace the raw .puml link in the README with an inline mermaid class diagram

Files Processed (11)
  • event-carried-state-transfer/README.md (1 hunk)
  • event-carried-state-transfer/etc/event-carried-state-transfer.urm.png (0 hunks)
  • event-carried-state-transfer/etc/event-carried-state-transfer.urm.puml (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerServiceTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/EventBusTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/OrderServiceTest.java (1 hunk)
Actionable Comments (5)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java [62-62]

    bug: "Logging: inconsistent logger name in App."

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java [59-59]

    bug: "Logging: inconsistent logger name in EventBus."

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java [73-73]

    bug: "Logging: inconsistent logger name in CustomerService."

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java [56-61]

    bug: "Logging: inconsistent logger name in CustomerReplica."

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java [79-86]

    bug: "Logging: inconsistent logger name in OrderService."

Skipped Comments (0)

@ylcn91
ylcn91 force-pushed the feat/event-carried-state-transfer branch from 00c006f to ab69096 Compare September 7, 2026 08:04
publish rejects null, exact-class dispatch is documented, the sequence counters are plain longs, and the demo shows a credit limit change flowing through the replica. The class diagram is a rendered PNG.
@ylcn91
ylcn91 force-pushed the feat/event-carried-state-transfer branch from ab69096 to 1cb094e Compare September 7, 2026 09:09

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Pull request needs attention.

Review Summary

Commits Considered (2)
  • 1cb094e: fix: align the event bus contract with the code and extend the demo

publish rejects null, exact-class dispatch is documented, the sequence counters are plain longs, and the demo shows a credit limit change flowing through the replica. The class diagram is a rendered PNG.

  • c92197c: feat: add Event-Carried State Transfer pattern (#2434)
Files Processed (21)
  • event-carried-state-transfer/README.md (1 hunk)
  • event-carried-state-transfer/etc/event-carried-state-transfer.urm.png (0 hunks)
  • event-carried-state-transfer/etc/event-carried-state-transfer.urm.puml (1 hunk)
  • event-carried-state-transfer/pom.xml (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerState.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerUpdatedEvent.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventListener.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/Order.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderRejectedException.java (1 hunk)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/AppTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplicaTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerServiceTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerStateTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/EventBusTest.java (1 hunk)
  • event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/OrderServiceTest.java (1 hunk)
  • pom.xml (1 hunk)
Actionable Comments (8)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java [62-66]

    bug: "Logger name mismatch with Lombok in App"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java [73-75]

    bug: "Logger name mismatch in CustomerService"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java [85-85]

    bug: "Logger name mismatch in CustomerService"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java [97-97]

    bug: "Logger name mismatch in CustomerService"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java [56-62]

    bug: "Logger name mismatch in CustomerReplica"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java [81-85]

    bug: "Logger name mismatch in OrderService"

  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java [59-59]

    bug: "Logger name mismatch in EventBus"

  • event-carried-state-transfer/pom.xml [36-50]

    maintainability: "JUnit 5 API dependency missing"

Skipped Comments (0)

Comment thread event-carried-state-transfer/pom.xml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Event-carried state transfer pattern

1 participant