Open-source project
andreschaffer/event-sourcing-cqrs-examples avatar
andreschaffer/event-sourcing-cqrs-examples

event-sourcing-cqrs-examples: a bank domain showing Event Sourcing without textbook CQRS

Event Sourcing and CQRS in practice.

599 stars122 forksJavaMIT

At a glance

What is it?
This Java project implements Event Sourcing and a pragmatic form of CQRS over a minimal bank domain, and its most useful content is the set of design decisions it documents, from replay idempotency to event versioning for optimistic locking.
Who is it for?
Adopt this repository as a reference implementation if your team already understands Event Sourcing and CQRS and wants a working Java example with its trade-offs spelled out, and skip it if you are looking for a framework, since the repository ships none.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A bank domain small enough to read end to end

The project description is one line: Event Sourcing and CQRS in practice. The subject is a minimalistic bank. A client can open one or more accounts, and on each account the client can deposit or withdraw money. The history of an account's transactions is available to the client, and so is a summary of the client's accounts.

That is deliberately small. The whole point of a teaching implementation is that you can hold the entire domain in your head, and a bank is small enough for that while still exercising the interesting parts: an account has a lifecycle, money moves, and the balance is a derived value rather than a stored one.

The README is explicit about its audience. It assumes the reader has basic knowledge of Event Sourcing and CQRS concepts, and points to Martin Fowler's Event Sourcing and CQRS articles for anyone who wants to brush up. So this is not a tutorial that teaches the concepts from zero. It is a reference implementation that shows what the concepts look like in code once you already have the vocabulary.

The primary language is Java, the licence is MIT, and the repository is organised as a single Maven project with a pom.xml and a src directory.

The architecture puts the event store upstream of both models

The diagram in the README is worth reading slowly, because the arrows carry the argument.

At the top sit two databases, an Event Store and Projections. The Domain Model and the Read Model sit below them, side by side, with the API at the bottom. The event store receives events from the domain model side, and the read models are projections built from those events.

What the layout shows is that the Domain Model owns the writes. Commands are handled by aggregates on the domain side, and the events they produce are what gets persisted. The Read Model exists to serve queries, and it is a projection that can be built from different events and aggregates and queried by more appropriate fields.

The design choices section then explains two structural decisions. For the Domain Model, the project uses a Ports and Adapters structure, chosen to protect the domain logic from all the technical concerns, with a link to an explanation of the pattern. For the Read Models, it uses Package by Feature instead of a layered structure, on the reasoning that isolating the layers would bring no benefit, so all feature related parts are kept close together.

Two different structures for two different halves of the system is the point. The domain side is protected by ports; the read side is organised for query convenience.

CQRS here does not separate reads from writes

This is the section that makes the project worth reading, because the authors say plainly what they did not build.

The README describes the approach as pragmatic. By the book, CQRS proposes a complete separation between the read/query and write/command sides, and the project states that this is not what is here.

What is here instead:

The writes and commands all live on the domain model side and are processed by aggregates. The reads and queries live on both sides. Queries on the domain model side are only allowed when the data needed is a single aggregate itself, and the stated reason is practical rather than doctrinal: the event store can only be queried by aggregate id, so those queries can be fulfilled by replaying that single aggregate's events.

For any other kind of query, the project refuses to compromise the domain model and creates read models instead. Those read models are projections, potentially built from different events and aggregates, and queryable by more appropriate fields.

So the rule of thumb is that one aggregate can be read from the event stream, and anything wider needs a projection. This is a coherent middle position between textbook CQRS and no CQRS at all, and it is a good deal more useful than a design that only ever appears in diagrams.

Replay runs assignments, never business logic

Event Sourcing has one failure mode that quietly breaks systems, and the project addresses it head on.

The section on idempotency states the rule: when replaying events, you do not want to execute any business logic because history cannot change, and you only want assignments. The example given is a deposit event. Instead of adding the deposited amount to the balance during replay, which would be business logic, the updated balance is made available so it can simply be assigned. The consequence stated is that the event can be replayed multiple times with the same outcome.

This is the difference between a balance of 100 and a balance of 10000 after you rebuild your projections. If a replay applies arithmetic on top of a balance that already reflects the arithmetic, every rebuild inflates the numbers. Carrying the resulting state in the event and assigning it makes the rebuild idempotent, and it moves the business decision to the moment the event was first created.

The cost is that events become less compact and carry redundant state, and it means the aggregate's decision logic has to run exactly once, at write time, for the recorded outcome to be trustworthy. The README does not discuss how the write path guarantees that, but the principle is clear enough to implement correctly.

Event versioning replaces timestamps for ordering

The second interesting design decision is about ordering, and it comes from a specific problem: in a distributed world, event timestamps are unreliable for ordering because machines have their own clocks.

