Sample is showing basic Event Sourcing flow. It uses EventStoreDB for event storage and Spring Data JPA backed with PostgreSQL for read models.
The presented use case is Shopping Cart flow:
- The customer may add a product to the shopping cart only after opening it.
- When selecting and adding a product to the basket customer needs to provide the quantity chosen. The product price is calculated by the system based on the current price list.
- The customer may remove a product with a given price from the cart.
- The customer can confirm the shopping cart and start the order fulfilment process.
- The customer may also cancel the shopping cart and reject all selected products.
- After shopping cart confirmation or cancellation, the product can no longer be added or removed from the cart.
Technically it's modelled as Web API written in Spring Boot and Java 22.
Practical introduction to Event Sourcing with Spring Boot and EventStoreDB
- explain basics of Event Sourcing, both from the write model (EventStoreDB) and read model part (PostgreSQL and Spring Data JPA),
- present that you can join classical approach with Event Sourcing without making a massive revolution,
- CQRS architecture sliced by business features, keeping code that changes together at the same place. Read more in How to slice the codebase effectively?,
- clean, composable (pure) functions for command, events, projections, query handling, minimising the need for marker interfaces. Thanks to that testability and easier maintenance.
- easy to use and self-explanatory fluent API for registering commands and projections with possible fallbacks,
- registering everything into regular DI containers to integrate with other application services.
- pushing the type/signature enforcement on edge, so when plugging to DI.
It uses:
- pure data entities, functions and handlers,
- Stores events from the command handler result EventStoreDB,
- Builds read models using Subscription to
$all
. - Read models are stored to Postgres relational tables with Spring Data JPA.
- App has Swagger and predefined docker-compose to run and play with samples.
- Sample ShoppingCart entity and events represent the business workflow. All events are stored in the same file using sealed classes to be able to understand flow without jumping from one file to another. It also enables better typing support in pattern matching.
ShoppingCart
also contains when method method defining how to apply events to get the entity state. It uses the Java 22 switch syntax for pattern matching. - Command handlers are defined as static methods in the same file as command definition. It improves ergonomy and reduces context switching as they usually change together. They are pure functions that take command and/or state and create new events based on the business logic. See sample Adding Product Item to ShoppingCart. This example also shows that you can inject external services to handlers if needed.
- Code uses functional interfaces in many places to introduce composability and lously coupled, testable code.
- Added EventStoreDB entity store to load entity state and store event created by business logic.
- Command and Query handlers are grouped/wrapped into application service, to have the single entry point with all possible operations, see ShoppingCartService
- All command handling has to support optimistic concurrency. Implemented full flow using If-Match header and returning ETag. Read more details in my article How to use ETag header for optimistic concurrency
- Read models are rebuilt with eventual consistency using subscribe to $all stream EventStoreDB feature,
- Used Spring Data JPA to store projection data into Postgres tables.
- Added sample projection for Shopping cart details and slimmed Shopping cart short info as an example of different interpretations of the same events. Shopping cart details also contain a nested collection of product items to show more advanced use case. Added also base JPAProjection class to reduce needed boilerplate. It supports idempotency by checking the last processed event position against the read model.
- Projections are registered as Spring Boot event listeners. Which enables reusing the generic mechanism for publishing events from EventStoreDB,
- Added long-polling example with If-None-Match header handling for getting shopping cart details by id. See details in ShoppingCartService and ShoppingCartDetailsRepository
- Used service EventStoreDBSubscriptionToAll to handle subscribing to all. It handles checkpointing and resubscribing when the connection is dropped. Added also general background worker to wrap the general Smart Lifecycle handling.
- EventStoreDB subscription forwards events to the Spring Boot ApplicationEventPublisher through EventForwarder wrapping them with EventEnvelope to pass theirs metadata.
- example query handlers for reading data t.
- Used checkpointing to EventStoreDB stream with EventStoreDBSubscriptionCheckpointRepository.
- Added API integration tests together with a simple wrapper with Specification Pattern to make tests cleaner using Given/When/Then approach. See more in ApiSpecification. See example tests in AddProductItemToShoppingCartTests.
- Added basic unit tests for state rebuild in ShoppingCartTests
- Install git - https://git-scm.com/downloads.
- Install Java JDK 22 (or later) - https://www.oracle.com/java/technologies/downloads/.
- Install IntelliJ, Eclipse, VSCode or other preferred IDE.
- Install docker - https://docs.docker.com/engine/install/.
- Open project folder.
- Run:
docker-compose up
. - Wait until all dockers got are downloaded and running.
- You should automatically get:
- EventStoreDB UI (for event store): http://localhost:2113/
- Postgres DB running (for read models)
- PG Admin - IDE for postgres. Available at: http://localhost:5050.
- Login:
[email protected]
, Password:admin
- To connect to server Use host:
postgres
, user:postgres
, password:Password12!
- Login:
- Open, build and run
ECommerceApplication
.- Swagger should be available at: http://localhost:8080/swagger-ui/index.html