Event-driven microservices2025Backend lead6 months

Order Pipeline

Moved order processing out of a monolith into event-driven Spring Boot services with an outbox pattern, so a payment provider outage no longer takes the whole shop down.

  • Spring Boot
  • Kafka
  • PostgreSQL
  • Kubernetes
0
double charges since launch
99.95%
order API availability, up from 99.2%
-60%
p95 order placement latency

The problem

Placing an order called payment, inventory, invoicing and notifications synchronously inside one database transaction. Any slow dependency meant failed orders and double charges on retry.

Constraints

Architecture

Key decisions

Decision 01
Transactional outbox instead of dual writes

The order and its OrderPlaced event are written in the same Postgres transaction. A relay publishes the outbox to Kafka, so an event is never lost or sent for a rolled-back order.

Trade-off: A second of extra latency between commit and publish.

Decision 02
Idempotency keys on every consumer

Consumers record processed event ids in the same transaction as their side effect. Redelivery becomes a no-op instead of a double charge.

Trade-off: A dedupe table per service that needs pruning.

Decision 03
Strangler routing at the gateway

Order traffic moved to the new service per market behind a feature flag, with instant rollback to the monolith path.

Trade-off: Two code paths alive for one quarter.

In the code

orders/src/main/java/OrderService.java
1@Service2@RequiredArgsConstructor3public class OrderService {45  private final OrderRepository orders;6  private final OutboxRepository outbox;78  @Transactional9  public Order place(PlaceOrder cmd) {10    var order = orders.save(Order.from(cmd));1112    // Same transaction: the event exists if and only if the order does.13    outbox.save(OutboxEvent.of(14        "order.placed",15        order.getId(),16        new OrderPlaced(order.getId(), order.total(), cmd.idempotencyKey())));1718    return order;19  }20}

What I learned