Event-Driven Order Processing Platform

Python · Django · PostgreSQL · RabbitMQ · Docker · Kubernetes

An e-commerce order flow split into independent Order, Stock and Payment services. Services communicate asynchronously through RabbitMQ, coordinated with the Saga pattern and made reliable with the Outbox Pattern.

3Django services
2Runtime targets (Docker / K8s)
SagaOrder orchestration
OutboxReliable publishing

Overview

The platform models a realistic e-commerce backend: an Order service that owns orders, a Stock service that owns inventory, and a Payment service that owns payments. They don't call each other directly — they exchange messages over RabbitMQ, so each service can evolve and scale independently. A Kong API gateway fronts the services, and a small Next.js demo app exercises the flow.

Problem

Distributed systems introduce problems monoliths don't have: how do you keep multiple services consistent when a request spans several of them? What happens if a service is down mid-order? And how do you publish events without risking the classic dual-write problem — writing to the database and the broker, where one can succeed and the other fail? This project implements the standard answers — Saga orchestration and the Outbox Pattern — rather than hand-waving around them.

Architecture

Every service has its own PostgreSQL database. The whole stack runs on Docker Compose for development, with a Kubernetes (k3d + Helm) path also provided.

Outbox Pattern

When an order is created, the service must both save the order and publish an event. Doing these as two separate operations is the dual-write hazard.

The Outbox Pattern solves it by writing the event record into the same database transaction as the business data:

Application state and the pending event are committed atomically. A publisher then reads and forwards those records to RabbitMQ — the two writes are now safe because the second one is derived from the first.

Saga Pattern

Completing an order touches three services. The Saga pattern coordinates the steps as a sequence of local transactions with compensating actions for earlier steps if a later one fails:

If stock is unavailable or a payment is rejected, compensating messages roll back the earlier steps — the flow was verified with exactly these failure cases.

Technology Stack

LayerTechnology
ServicesOrder, Stock, Payment — Django + PostgreSQL each
MessagingRabbitMQ with outbox-based publishing (django-outbox-pattern)
GatewayKong API gateway
RuntimeDocker Compose or Kubernetes (k3d + Helm)
Demo frontendNext.js app in web/

Key Engineering Decisions

  • Service-per-context databases — Order, Stock and Payment own their data; no shared schema dependencies.
  • Outbox-based publishing — messages are safe by construction instead of relying on retry luck.
  • Saga orchestration over choreography — one coordinator tracks the order lifecycle, which is easier to reason about and test for this flow.
  • API gateway in front — a single entry point for clients instead of exposing three service URLs.
  • Two deployment targets — Docker Compose for fast local iteration, Kubernetes specs for a more production-like path.

Messaging & API Design

Services exchange domain events over RabbitMQ exchanges and queues. The message flow traces the order lifecycle: an order event published from the Outbox is consumed by Stock (reserve) and Payment (charge), whose outcome events drive the next step.

Entry points in the Docker quick-start:

UIlocalhost:15672RabbitMQ management console
APIlocalhost:8080Kong gateway to the services
ADMINlocalhost:8000 / 8001 / 8002Django admin per service
APPweb/Next.js demo flow

Database Design

  • Order service DB — orders and order items; an outbox table records outgoing events in the same transaction.
  • Stock service DB — product inventory; consumes reservations events and publishes outcomes.
  • Payment service DB — payment records; consumes payment requests and publishes results.

Each outbox table is the durability anchor for the system: if RabbitMQ is unavailable, publishers retry from the database — nothing is lost.

Authentication & Security

This is a demonstration system, so the security posture is honest and limited: default admin accounts exist for the Django consoles and RabbitMQ (documented as dev only), all traffic in the quick-start is local, and the Kubernetes path is prepared with Helm values per service. API gateway routing and network policy are the natural next hardening steps.

Testing

Verification focused on the behaviour that matters in an event-driven flow — failure handling:

  • Created an order and confirmed the happy path through Stock and Payment.
  • Simulated unavailable stock — confirmed the order path compensates.
  • Simulated rejected payment — confirmed earlier steps are rolled back.
  • Used Postman to drive the endpoints and RabbitMQ's console to inspect actual message flow between services.

Challenges & Solutions

  • Losing messages on dual-write — solved with the Outbox Pattern: events committed with the data, then published asynchronously.
  • Cross-service consistency — the Saga pattern sequences steps and compensates earlier work when a later step fails.
  • Privately owned data per service — each service keeps its own PostgreSQL database and only shares messages, keeping coupling low.
  • Reproducible environments — Docker Compose startup scripts and a k3d/Helm bootstrap make both runtimes one command.

What I Learned

This project made abstract patterns concrete: I now understand why the Outbox Pattern exists (dual-write is a real hazard, not a theory) and how a Saga keeps a multi-service transaction honest. I also learned to verify distributed behaviour by breaking it on purpose — failing stock and payments in Postman taught me more than the happy path ever did.