Skip to content

feat: transactional outbox pattern - #3589

Open
Mukul-Howale wants to merge 6 commits into
iluwatar:masterfrom
Mukul-Howale:feat-transactional-outbox-pattern
Open

feat: transactional outbox pattern#3589
Mukul-Howale wants to merge 6 commits into
iluwatar:masterfrom
Mukul-Howale:feat-transactional-outbox-pattern

Conversation

@Mukul-Howale

Copy link
Copy Markdown
Contributor

Adds the Transactional Outbox Pattern (transactional-outbox) module to the pattern catalog using Spring Boot.

  • Problem: Resolves the dual-write problem in microservices where writing to a database and publishing to a message broker in separate steps causes data inconsistencies or lost messages.
  • Solution: Persists business entities (Order) and outbound events (OutboxEvent) into the database within a single @Transactional boundary. A background @Scheduled service (OutboxPublisher) periodically polls pending events and dispatches them to the message broker.
  • Key Components:
    • Order & OutboxEvent: Spring Data JPA entities.
    • OrderService: @Transactional service performing atomic dual-writes.
    • OutboxPublisher: Background worker polling PENDING outbox events and updating status to PROCESSED.
    • README.md: Complete documentation including architecture flow, Mermaid class diagram, and programmatic examples.
    • Unit & Integration tests using @SpringBootTest and H2 in-memory database.

Fixes : 3531

Introduce a new transactional-outbox Maven module demonstrating the Transactional Outbox pattern. Adds a Spring Boot sample app and README explaining the pattern. New code includes Order and OutboxEvent entities, OrderStatus/EventStatus enums, Spring Data repositories (OrderRepository, OutboxRepository), OrderService (atomic write of order + outbox), OutboxPublisher (scheduled poll & publish), MessageBroker interface and MessageConsumer implementation, and App entrypoint. Includes unit tests (AppTest, OrderServiceTest, OutboxPublisherTest). Root pom.xml updated to register the new module. Module uses Spring Data JPA, Spring Web, Lombok, H2 and standard test dependencies.
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

PR Summary

Introduced the Transactional Outbox Pattern module to demonstrate reliable event publishing in microservices. The module persists orders and OutboxEvent within a single transactional boundary and uses a scheduled OutboxPublisher to dispatch events to a message broker. Includes entities, repositories, services, a Spring Boot demo App, tests, and README documentation.

Changes

File Summary
pom.xml Root pom updated to include new Maven module 'transactional-outbox', enabling multi-module build.
transactional-outbox/README.md Documentation for the Transactional Outbox pattern including architecture, class diagram, programmatic example, and usage in a Spring Boot app.
transactional-outbox/pom.xml Module pom declaring dependencies (Spring Boot Data JPA, Web, Lombok, H2, Test), test config, and main class packaging; includes jar-with-dependencies assembly config.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/App.java Demo app entrypoint enabling scheduling; creates sample orders, triggers outbox publishing, and inspects consumed messages via the in-memory broker.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/EventStatus.java Enum representing the status of an outbox event (PENDING, PROCESSED, FAILED).
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/MessageBroker.java Interface for message broker publishing. Provides publish(topic, payload).
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/MessageConsumer.java In-memory MessageBroker implementation that records consumed messages for testing and demonstration.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/Order.java JPA entity representing a business Order with fields: id, customerName, productName, amount, status, createdAt.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderRepository.java Spring Data JPA repository for Order entity.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderService.java Service that creates an Order and a corresponding OutboxEvent within a transactional boundary; uses UTC timestamps and logs.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderStatus.java Enum representing the status of an order (CREATED, PROCESSING, COMPLETED, CANCELLED).
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxEvent.java Entity representing an outbox_events record with fields for id, aggregateType, aggregateId, eventType, payload, status, createdAt, processedAt.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java Scheduled OutboxPublisher that polls PENDING events, publishes via MessageBroker, and updates statuses with UTC timestamps.
transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxRepository.java Spring Data JPA repository for OutboxEvent with a method to find by status.
transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/AppTest.java Spring Boot test ensuring App.main runs without errors.
transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OrderServiceTest.java Spring Boot test verifying that creating an order saves both the Order and a pending OutboxEvent atomically.
transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherTest.java Spring Boot tests for OutboxPublisher behavior: empty result when no pending events and publishing with status updates.
transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherUnitTest.java JUnit5 + Mockito unit test verifying broker exception handling marks events as FAILED and calls repo.save.

autogenerated by presubmit.ai

@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)
  • 4925e7e: Add transactional-outbox module

Introduce a new transactional-outbox Maven module demonstrating the Transactional Outbox pattern. Adds a Spring Boot sample app and README explaining the pattern. New code includes Order and OutboxEvent entities, OrderStatus/EventStatus enums, Spring Data repositories (OrderRepository, OutboxRepository), OrderService (atomic write of order + outbox), OutboxPublisher (scheduled poll & publish), MessageBroker interface and MessageConsumer implementation, and App entrypoint. Includes unit tests (AppTest, OrderServiceTest, OutboxPublisherTest). Root pom.xml updated to register the new module. Module uses Spring Data JPA, Spring Web, Lombok, H2 and standard test dependencies.

