Skip to content
Kumar Chandrachooda
Microservices

The Order That Choreographs Itself

Nobody in Pacco approves an order - a vehicle reservation does. A tour of DevMentors' parcel-delivery estate, the domain it models, and the fifteen-part series it earns.

By Kumar Chandrachooda 01 Nov 2025 6 min read
One order, many voices, no conductor

Nobody in Pacco approves an order. There is an Approve() method on the Order aggregate, and a guard that throws if you call it at the wrong moment, but in the end-to-end flow no human and no endpoint invokes it directly. The order approves itself — the moment a different service, three repositories away, announces on RabbitMQ that a vehicle was reserved for a delivery date. The order's whole lifecycle works like this: it is created by a command, and then it mostly reacts. Deliveries starting, deliveries failing, reservations being cancelled because someone more important wanted the same van on the same day — each arrives as an event, and the order changes state in response.

That design — an aggregate whose transitions are mostly reactions to other services' facts — is the thing this series reads, tests against the source, and occasionally argues with.

Pacco is the open-source parcel-delivery estate by DevMentors — Piotr Gankiewicz and Dariusz Pawlukiewicz — published at github.com/devmentors under MIT: ten backend microservices plus a saga-driving client, spread across thirteen sibling repositories. It is the successor to their earlier DShop sample, and it is built on Convey, the framework the same authors distilled out of DShop's shared code. To be clear up front: I did not write Pacco. I am a source-reader working through a well-known teaching estate, and everything in this series is grounded in the actual repositories. Where the implementation is the story I will quote it, attributed by file path; everything else is fresh example code. Where I find defects — and I have found real ones — I will show the code, say what it does versus what it announces, and be fair about why it might be shaped that way.

A marketplace for weapons and organs

The domain deserves a proper introduction, because it is funnier and richer than “todo app with queues”. Pacco is a parcel-delivery marketplace. Customers sign up, complete their registration, and create orders. Orders hold parcels — and Pacco.Services.Parcels declares the parcel variants as an enum: Standard, Chemistry, Weapon, Animal, Organ. Weapons and organs, welcome cargo. Parcels are sized as cubes, from Tiny (10 cm a side) to Exclusive (2 m a side), and a domain service computes total volume in cubic metres.

Delivery capacity is a reservation ledger. Pacco.Services.Availability models vehicles as generic “resources”, each with a set of date-granular reservations — and the reservation rule has teeth: a reservation carries a Priority, and a higher-priority reservation for the same date expropriates the existing one, cancelling it with a compensating event. VIP customers can bump you off your delivery date. Pricing is a loyalty ladder: Pacco.Services.Pricing grants 2 % off after your first completed order, 5 % after three, 10 % after ten, and a further 10 % on top once Pacco.Services.Customers marks you VIP at twenty completed orders. Real rules, real state machines, real conflicts — we will also see, in the closing part, how deliberately thin the data underneath them is.

Five services carry the domain weight and are this series' main cast:

Service Owns Style
Orders order lifecycle, parcels-in-order aggregate + state machine
Parcels parcel catalogue per customer anemic entity, publishes from handlers
Availability resources + reservations the DDD showcase
Customers registration lifecycle, VIP aggregate with a lifecycle machine
Pricing the discount ladder deliberately bus-less calculator

Around them sit Deliveries, Vehicles, Identity, Operations, OrderMaker, and two API gateways — companion series cover the estate-as-a-system and the framework-consumer view; this one stays inside the domain.

An order is a list of reactions

Here is the shape that gives the series its title. Pacco.Services.Orders subscribes to seven commands — and seven external events. Reading Infrastructure/Extensions.cs, the subscription list is the plot summary:

.SubscribeEvent<CustomerCreated>()
.SubscribeEvent<DeliveryCompleted>()
.SubscribeEvent<DeliveryFailed>()
.SubscribeEvent<DeliveryStarted>()
.SubscribeEvent<ParcelDeleted>()
.SubscribeEvent<ResourceReserved>()
.SubscribeEvent<ResourceReservationCanceled>()

CustomerCreated seeds a local id-only projection that gates order creation. DeliveryStarted, DeliveryCompleted and DeliveryFailed drive the order to Delivering, Completed and Canceled. ResourceReserved — Availability's announcement that a reservation landed — triggers Approve(). ResourceReservationCanceled — the expropriation aftermath — triggers Cancel(reason). The order is less a thing you operate and more a thing that listens. There is no process manager inside any of these services deciding the sequence; the sequence emerges from who publishes what and who subscribes to it. That is choreography in the textbook sense, and Pacco commits to it hard enough that we can study both what it buys and precisely where it frays — including two places where the same fact ends up permanently disagreed about by two services.

Reading a 2020 estate honestly

A note on era, because source-reading without it turns into cheap shots. Pacco targets netcoreapp3.1 — roughly 2020, end-of-life since December 2022 — on the legacy WebHost builder with Newtonsoft serialisation. Every Convey package floats at a 0.4.* wildcard; nothing is pinned. The infrastructure is RabbitMQ as the bus, MongoDB per service, Redis, and a Consul/Fabio/Vault/Jaeger platform in Docker. There is no frontend anywhere in the thirteen repositories — a deliberate contrast with DShop, which shipped two full SPAs. None of this makes the estate less worth reading; teaching estates age in their packages and stay young in their ideas. When I critique, I will critique the ideas and the code as written, not the year on the calendar.

And the ideas earn the fifteen parts. Within one estate, under one framework, you can watch the same problem solved at three maturity levels — an anemic entity publishing from handlers, an aggregate with inline event mapping, and an aggregate behind a processing pipeline. You can watch one domain event fan out into five integration contracts. You can watch choreography correlate by natural keys, rejected events rot into the wrong channels, and an outbox run with its transactions switched off. Every one of those sentences is a part of this series, and every one is backed by a file path.

Where the series goes

  1. The Order That Choreographs Itself — this post.
  2. Three Ages of a Domain Model — Parcels, Orders and Availability as three maturities of the same shape.
  3. The Reservation That Bumps You — the expropriation invariant and a struct value object with a deliberate asymmetry.
  4. One Event, Five Contracts — the order state machine and the mapper that routes on aggregate state.
  5. A Checkout With No Conductor — the reservation conversation reconstructed end-to-end.
  6. Correlation by Natural Key — choreography without correlation ids, and what shares a vehicle and a date.
  7. Rejected Events and How They Rot — the application-level invalid message channel and its decay.
  8. The Parcel That Wouldn't Leave — two bounded contexts, one association, two bugs.
  9. Same Fact, Two Names — ubiquitous-language translation at the Customers boundary.
  10. Three Ways to Borrow Another Service's Data — one upstream, three consistency stances.
  11. The Outbox With Transactions Turned Off — guaranteed delivery minus atomicity.
  12. CQRS at the Transport Layer — commands on two channels, queries on one.
  13. Optimistic Concurrency That Never Says No — a version filter with a discarded result.
  14. One Request, Four Services — the synchronous call chain on the write path.
  15. Rich Rules, Demo-Grade Data — the honest retrospective.

If you have ever wondered what event-driven microservices look like past the diagram — with real aggregates, real RabbitMQ bindings, and real bugs left in for the finding — this estate is one of the best public places to look, and the next fourteen parts are the guided read. We start with the estate's most instructive trick: the same domain, modelled three different ways, three folders apart — three ages of a domain model.