The project makes ordering explicit with an event version, and uses that version in two distinct ways.

On the write and command side, the version protects against race conditions through optimistic locking. Two commands reading the same account state concurrently would otherwise both succeed and one would be lost; comparing the expected version against the stored version turns that into a conflict.

On the read and query side, the version is used for commutative reasons. Events can come out of order and the projection can still handle them properly. This is the more interesting half, because a projection built from events that arrive out of sequence has to decide what to do with a stale event, and a version number gives it something concrete to compare.

The README also points at Lamport timestamps and Vector clocks as alternatives to read about, which is a fair pointer rather than a claim that one of them is better. The version number here is simpler than both and works because the system is small.

One related note sits in the DDD and REST section. The project says it followed both techniques quite strictly and hopes the result shows they can be combined well, while explicitly leaving out REST hypermedia controls because that is a big subject in itself and would shift the focus away from Event Sourcing and CQRS.

Building and running the bank service locally

The requirements are short: Java 14 and Maven. That Java version is old, and it is the first thing to check against your own toolchain before you start, because the build is pinned to it rather than floating.

The build command is a single Maven invocation, and the README gives it in this form:

bash
mvn clean verify

The application is then started from the packaged jar, pointing at the development environment configuration:

bash
` java -jar target/bank-service-1.0-SNAPSHOT.jar server src/environments/development.yml `

Once it is running, the README walks through the API with curl calls. Creating a client posts a name and an email to the clients endpoint and the response Location header holds the new identifier. Creating an account posts a clientId to the accounts endpoint and again returns the identifier in Location. Depositing posts an amount to the deposits path of an account, and a plain GET on the account endpoint returns the balance, which the README invites you to check by making yourself a millionaire with a deposit of 1000000.

The placeholders in those commands are written as {CLIENT_ID} and {ACCOUNT_ID}, and you substitute the values from the Location headers. The service listens on port 8080 in the development configuration.

What this repository is not, and who should use it

The boundaries matter more than the code. This is examples, not a framework. There is no event store library to adopt, no aggregate base class, no projection runner to configure. What you get is one working implementation with the design decisions written down, so that you can copy the reasoning rather than the code.

The second boundary is the language and the shape. Java 14 with Maven, a single service, a hexagonal structure on the write side and package by feature on the read side. If your stack is .NET, Go or Python, the structure translates but the code does not, and the value drops to the design notes alone.

The third is that the README points to Fowler for the concepts. Beginners will be blocked, and that is intentional rather than an oversight.

A fair comparison is with any well-written reference implementation, or with writing the toy yourself. Writing it yourself teaches more, but it also means you will spend a day rediscovering the replay idempotency trap and the ordering problem. Reading this repository first, then writing your own version in your own stack, is probably the highest-value use of it. The one thing to keep in mind is the MIT licence, which permits reuse and modification with the notice retained, so nothing here legally prevents you from lifting a domain model into a commercial project.

Editorial conclusion

Adopt this repository as a reference implementation if your team already understands Event Sourcing and CQRS and wants a working Java example with its trade-offs spelled out, and skip it if you are looking for a framework, since the repository ships none. Before you start, read the DDD and REST section and the note that reads and writes are not fully separated, because the design deliberately departs from the textbook definition, and note that the project requires Java 14 and Maven and has no tagged releases, with the last push on 2026-10-19.

Frequently asked questions

What does event-sourcing-cqrs-examples demonstrate?

It applies Event Sourcing and a pragmatic form of CQRS to a minimal bank domain where a client opens accounts and deposits or withdraws money. Both the transaction history and a client account summary are exposed.

How does this project handle idempotency when replaying events?

Replays perform assignments only, never business logic, because history cannot change. A deposit event carries the updated balance so it can be assigned directly, which makes replaying the same event multiple times produce the same outcome.

Why does the project use event versioning instead of timestamps?

Timestamps are unreliable for ordering in a distributed world because machines have their own clocks. The event version is used on the write side for optimistic locking against race conditions and on the read side so projections can handle events that arrive out of order.

What are the requirements to build and run the bank service?

Java 14 and Maven. The build is run with mvn clean verify, and the service is started from the packaged jar with the development environment configuration.

Is this a complete CQRS implementation?

No. The README states that by the book CQRS proposes a complete separation between read and write sides, and that is not what is here. Writes are handled by aggregates on the domain side, while queries for a single aggregate are served from the event stream and anything wider is served from read model projections.

Official sources

  1. andreschaffer/event-sourcing-cqrs-examples on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/andreschaffer-event-sourcing-cqrs-examples.svg)](https://hysenlabs.com/projects/andreschaffer-event-sourcing-cqrs-examples)

Was this useful?