Transactional Outbox: the order and the message for the ERP are written in the same transaction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // ====================================================================== // Q: Our shop must send orders to the ERP. The ERP is sometimes down. Design it. // // Transactional Outbox: the order and the message for the ERP are written in the // same transaction. A background worker sends the message with retries. // The worker reads unsent messages (PostgreSQL: FOR UPDATE SKIP LOCKED, so // multiple instances don't pick up the same one), sends them and marks them as // processed. After N failures: dead-letter, alert, manual replay. Alternatively, // Azure Service Bus with its built-in DLQ. // Key point: idempotency. With retries the ERP may receive the same message // twice. Send an Idempotency-Key or the OrderNumber as an external reference so // the ERP rejects duplicates. // ====================================================================== // Save the order and the outbox message in one transaction await using var tx = await _db.Database.BeginTransactionAsync(ct); _db.Orders.Add(order); _db.Outbox.Add(new OutboxMessage { Id = Guid.NewGuid(), Type = "OrderCreated", Payload = JsonSerializer.Serialize(new { order.Id, order.OrderNumber }), CreatedAt = DateTime.UtcNow }); await _db.SaveChangesAsync(ct); await tx.CommitAsync(ct); |