skip to content

Distributed E-Commerce Platform

[completed]github ↗
  • Spring Boot3.5.14
  • Microservices
  • Java
  • RabbitMQ
  • PostgreSQL
  • React^19.2.5
  • Docker
  • AI

Project Overview

The final case for the Patika.dev x n11 Spring Boot bootcamp: a Turkish e-commerce platform modelled on n11.com, built not as one app but as a 13-service microservice ecosystem. It covers the full path from browsing a catalog to placing an order, taking a real payment, and chatting with an AI shopping assistant — end to end, all running locally on docker-compose.

I built it in structured phases with a verification gate between each, which is the only way a system this size stayed tractable for one person. There's a narrative write-up of the whole journey in the blog post; this page is the condensed technical reference.

Stack

  • Language / framework: Java 21, Spring Boot 3.5, Spring Cloud 2025.0 (Eureka discovery, Config Server, API Gateway)
  • Data: PostgreSQL 16 + pgvector — schema-per-service, one DB user per service with a cross-schema deny matrix
  • Messaging: RabbitMQ — the backbone of the event-driven order flow
  • AI: Google Gemini (chat + embeddings) behind a provider-agnostic ChatProvider port, plus a Model Context Protocol (MCP) server for external agents
  • Frontend: React 19 + Vite 8 + Tailwind 4 — a fully Turkish storefront
  • Build / deploy: Gradle + Jib (image-per-service), docker-compose, GitHub Actions CI

Architecture

1. Edge gateway, internal trust

Only the API Gateway is exposed. It validates the RS256 JWT (issued by identity-service, keys served via JWKS), strips the Authorization header, and injects a trusted X-User-Id downstream — so no internal service ever re-parses a token. Every service owns its own database schema and reaches others only over the network or via events.

2. The order SAGA

Because a cross-service order can't be one ACID transaction, ordering is a choreography SAGA over RabbitMQ: OrderCreated → StockReserved → PaymentCompleted → OrderConfirmed, with a compensation path (PaymentFailed → StockReleased → OrderCancelled) that unwinds reserved stock. Correctness rests on two patterns — a transactional outbox (write the row and the event in one DB commit, so no dual-write race) and an idempotency inbox (each consumer records processed event IDs, so at-least-once redelivery is harmless).

3. Real payments

payment-service integrates the Iyzico sandbox Checkout Form with 3D Secure. Because the provider's callback can't reach localhost, a Cloudflare Tunnel exposes a public HTTPS URL; a scheduled job sweeps up orders stuck mid-payment so reserved stock is eventually released.

4. One toolset, two surfaces

The AI assistant is Gemini with function-calling over a shared agent-toolset module (search_products, add_to_cart, create_order, …). That same module is imported by both the in-app ai-service (SSE-streamed chat) and a standalone MCP server that exposes the identical capabilities to external agents like Claude Desktop — zero duplicated tool definitions. Underneath, the rest of the system talks only to a ChatProvider interface, so swapping the model is a one-adapter change.

What I Learned

  • Microservices don't remove complexity, they trade it: you buy independent, well-bounded services at the cost of distributed correctness — outboxes, idempotency keys, and compensation logic. For a project whose whole point was distributed systems, the trade was worth it.
  • A huge share of real backend work is just carefully accounting for the moment a message arrives twice or a service dies mid-flow.
  • Designing the AI tools as one shared module — rather than wiring Gemini in directly — is what made exposing the same capabilities over MCP almost free.