Files Processed (17)
  • pom.xml (1 hunk)
  • transactional-outbox/README.md (1 hunk)
  • transactional-outbox/pom.xml (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/App.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/EventStatus.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/MessageBroker.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/MessageConsumer.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/Order.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderRepository.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderService.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderStatus.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxEvent.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxRepository.java (1 hunk)
  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/AppTest.java (1 hunk)
  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OrderServiceTest.java (1 hunk)
  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherTest.java (1 hunk)
Actionable Comments (0)
Skipped Comments (0)

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.72%. Comparing base (c842319) to head (97aea79).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3589      +/-   ##
============================================
+ Coverage     83.69%   83.72%   +0.02%     
- Complexity     4257     4272      +15     
============================================
  Files          1115     1121       +6     
  Lines         15066    15144      +78     
  Branches        721      723       +2     
============================================
+ Hits          12610    12679      +69     
- Misses         2161     2167       +6     
- Partials        295      298       +3     

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

Add two unit tests for OutboxPublisher: one verifies processOutboxEvents returns an empty list when there are no pending events; the other simulates a MessageBroker exception to ensure the event status is set to FAILED and the repository.save is called. Added necessary Mockito and assertion imports to support the tests.
Replace the legacy inline/mock-based test with a JUnit5 + MockitoExtension unit test. Adds OutboxPublisherUnitTest (uses @ExtendWith(MockitoExtension.class), @mock and @Injectmocks) that verifies broker exceptions mark events as FAILED and that the repository.save(...) is called. Removes the duplicate inline-mocking test and unused Mockito imports from OutboxPublisherTest.

@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)
  • b46109e: Use MockitoExtension for OutboxPublisher test

Replace the legacy inline/mock-based test with a JUnit5 + MockitoExtension unit test. Adds OutboxPublisherUnitTest (uses @ExtendWith(MockitoExtension.class), @mock and @Injectmocks) that verifies broker exceptions mark events as FAILED and that the repository.save(...) is called. Removes the duplicate inline-mocking test and unused Mockito imports from OutboxPublisherTest.

Files Processed (2)
  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherTest.java (1 hunk)
  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherUnitTest.java (1 hunk)
Actionable Comments (0)
Skipped Comments (3)
  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherTest.java [57-59]

    best_practice: "Graceful handling of empty outbox"

  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherTest.java [68-75]

    testing: "End-to-end state validation after processing"

  • transactional-outbox/src/test/java/com/iluwatar/transactionaloutbox/OutboxPublisherTest.java [60-65]

    testing: "Improve coverage for failure path and success path"

Remove Lombok's @slf4j and add explicit org.slf4j.Logger/LoggerFactory fields in transactional-outbox classes.
This makes logging explicit and removes reliance on Lombok's @slf4j annotation.

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

Remove Lombok's @slf4j and add explicit org.slf4j.Logger/LoggerFactory fields in transactional-outbox classes.
This makes logging explicit and removes reliance on Lombok's @slf4j annotation.

Files Processed (4)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/App.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/MessageConsumer.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderService.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java (1 hunk)
Actionable Comments (1)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java [53-58]

    best_practice: "Scheduled method should return void"

Skipped Comments (5)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/App.java [69-72]

    possible_bug: "Potential null IDs when logging after transactional creates"

  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/App.java [74-76]

    best_practice: "Directly invoking scheduled method"

  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/App.java [79-82]

    maintainability: "Thread-safety considerations for in-memory broker"

  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/MessageConsumer.java [41-47]

    possible_issue: "Null payload safety on publish"

  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java [66-69]

    performance: "Suggest batch update for efficiency"

Introduce publishOutboxEvents() annotated with @scheduled(fixedDelay = 5000) that delegates to the existing processOutboxEvents(). This separates the scheduling concern from the transactional processing method; processOutboxEvents() remains @transactional and now only handles fetching and dispatching pending OutboxEvent items.
Use UTC for timestamps in OrderService and OutboxPublisher by switching LocalDateTime.now() to LocalDateTime.now(ZoneOffset.UTC). Replace Lombok @requiredargsconstructor on OutboxPublisher with an explicit constructor for dependency injection and remove the @transactional annotation from processOutboxEvents. Update processedAt assignment to use UTC as well.

@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)
  • 97aea79: Use UTC timestamps and add OutboxPublisher ctor

Use UTC for timestamps in OrderService and OutboxPublisher by switching LocalDateTime.now() to LocalDateTime.now(ZoneOffset.UTC). Replace Lombok @requiredargsconstructor on OutboxPublisher with an explicit constructor for dependency injection and remove the @transactional annotation from processOutboxEvents. Update processedAt assignment to use UTC as well.

Files Processed (2)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderService.java (1 hunk)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java (1 hunk)
Actionable Comments (1)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OrderService.java [76-80]

    possible bug: "Locale-sensitive JSON payload formatting"

Skipped Comments (2)
  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java [54-57]

    possible issue: "Scheduled publisher potential multi-instance concurrency"

  • transactional-outbox/src/main/java/com/iluwatar/transactionaloutbox/OutboxPublisher.java [65-78]

    possible issue: "Potential race/duplication risk when processing pending events"

@Mukul-Howale

Copy link
Copy Markdown
Contributor Author

@iluwatar Please review

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.

Add Outbox Pattern for Event Publishing

1 participant