CQRS with Event Sourcing sample app using Rails Event Store. See it live.
This application simulates a process of managing orders.
We start with a list of exiting products and customers (populated with seeds).
If you would like to use Docker image with PostgreSQL provided by us, run docker-compose up -d
. Your done for this
step.
If you have PostgreSQL installed directly in your system and prefer to use it, create
.env.development.local
containing:
DATABASE_URL=postgresql:///cqrs-es-sample-with-res_development
.env.test.local
containing:
DATABASE_URL=postgresql:///cqrs-es-sample-with-res_test
It should would work for most of the cases. If you have more sophisticated setup, you need to update DATABASE_URL
accordingly.
Run make dev
to install dependencies, create db, setup schema and seed data.
Run bin/rails s
to start the web server.
The customer perspective is "simulated" only - via using the select box.
- Add/remove some of the existing products to the order
- Choose the customer
- Submit the order
- after this you can't update the order items or customer
- it generates the order number like "2021/03/20" (the last part is random(100))
- Look at the order
- Look at the history of events (in the Rails Event Store Browser)
In /admin
we show how to combine the ActiveAdmin
gem with the
DDD/event-driven approach. We do it via limiting the typical CRUD actions.
All the "view" options are still there.
Admin can cancel an order in the admin panel.
Domains exist in directories at the root level of the Rails app.
The Ordering::Order
aggregate manages the state machine of an order:
- draft
- submitted
- paid
- expired
- cancelled
After each successful change an appropriate event is published in the Order stream. This object is fully event sourced.
Order | draft | submitted | paid | expired | cancelled |
---|---|---|---|---|---|
draft | ✅ | ✅ | |||
submitted | ✅ | ✅ | |||
paid | |||||
expired | |||||
cancelled |
The Payments::Payment
aggregate manages the following states:
- authorized
- captured
- released
This Order object is fully event sourced.
We implement this domain as a CRUD-based bounded context. The goal is to present how to deal with such CRUD-ish domains and to show how to integrate it with parts of the system.
It's just a single ActiveRecord Product
class.
We wrap it with a ProductCatalog
namespace to explicitly set its boundaries.
This Bounded Context has both - the write part and the read part as the same model. You can say it's not really CQRS - which is true for many CRUDish bounded contexts.
There's only one read model - which helps us listing all the orders and individual order details.
It consists of 2 ActiveRecord classes: Order
and OrderItem
.
There's a process manager responsible for dealing with the process of expiring orders.
It takes the following events as the input:
- Ordering::OrderSubmitted
- Ordering::OrderExpired
- Ordering::OrderPaid
- Payments::PaymentAuthorized
- Payments::PaymentReleased
When certain conditions are met the process manager return a
ReleasePayment
command.
Another process manager is responsible for confirming order. It does it, when a successful payment is detected.
Over time we have developed a number of DDD-related online courses. We now sell them as part of 1 membership access via https://arkademy.dev for $49/month.