Overview
At Floures Technologies, we've been designing a Distributed System Design (DSD) platform — a microservices architecture with Kong API Gateway, 7 backend services, and supporting infrastructure running on Kubernetes (Docker Desktop).
This post walks through the full system architecture, design decisions, and integration patterns we've adopted.
Architecture Overview
The system consists of three layers:
- Kong API Gateway — single entry point for all external traffic
- Backend Services — 7 Go microservices with ClusterIP (internal only)
- Infrastructure — PostgreSQL, MongoDB, Kafka, Redis via Docker Compose
Components
Kong API Gateway
Single entry point for all external traffic. Deployed as Kong Ingress Controller (KIC) in K8s.
| Port | Purpose |
|---|---|
| 8000 | Proxy (client requests) |
| 8001 | Admin API (management) |
| 8443 | Proxy SSL |
| 8444 | Admin API SSL |
Plugins:
- CORS — cross-origin resource sharing
- Rate Limiting — per-consumer, per-route
- JWT Authentication — token validation
- Prometheus — metrics export
- Request/Response Logging
Services
All services are Go 1.22, deployed as K8s Deployments with ClusterIP (internal only).
| Service | Port | Database | Messaging | Purpose |
|---|---|---|---|---|
| inventory-service | 8080 | PostgreSQL | Redis (cache) | Product inventory management |
| order-service | 8080 | PostgreSQL (write) | Kafka (CDC consumer) | Order processing (CQRS pattern) |
| payment-service | 8080 | PostgreSQL | — | Payment handling |
| notification-service | 8080 | — | Kafka (consumer) | Notifications delivery |
| query-service | 8080 | MongoDB | — | Read-optimized queries (CQRS) |
| product-catalog-service | 8080 | PostgreSQL | Redis (cache) | Product catalog & SKU master data |
| promotion-service | 8080 | PostgreSQL | — | Promotion management & validation |
Note: Order Service writes to PostgreSQL only. MongoDB read model is populated via CDC (Debezium → Kafka → CDC Consumer).
Infrastructure
Managed via Docker Compose, running alongside K8s.
| Component | Image | Port | Purpose |
|---|---|---|---|
| PostgreSQL | postgres:16-alpine | 5432 | Primary relational database (wal_level=logical) |
| MongoDB | mongo:7 | 27017 | Document store for read models |
| Kafka | confluentinc/cp-kafka:7.6.1 | 9092 | Event streaming |
| Zookeeper | confluentinc/cp-zookeeper:7.6.1 | 2181 | Kafka coordination |
| Kafka Connect (Debezium) | debezium/connect:2.5 | 8083 | CDC — captures Postgres WAL changes |
| Kafka UI | provectuslabs/kafka-ui | 8084 | Kafka topic & connector monitoring |
| Redis | redis:7-alpine | 6379 | Caching layer |
Request Flow
Client
→ localhost:8000/api/inventory/items
→ Kong Proxy
→ Plugin chain: JWT Auth → Rate Limit → CORS → Logging
→ Route match: /api/inventory/* → inventory-service
→ inventory-service:8080/items (ClusterIP, internal)
→ Response back through Kong
Route Mapping
| External Path | Internal Service | Internal Path |
|---|---|---|
| /api/inventory/* | inventory-service:8080 | /* |
| /api/orders/* | order-service:8080 | /* |
| /api/payments/* | payment-service:8080 | /* |
| /api/notifications/* | notification-service:8080 | /* |
| /api/queries/* | query-service:8080 | /* |
| /api/products/* | product-catalog-service:8080 | /* |
| /api/promotions/* | promotion-service:8080 | /* |
Service Integration Patterns
Order → Product Catalog
Order Service calls Product Catalog Service during order creation to snapshot product data:
GET /api/products/{sku}
Response: { sku, name, description, price, image_url, inventory_count }
Pattern: Request-Response (Synchronous) Rationale: Product snapshot captured at order time for historical accuracy
Order → Promotion
Order Service calls Promotion Service to validate and apply discounts:
1. POST /api/promotions/validate
{ promotion_code, customer_id, items[] }
→ { valid, discount_amount, promotion_id }
2. POST /api/promotions/usage
{ promotion_id, order_id, customer_id }
→ { success, usage_count }
Pattern: Request-Response (Synchronous) Rationale: Promotion validation must complete before order confirmation
Postgres → MongoDB (CDC Pipeline)
Order data synchronization from Postgres (write model) to MongoDB (read model) using Change Data Capture:
Order Service
↓ writes
PostgreSQL (wal_level=logical)
↓ WAL (Write-Ahead Log)
Debezium Connector (Kafka Connect :8083)
↓ captures INSERT/UPDATE/DELETE
Kafka Topics:
├─ dsd.public.orders
├─ dsd.public.order_items
└─ dsd.public.order_promotions
↓ consumed by
CDC Consumer (goroutine in order-service)
↓ denormalizes & upserts
MongoDB (order_views collection)
Pattern: Change Data Capture (Asynchronous, Eventual Consistency) Latency: ~100ms-1s from Postgres write to MongoDB read availability
Design Decisions
Why Namespace Separation?
Kong runs in its own kong namespace, separate from application namespaces (dsd-dev, dsd-staging, dsd-prod).
Rationale:
- Cluster-Wide Scope — Kong routes traffic to services across all namespaces. One Kong instance serves multiple environments.
- Lifecycle Independence — Platform team manages Kong upgrades independently from app deployments.
- RBAC Separation — Platform/ops team gets admin access to
kongnamespace; dev team gets access todsd-*namespaces only. - Resource Isolation — Kong gets dedicated CPU/memory quotas separate from app namespaces.
Kubernetes Cluster (Docker Desktop) ├── kube-system ← K8s core components ├── kong ← API Gateway (platform layer) ├── dsd-dev ← Development environment ├── dsd-staging ← Staging environment └── dsd-prod ← Production environment
Why ClusterIP for All Services?
All backend services use ClusterIP (internal-only), not NodePort or LoadBalancer.
- Security — Services not directly exposed. Single entry point (Kong) simplifies the security model.
- Centralized Traffic Management — All policies (CORS, JWT, rate limits) applied at Kong. No need to duplicate auth logic.
- Service Discovery — K8s DNS resolves
inventory-service:8080within the cluster. No external load balancer per service. - Cost Efficiency — One LoadBalancer (Kong) vs. 7 LoadBalancers. Matters significantly in cloud environments.
Why Docker Compose for Infrastructure?
Databases and messaging run in Docker Compose, not K8s.
- Simplicity — Easier to manage stateful workloads outside K8s.
docker compose down -vclears everything. - Resource Efficiency — Docker Desktop K8s has limited resources. Running 7 services + Kong + all databases in K8s = resource constrained.
- Production Parity — In production, you'd use managed services (RDS, DocumentDB, MSK, ElastiCache), not self-hosted. Docker Compose simulates "external managed services."
- Faster Iteration — Restart Postgres without restarting K8s pods. Clear separation of concerns.
Technology Stack
| Layer | Technology | Version |
|---|---|---|
| Language | Go | 1.22 |
| API Gateway | Kong Ingress Controller | Latest |
| Container Orchestration | Kubernetes (Docker Desktop) | v1.31+ |
| Database (relational) | PostgreSQL | 16-alpine |
| Database (document) | MongoDB | 7 |
| Messaging | Kafka + Zookeeper | 7.6.1 |
| Cache | Redis | 7-alpine |
| Build Tool | Docker multi-stage | - |
| IaC | Kustomize | - |
Service Dependency Map
Client ↓ Kong API Gateway ↓ ├─→ inventory-service → PostgreSQL, Redis ├─→ order-service → PostgreSQL, MongoDB, Kafka │ ├─→ product-catalog-service (snapshot) │ ├─→ promotion-service (validate) │ └─→ tax-service (external API) ├─→ payment-service → PostgreSQL ├─→ notification-service → Kafka ├─→ query-service → MongoDB ├─→ product-catalog-service → PostgreSQL, Redis └─→ promotion-service → PostgreSQL
Implementation Status
| Service | Status | Notes |
|---|---|---|
| inventory-service | ✅ Implemented | Basic CRUD operations |
| order-service | 🟡 In Planning | Architecture designed |
| payment-service | ✅ Implemented | Basic payment processing |
| notification-service | ✅ Implemented | Kafka consumer |
| query-service | ✅ Implemented | MongoDB read model |
| product-catalog-service | 📋 Planned | Design complete |
| promotion-service | 📋 Planned | Design complete |
What's Next
The current focus is Order Service implementation with the CQRS pattern — writing to PostgreSQL and syncing to MongoDB via the CDC pipeline. This is the most complex service in the system, with synchronous dependencies on Product Catalog, Promotion, and Tax services.
Stay tuned for the follow-up post on CQRS implementation patterns with Go and Kafka.