Skip to content

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

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

ylcn91 wants to merge 3 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

Introduced ECST as a new module that demonstrates using state-carrying events to keep consumers autonomous. Adds producer, consumer replica, in-memory bus, tests, docs, diagrams, and an end-to-end runnable demo. Includes 27 tests and integrates the module into the parent POM. Related to issue #2434.

Changes

File Summary
event-carried-state-transfer/README.md Documentation of the ECST pattern, including intent, real-world scenario, sequence diagram, and trade-offs; outlines the runnable demo and references.
event-carried-state-transfer/etc/event-carried-state-transfer.urm.png New class diagram image asset for ECST class relationships.
event-carried-state-transfer/etc/event-carried-state-transfer.urm.puml PlantUML diagram describing ECST classes, events, and relationships.
event-carried-state-transfer/pom.xml Module descriptor for ECST: defines module, dependencies, main class, and build setup for running the demo.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java Implements the ECST runnable demo: sets up EventBus, CustomerService, and OrderService, and walks through the five demo steps with logging.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java Consumer replica storing state; applies CustomerUpdatedEvents with version-guarded upsert; provides find and size accessors.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java Producer managing authoritative CustomerState; on changes, publishes CustomerUpdatedEvent carrying full state; supports registration, updates, and offline/restart behavior.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerState.java Immutable CustomerState with validation and helpers to produce updated copies (withShippingAddress, withCreditLimit).
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerUpdatedEvent.java Event carrying the full CustomerState (includes eventId, occurredAt, and state).
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventBus.java In-memory, synchronous bus mapping events to listeners by exact runtime class; supports subscribe and publish with logging and null checks.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/EventListener.java Functional interface for event listeners with a single onEvent method.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/Order.java Value object representing an order: orderId, customerId, shippingAddress, and amount.
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderRejectedException.java Runtime exception thrown when an order is rejected due to replica constraints (unknown customer or over credit limit).
event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/OrderService.java Consumer side that places orders from the replicated data; subscribes to events and exposes the replica via replica(); no producer dependencies.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/AppTest.java Tests for App instantiation, app main execution, and basic flow (online lookup and order logging).
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplicaTest.java Tests replica behavior: first application of event, newer versions, late/duplicate events ignored, unknown customers absent.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerServiceTest.java Tests production side: initial registration publishes full state, subsequent changes publish updated full state, and offline/online behavior.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerStateTest.java Tests CustomerState validation and version bump semantics for address and credit limit updates.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/EventBusTest.java Tests event delivery by exact type, ordering, empty subscribers, and null event rejection.
event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/OrderServiceTest.java Tests order placement from the replica, handling unknown customers, credit-limit checks, and credit-limit updates propagation.
pom.xml Parent 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 84.22%. Comparing base (4cabb20) to head (0cf9b1f).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3602      +/-   ##
============================================
+ Coverage     84.06%   84.22%   +0.16%     
- Complexity     4353     4400      +47     
============================================
  Files          1133     1142       +9     
  Lines         15400    15566     +166     
  Branches        739      745       +6     
============================================
+ Hits          12946    13111     +165     
  Misses         2158     2158              
- Partials        296      297       +1     

☔ 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

@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.

✅ LGTM!

Review Summary

Commits Considered (1)
  • 0cf9b1f: Merge remote-tracking branch 'origin/master' into feat/event-carried-state-transfer

Conflicts:

pom.xml

Files Processed (12)
  • 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)
  • pom.xml (1 hunk)
Actionable Comments (0)
Skipped Comments (5)
  • event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java [62-62]

    best_practice: "Logger name mismatch in App.java"

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

    best_practice: "Logger usage in CustomerService"

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

    best_practice: "Logger usage in CustomerReplica"

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

    best_practice: "Logger usage in EventBus"

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

    best_practice: "Logger usage in OrderService"

This branch has not been deployed

No deployments
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