Distributed E-Commerce Platform
- 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
ChatProviderport, 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.