The Outbox That Keeps Your Events Honest
Save the order, publish the event, crash in between - the dual-write problem, and how Convey's outbox and inbox turn at-least-once messaging into something you can build a business on.
Newest first, grouped by year. Search or filter by topic.
Save the order, publish the event, crash in between - the dual-write problem, and how Convey's outbox and inbox turn at-least-once messaging into something you can build a business on.
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.
What Convey's RabbitMQ subscriber actually does when your handler throws - fixed-interval retries, an exception-to-message escape hatch, a dlx- safety net, and one plugin that can quietly defeat all of it.
How Convey's RabbitMQ package turns a class name into an exchange, a routing key and a queue - two eager connections, a channel per thread, and a topology you never declare by hand.
Convey's messaging abstraction is two interfaces and an attribute - and the naming conventions underneath them quietly turn your C# class names into a public wire contract.
Operations subscribes to eighty estate messages from one hand-maintained JSON file - a shadow copy of every contract in the system that nothing validates, with ghost entries nobody publishes and real rejections it never lists.
The estate's full who-publishes-what matrix, built from the source - and the one broken cell in it. Orders' copied ParcelDeleted event declares the deliveries exchange while Parcels publishes on parcels, so Orders never learns a parcel was deleted, and neither the Pact tests nor the hand-maintained registry can catch it.
Pacco's naming system is consistent enough to parse - exchanges are bounded contexts, queue names spell their own bindings, commands and events conjugate in snake_case, and every command has a _rejected twin. Reading the estate as a language, including its irregular verbs and the fact that no grammar-checker exists.
One JSON blob born at Pacco's gateway rides the Correlation-Context HTTP header into services and the message_context AMQP header across the bus, surfacing in SignalR pushes - carried by a class copy-pasted into eleven repos and a broker that forwards exactly one header.
Pacco's Ntrada gateway is ninety lines of C# and five hundred of YAML, and swapping one route key - use downstream for use rabbitmq - turns the same HTTP surface from a proxy into a bus publisher answering 202. The two compose files disagree about which one you get.
Pacco's Operations service listens to all eighty messages in the estate while owning zero contract classes - it reads a JSON registry and forges the CLR types at runtime with Reflection.Emit. The estate's answer to the shared-contracts problem, and the drift it cannot see.
Thirteen repositories, one umbrella that runs twelve of them, two interchangeable API gateways and a service that watches everything - the Pacco estate read as a system of systems, and the fifteen-part series it earns.
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.