Commands and Events on the Wire
Convey's broker-CQRS bridge is seventy lines of code that make the same handler serve local dispatch and RabbitMQ delivery — one line per subscription, correlation context riding along for free.
Newest first, grouped by year. Search or filter by topic.
Convey's broker-CQRS bridge is seventy lines of code that make the same handler serve local dispatch and RabbitMQ delivery — one line per subscription, correlation context riding along for free.
FastEndpoints ships in-process messaging that covers most MediatR use cases - events with three wait modes, commands with middleware pipelines, generic and streaming handlers. The dispatch code makes some sharp lifetime choices worth knowing. Part 11 of FastEndpoints in Depth.
The fair part of the ledger - how UseDispatcherEndpoints, self-identifying commands, afterDispatch hooks and snake_case conventions let every Pacco service put its whole HTTP surface in one readable file.
Convey.WebApi.CQRS wires HTTP verbs straight to command and query dispatchers - one line per endpoint - and publishes your message catalog at /_contracts. A source read of the thinnest HTTP layer I've shipped to production.
The series retrospective - Pacco's domain teaches real rules (state machines, priority expropriation, loyalty ladders, compensations) on top of cardboard data (bare-decimal prices, one vehicle per order, size-cube parcels, no payment, and a capacity check that ships in two halves nobody ever connects). What to steal, what to distrust, and what fourteen parts of source-reading add up to.
AssignVehicleToOrder is the one command in Pacco that cannot finish alone - Orders calls Vehicles, then Pricing, and Pricing calls Customers, a synchronous enrichment chain on the write path of a messaging estate. At the end of it sits a deliberately bus-less calculator with a loyalty ladder, an unassigned Id and a discount logged in dollars.
Availability guards every save with a version-filtered ReplaceOneAsync - and discards the result, so a lost update is indistinguishable from a successful one and the integration event ships either way. Three folders away, Orders and Customers inherit the same AggregateRoot name with the versioning quietly deleted.
In Pacco the CQRS split is not a diagram - it is wiring you can read. Every command arrives through two front doors, RabbitMQ subscription and controller-less HTTP dispatch, while queries ride HTTP only; commands assign their own ids in the constructor, and a marker attribute turns each service into its own runtime contract registry.
Every Pacco service that enables Convey's Mongo outbox also sets disableTransactions to true - the aggregate write and the outbox write are separate operations, the pattern's one guarantee switched off in configuration. Meanwhile the inbox that would absorb the resulting duplicates sits beside three handlers that throw on them.
Four Pacco services need customer data and no two agree on how to get it - Orders and Parcels keep id-only replicas fed by events, Availability phones home mid-command, and Pricing pulls the full details document to count one number. One upstream, three consistency stances, five representations of the same entity.
Inside the Customers service a registration completes - and the rest of the estate hears that a customer was created. Three lines of EventMapper perform the best ubiquitous-language translation in Pacco, sitting on top of a lifecycle state machine with one guarded transition and four administrative jumps.
Order.DeleteParcel raises a ParcelDeleted event and never removes the parcel from the set, while Parcels' cancel and delete handlers detach exactly one parcel of many. Two bounded contexts maintain the same association independently, both with bugs, and end up permanently disagreeing about a fact.
Convey's IRejectedEvent gives Pacco an application-level negative acknowledgement - a rejected twin for every command. Reading the four exception-to-message mappers shows the pattern decaying in four different ways, from a failed reserve announced as a failed release to rejection classes nothing ever constructs.
Availability's ResourceReserved event carries no order id, no correlation id, no saga id - just a resource and a date. Orders finds the right order anyway, by treating (vehicleId, deliveryDate) as the correlation key, and the expropriation flow shows exactly when that key stops being unique.
Pacco's flagship flow reconstructed from source - a vehicle is assigned, a reservation lands, an order approves itself, and when a VIP bumps the booking the cancellation ripples through three services with no process manager in sight.
Every transition of Pacco's Order raises the same domain event - OrderStateChanged - and a mapper switches on aggregate status to mint five distinct integration events. A content-based router keyed on state, weighed honestly.
Availability's Resource aggregate enforces one real business rule - a higher-priority reservation evicts yours and announces it. The invariant, the struct value object behind it, and the day-integer it becomes on disk.
Same framework, same four-project layout, three maturities - Parcels publishes from handlers, Orders raises events and maps them inline, Availability runs a full pipeline. The lineage made diffable.
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.
Convey's CQRS layer is small enough to read in one sitting — commands, queries and events as empty interfaces, Scrutor scans to find handlers, and a scope-per-dispatch decision with consequences worth knowing before production.
Storage subscribes to almost every event in DShop, serves read-only queries, and owns nothing it cannot rebuild - the read side of a CQRS split that runs across a service boundary instead of inside one. Part 10 of Nine Services and a Message Bus.
DShop.Common ships its own in-process command and query dispatch - marker interfaces, a handler triple, and one dynamic double-dispatch standing in for a library.
DShop moves data between services two opposite ways - one rebuilds a local replica from the event payload, the other treats the event as a doorbell and fetches over HTTP - and the estate runs both at once. Part 5 of Nine Services and a Message Bus.
The monolith and the microservices share the same command names. Decomposition here was one substitution - resolve a local handler, or publish to a broker - and reading both dispatchers shows exactly what changes.
In DShop.Common a command and an event are published by identical code - the difference between them is a marker interface and a promise nothing enforces.