Skip to content

Repository files navigation

eventflow

A small event-driven order pipeline: a Django REST API publishes domain events over Redis pub/sub, a standalone listener reacts to them, and the actual work happens on a Celery task queue — so the API stays fast and every downstream step can be added or changed without touching the others.

CI

Architecture

POST /api/orders/
       │
       ▼
 Django REST API ──creates Order (status=pending)──▶ Postgres
       │
       │ post_save signal
       ▼
 Redis pub/sub  ◀── publish("events:orders", order.created)
       │
       ▼
 listen_events (management command, long-running process)
       │
       │ process_new_order.delay(order_id)
       ▼
 Celery worker ──validates/"charges"──▶ Order.status = confirmed/failed
       │
       │ publish(order.confirmed)
       ▼
 Redis pub/sub ──▶ listen_events ──▶ send_confirmation_email.delay(...)

Two distinct patterns are deliberately kept separate:

  • Pub/sub (Redis) — decouples "something happened" from "who cares". The API and the Celery tasks never import each other; listen_events is the only place that maps event → reaction, so adding a new reaction to order.confirmed never touches the API or the payment task.
  • Task queue (Celery) — does the actual (simulated) work, with retries and a dedicated worker pool, instead of blocking the pub/sub listener.

Running it

cp .env.example .env
docker compose up --build

This starts Postgres, Redis, the Django API (:8000), a Celery worker, and the event listener as separate containers — mirroring how you'd actually deploy each piece independently.

Check that the API can actually reach Postgres and Redis:

curl http://localhost:8000/api/health/

Create an order:

curl -X POST http://localhost:8000/api/orders/ \
  -H "Content-Type: application/json" \
  -d '{"customer_email": "a@example.com", "amount_cents": 5000}'

Watch docker compose logs -f listener worker to see the event flow from order.created → payment task → order.confirmed → notification task.

Orders with amount_cents < 100 are treated as a simulated payment decline, so you can exercise the order.failed path without a real payment gateway.

Local development (without Docker)

python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
cp .env.example .env

python manage.py migrate
python manage.py runserver          # terminal 1
celery -A config worker -l info     # terminal 2
python manage.py listen_events      # terminal 3

Tests

ruff check orders config
pytest -v

CI runs the same commands against real Postgres and Redis service containers (see .github/workflows/ci.yml) — no mocking the database.

Why this shape

Most demo backends either do everything in the request/response cycle, or reach for a task queue without a clear story for what decides to enqueue what. This project's whole point is to keep those two concerns visibly separate — you can read orders/events.py, orders/tasks.py, and orders/management/commands/listen_events.py independently and understand the full system.

License

MIT

About

Event-driven order pipeline — Django REST API + Redis pub/sub + Celery task queue, with a listener that bridges events to async tasks

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages