Back to Home
Completed
4 Months

Modular Mart.

A cloud-native, microservices-based multi-vendor e-commerce platform designed with a focus on domain isolation, event-driven choreography, and deep observability.

Choreographed SagaTransactional OutboxDatabase-per-ServiceEvent-Driven ArchitectureIdempotent ConsumersDead Letter QueuesDistributed TracingCircuit Breakers
ArchitectureMicroservices
API GatewayKong
Event BusRabbitMQ
DatabasesIsolated PostgreSQL
ObservabilityLGTM Stack
AuthClerk

Overview

Modular Mart is a highly scalable, multi-vendor e-commerce platform. It evolved from a single-vendor monolithic structure into a fully distributed microservices architecture to support growing traffic and vendor requirements.

Key focus areas:

  • Domain Isolation: Dedicated services for Users, Catalog, Orders, Payments, and Notifications.
  • Event-Driven Choreography: Asynchronous saga patterns for complex workflows like checkout.
  • Deep Observability: Comprehensive tracing, logging, and metrics using the LGTM stack.
Interactive Topology

System Architecture & Infrastructure Topology

Explore the distributed microservices architecture: Kong Gateway, 5 isolated domain services with dedicated PostgreSQL databases, RabbitMQ event bus, and the LGTM observability stack.

Click to interact & explore topology

Checkout Saga Orchestration

Visualizing the distributed transaction flow. This choreography ensures inventory is reserved before payment is processed, with built-in failure compensation.

HTTP / RPC
Event Driven

Observability Architecture

How telemetry data is aggregated from services and pushed to the LGTM stack for professional visualization and debugging.

Domain & Bounded Contexts

Illustrating business boundaries and service ownership based on Domain-Driven Design principles.


Key Architectural Decisions

Why RabbitMQ?

We chose RabbitMQ to implement asynchronous, event-driven communication. This decouples services, allowing the system to remain responsive even if some downstream services (like Notifications) are temporarily unavailable. It also enables the Choreographed Saga pattern for reliable distributed transactions.

Why Database-per-Service?

Each microservice owns its own PostgreSQL database. This ensures strong service ownership and autonomy, prevents accidental coupling through shared tables, and allows each service to scale its persistence layer independently based on its specific load profile.

Why Choreographed Saga?

To manage distributed transactions (like Checkout) without the complexity of a central orchestrator or the overhead of distributed locks. Services listen for events and react accordingly, including executing compensating transactions to maintain eventual consistency if a step fails.

Why Kong Gateway?

Kong acts as our centralized entry point, handling cross-cutting concerns like JWT validation (via Clerk), rate limiting, and request routing. This keeps our service logic clean and focused solely on business domains.


Trade-offs

Pros

  • Independent Deployment: Each service can be updated and scaled without affecting the rest of the system.
  • Scalability: High-traffic domains like Catalog can be scaled independently of low-traffic ones like Payments.
  • Fault Isolation: A failure in the Notification service does not prevent a user from placing an order.

Cons

  • Eventual Consistency: The system must handle temporary inconsistencies during cross-service workflows.
  • Operational Complexity: Managing multiple services, databases, and a message broker requires robust DevOps and automation.
  • Observability Overhead: Distributed tracing is mandatory to understand requests that span multiple service boundaries.

Impact & Results

99.9%Reliability

Fault-tolerant design with transactional outbox and compensating transactions.

100%Traceability

End-to-end distributed tracing across HTTP and AMQP.

Exactly-OnceConsistency

Idempotent consumers guarantee safe event processing.


Features

Choreographed Saga

Checkout handled via an asynchronous saga combined with the Transactional Outbox Pattern for atomic operations.

Microservice Chassis

Standardized behavior across services including logging (nestjs-pino), tracing, and health checks.

Exactly-Once Processing

Idempotent event consumers using a processed_messages table to guarantee reliable side effects.

Full-Stack Observability

Deep system visibility using the LGTM stack (Loki, Grafana, Prometheus, Jaeger).


Technical Challenges

Distributed Transaction Management

The Problem

Ensuring data consistency across multiple services (Orders, Catalog, Payments) during checkout without distributed locks.

The Solution

Implemented an asynchronous Choreographed Saga pattern with a Transactional Outbox. Events are published reliably, and compensating transactions handle failure rollbacks (e.g., releasing reserved stock if payment fails).

Result: Atomic, eventually consistent transactions with zero data loss or ghost orders.

Event Processing Reliability

The Problem

RabbitMQ guarantees at-least-once delivery, which can lead to duplicate event processing and inconsistent state.

The Solution

Designed idempotent consumers utilizing a dedicated processed_messages table to track and deduplicate incoming events.

Result: Guaranteed exactly-once side effects, ensuring reliable inventory updates and payment status transitions.

API Reference

All traffic enters through Kong Gateway (JWT via Clerk, rate limiting, routing) and fans out to domain services. Event names are the versioned contracts from @modular-mart/contracts (EVENT_PATTERNS).


Domain Events

The checkout saga and cross-service fan-out run on RabbitMQ. Consumers are idempotent (processed_messages table) with dead-letter routing on repeated failure.

EVENTstock.reserve.requested.v1

Order → Catalog (saga step 1). Published via transactional outbox when an order is created.

EVENTstock.reserved.v1

Catalog → Order (saga step 2). Pessimistic inventory lock succeeded; order advances to payment.

EVENTstock.reserve.failed.v1

Catalog → Order. Insufficient stock; order transitions to STOCK_FAILED, customer is notified.

EVENTpayment.succeeded.v1

Payment → Order + Notifications. Stripe webhook confirmed; order finalizes to PAID.

EVENTpayment.failed.v1

Payment → Order + Catalog. Triggers compensating transaction that releases reserved stock.

EVENTstock.released.v1

Catalog → Order. Compensation acknowledged; saga closes without ghost inventory.

EVENTorder.status_updated.v1

Order → Notifications. Powers the live notification feed (approved, shipped, delivered).

EVENTuser.created.v1

User → Notifications. Welcome flow and read-model sync for new accounts.


Resiliency & Reliability

OptimizationResult
Dead Letter QueuesActive (Auto-routing)
Retry StrategyExponential Backoff
Inventory LockPessimistic Locking
Compensating LogicAutomated Rollbacks

Lessons Learned

  • Distributed Systems: Managing eventual consistency and saga coordination across service boundaries.
  • Infrastructure as Code: Orchestrating complex environments with Docker and Turborepo.
  • Observability: The critical importance of centralized logging and distributed tracing for debugging microservices.
  • Idempotency: Designing resilient event handlers that safely process duplicate messages.

Screenshots

Liked this project?

Check out more of my work or get in touch.