When a warehouse update fails, someone has to decide whether to retry it, correct it, or wait for another system. That decision gets awkward when the only documentation is a scheduled task on a server and the person who wrote it is on holiday. This is the work I want an integration platform to make easier.
For the fictional seafood company in this series, I would start with one flow: an accepted order in the ERP needs a stock reservation in the warehouse. It is a useful starting point because it has an identifiable business result, a failure that people care about, and a fairly clear owner on each side.
Give the flow a contract and an owner
I would write down what the ERP is promising when it emits OrderAccepted.v1. Has the customer been validated? Are quantities final? Can the order still change? The warehouse team needs those answers before it can implement a consumer. A JSON schema can check that quantity is a number. The teams still have to agree whether that number represents kilograms, boxes, or individual items.
The event needs a stable eventId, an orderId, a schema version and a source revision. I would keep customer names and addresses out of this event unless the warehouse genuinely needs them. The integration repository should include an example message, schema, producing team, consuming team and recovery instructions. An owner should be a maintained team contact that operations can actually reach.
{
"eventId": "9f26c80e-5b91-438b-86c9-6f27a13f2195",
"type": "OrderAccepted.v1",
"orderId": "SO-1042",
"revision": 3,
"lines": [{ "itemId": "SALMON-BOX", "quantity": 12, "unit": "box" }]
}
A small implementation I would start with
In a Microsoft environment, one possible implementation is an ERP adapter, Azure Service Bus and a .NET worker that calls the warehouse API. Azure API Management can sit in front of HTTP endpoints where authentication policy, quotas and API publication are needed. It has a different job from the message broker. Adding it to an asynchronous flow needs a specific reason.
If we can change the source application, I would write the order change and an outbox record in the same SQL transaction. A background relay sends committed outbox records to Service Bus. If the ERP is a packaged application, I would first investigate its supported event or change interface. Writing an outbox alongside a database we do not control requires care; an adapter cannot make two unrelated commits atomic just by calling them consecutively.
The warehouse consumer receives a message under a lock, validates it and calls the reservation endpoint using a stable request identity. It completes the message after the warehouse has confirmed the result. A crash between the reservation and message completion causes a repeat delivery, so the warehouse endpoint needs to recognize the same reservation request. That agreement belongs in the contract.
graph LR ERP[ERP transaction and outbox] --> RELAY[Outbox relay] RELAY --> BUS[Service Bus] BUS --> WORKER[Reservation worker] WORKER --> WH[Warehouse API] WORKER --> OPS[Outcome and error telemetry]
Make uncertain outcomes visible
Test the uncertain outcome
A timeout is the case I would test early. The warehouse may have created the reservation just before the connection dropped. Retrying with a new identifier could reserve the stock twice. I would reuse the original identifier and, where supported, query the reservation status before another attempt. If the target supports neither idempotency nor status lookup, the recovery procedure needs a reconciliation step and probably an operator.
Validation failures go to a controlled error path with a useful reason. Temporary connection failures get bounded retries with increasing delays. Repeated failures need an alert and an owner. A dead-letter queue is only useful if someone can explain why a message is there and what has to happen before it is safe to replay.
What operations should be able to see
For this flow, I want to search by orderId and see source acceptance, publication, receipt, reservation outcome and final completion. Each attempt should record duration, eventId and a technical error code. Full payload logging would expose more customer data than most investigations need.
I would measure the age of the oldest unprocessed message as well as queue depth. The dashboard also needs the last successful reservation, dead-letter count and a link to the runbook. An example service target might be reservation confirmation within two minutes during warehouse operating hours; that target has to be agreed with the people using the result.
Fifty messages arriving seconds ago and fifty messages stuck since yesterday need different responses.
Build a repeatable way to add the next flow
Once this path works, I would package its deployment, logging fields, health checks and contract tests so the next integration can reuse them. Bicep or Terraform can describe the broker resources and access assignments. The worker and schema live in version control, and a pipeline deploys the same artifact through test and production.
The platform team can own that common setup while the warehouse team owns reservation rules. Keeping those responsibilities explicit helps avoid a central integration team becoming the accidental owner of every business decision. Some flows can remain simple scheduled transfers if their timing and recovery requirements allow it.
Before calling the first flow ready, I would stop the worker during processing, send the same event several times, make the target time out and replay a corrected failed message. The expected warehouse state should be written down for each test. Those results give the next team something concrete to copy.
For the broker behaviour behind this design, see Microsoft’s documentation on duplicate detection. Broker deduplication has a configured history window; the reservation endpoint still needs its own repeat-request handling.

Leave a Reply