Skip to content

Repository files navigation

Event-driven Commerce Platform

Backend-first reference system for order processing with explicit reliability boundaries. The project demonstrates a modular monolith whose external behavior is driven by REST commands and durable domain events.

What this proves

  • Transactional order creation with atomic stock reservation.
  • Transactional Outbox for reliable publication to RabbitMQ.
  • Idempotency keys for safe command retries.
  • Retry with exponential backoff and a dead-letter state for poison events.
  • Simulated payment, compensation and order state transitions.
  • PostgreSQL for transactional data, Redis for catalog caching, RabbitMQ for delivery and MongoDB for document-shaped audit history.
  • Structured logs, correlation IDs propagated through events, Prometheus metrics and readiness checks.
  • Unit tests that run without infrastructure and integration tests that run against the Docker Compose services.
  • A small React admin console that exercises the real API; the UI is intentionally secondary to the backend.

Architecture

React admin ──REST──> Fastify API ──transaction──> PostgreSQL
                         │                            │
                         │                            └── outbox_events
                         ├── Redis catalog cache
                         └── JWT/RBAC

Worker ──poll──> PostgreSQL outbox ──publish──> RabbitMQ ──consume──> MongoDB audit
                                      │
                                      └── retry / dead-letter after five attempts

The API and worker are separate processes from the start, but the domain modules live in one repository. This keeps local development reproducible while preserving a clear extraction seam if operational load later justifies service decomposition.

Quick start

Requirements: Node.js 22+, Docker and Docker Compose.

cp .env.example .env
npm install
docker compose up -d postgres redis rabbitmq mongo
npm run dev:api

In another terminal:

npm run dev:worker
npm run dev:admin

The API is available at http://localhost:3000, the admin console at http://localhost:5173, RabbitMQ management at http://localhost:15672 and metrics at http://localhost:3000/metrics.

The default local credentials are the values from .env.example. Change them before exposing the system outside a local environment.

API walkthrough

# Authenticate
TOKEN=$(curl -s http://localhost:3000/auth/login \
  -H 'content-type: application/json' \
  -d '{"email":"admin@example.com","password":"change-me-now"}' | jq -r .accessToken)

# Create a product
PRODUCT=$(curl -s http://localhost:3000/products \
  -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{"sku":"keyboard-01","name":"Mechanical keyboard","priceCents":12990,"stock":10}' | jq -r .id)

# Create an idempotent order; replaying the same request returns the same order
curl -s http://localhost:3000/orders \
  -H "authorization: Bearer $TOKEN" \
  -H 'idempotency-key: demo-order-001' \
  -H 'content-type: application/json' \
  -d "{\"items\":[{\"productId\":\"$PRODUCT\",\"quantity\":1}]}"

Use POST /orders/:id/payment with {"approved":true} to simulate payment capture, and POST /orders/:id/fulfill after the order is paid. Audit records become visible through GET /audit?orderId=... after the worker consumes the event.

Commands

npm run lint
npm run typecheck
npm test
npm run build
npm run test:integration --workspace @commerce/api

Integration tests require the Compose dependencies and exercise the running API, worker, PostgreSQL, RabbitMQ and MongoDB. The CI workflow starts those dependencies and the application before running the integration suite.

Reliability notes

  • Stock reservation is performed in the same PostgreSQL transaction as the order and outbox insert. A failed payment compensates the reservation in the same transaction as the status transition.
  • A successful database transaction is not enough to guarantee message delivery, so the worker publishes from outbox_events and records the publication result separately.
  • RabbitMQ delivery is at-least-once. Consumers must be idempotent; the audit consumer uses the event ID as a unique MongoDB key.
  • Idempotency keys are scoped by authenticated subject and command name. They are retained indefinitely in this demonstration; a production deployment should add a retention policy based on business replay requirements.
  • Redis is a cache, never the source of truth. API behavior remains correct when the cache is unavailable.

ADRs

License

MIT. This is a portfolio project intended for study and technical discussion.

About

Backend-first event-driven commerce platform with PostgreSQL, RabbitMQ, Redis, MongoDB, outbox and idempotency.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages