Microservices Communication Project — Build a Complete Distributed System
In this tutorial, you will learn about Microservices Communication Project. We cover key concepts, practical examples, and best practices to help you master this topic.
This capstone project combines all microservices communication patterns into a production-ready distributed system: REST and gRPC services, async message queues, event bus, service discovery, circuit breakers, distributed tracing, and saga coordination.
What You'll Learn
By the end of this project you will design and implement a complete microservices communication architecture, integrate multiple communication patterns in a single system, handle real-world failure scenarios, and deploy services with proper observability.
Why It Matters
Real microservices systems rarely use a single communication pattern. They combine sync and async, REST and gRPC, queues and event buses. This project teaches you to design integrated communication architectures that handle the complexity of production systems.
Real-World Use
This project mirrors DodaZIP's production architecture: an API gateway with circuit breakers, gRPC for internal real-time communication, RabbitMQ for job queues, an event bus for domain events, service discovery via Consul, and OpenTelemetry tracing across all services.
flowchart TB
subgraph "Project Architecture"
A[API Gateway] -->|REST| B[User Service]
A -->|REST| C[Order Service]
B -->|gRPC| D[Payment Service]
C -->|Async| E[(RabbitMQ)]
E --> F[Worker Pool]
C -->|Event Bus| G[Notification Service]
C -->|Event Bus| H[Analytics Service]
I[Consul] -.->|Service Discovery| A
J[Jaeger] -.->|Tracing| A
J -.->|Tracing| B
J -.->|Tracing| C
end
Project Overview
The system you will build.
# project_overview.py
# Project architecture overview
def project_overview():
print("Microservices Communication Project Overview")
print("=" * 45)
print()
print("Services:")
print(" 1. API Gateway (REST) - Single entry point with circuit breakers")
print(" 2. User Service (REST + gRPC) - User management")
print(" 3. Order Service (REST + Events) - Order processing")
print(" 4. Payment Service (gRPC) - Payment processing")
print(" 5. Worker Service (RabbitMQ) - Background jobs")
print(" 6. Notification Service (Event Bus) - Email/SMS")
print(" 7. Analytics Service (Event Bus) - Usage tracking")
print()
print("Infrastructure:")
print(" - Consul: Service discovery + health checks")
print(" - RabbitMQ: Job queues + event bus")
print(" - Jaeger: Distributed tracing")
print(" - PostgreSQL: Service databases")
print()
print("Communication Patterns Used:")
print(" - REST: Gateway -> User, Gateway -> Order")
print(" - gRPC: User -> Payment (unary)")
print(" - RabbitMQ: Order -> Worker (work queue)")
print(" - Event Bus: Order -> Notification, Analytics")
print(" - Service Discovery: All services register in Consul")
project_overview()
Service Implementation
Detailed service implementations.
# service_implementations.py
# Service implementations
def service_implementations():
print("Service Implementation Details")
print("=" * 45)
print()
gateway_code = """
# API Gateway Service
# Communication: REST inbound, REST/gRPC outbound
# Patterns: Circuit breaker, service discovery, rate limiting
class OrderGateway:
def __init__(self, consul_client, tracer):
self.discovery = ServiceDiscoveryClient(consul_client)
self.circuit_breaker = CircuitBreaker(failure_threshold=5)
self.tracer = tracer
def get_order_details(self, request):
with self.tracer.start_span("gateway.get_order"):
# Discover order service via Consul
order_url = self.discovery.discover("order-service")
# Call with circuit breaker
def call_order():
return httpx.get(f"{order_url}/orders/{request.order_id}")
order_response = self.circuit_breaker.call(
call_order,
fallback=lambda: {"status": "degraded", "data": None}
)
return order_response
"""
print("API Gateway:")
print(gateway_code)
order_code = """
# Order Service
# Communication: REST inbound, RabbitMQ outbound, Event Bus outbound
# Patterns: CQRS, event-driven, saga participant
class OrderService:
def __init__(self, rabbitmq, event_bus, db):
self.rabbitmq = rabbitmq
self.event_bus = event_bus
self.db = db
def create_order(self, order_data, trace_context):
# 1. Save order to database
order = self.db.save_order(order_data)
# 2. Publish job to RabbitMQ for payment processing
self.rabbitmq.publish("payment.jobs", {
"order_id": order.id,
"amount": order.total,
"trace_id": trace_context.trace_id
})
# 3. Publish event to event bus for notification
self.event_bus.publish(Event(
topic="order.events",
type="order.created",
data={"order_id": order.id, "customer_id": order.customer_id},
trace_id=trace_context.trace_id
))
return order
"""
print("Order Service:")
print(order_code)
service_implementations()
Infrastructure Setup
Docker Compose configuration for the project.
# infrastructure.py
# Infrastructure setup
def infrastructure():
print("Infrastructure Setup (Docker Compose)")
print("=" * 45)
print()
docker_compose = """
version: '3.8'
services:
consul:
image: consul:1.15
ports:
- "8500:8500"
command: agent -dev -client=0.0.0.0
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
jaeger:
image: jaegertracing/all-in-one:1.50
ports:
- "16686:16686" # UI
- "4317:4317" # OTLP gRPC
postgres:
image: postgres:15
environment:
POSTGRES_USER: micro
POSTGRES_PASSWORD: micro_pass
POSTGRES_DB: microservices
user-service:
build: ./services/user
depends_on: [consul, postgres, jaeger]
environment:
CONSUL_HOST: consul
JAEGER_HOST: jaeger
DATABASE_URL: postgresql://micro:micro_pass@postgres/microservices
order-service:
build: ./services/order
depends_on: [consul, rabbitmq, postgres, jaeger]
environment:
CONSUL_HOST: consul
RABBITMQ_HOST: rabbitmq
JAEGER_HOST: jaeger
payment-service:
build: ./services/payment
depends_on: [consul, postgres, jaeger]
worker-service:
build: ./services/worker
depends_on: [rabbitmq, jaeger]
deploy:
replicas: 3 # Scale workers independently
notification-service:
build: ./services/notification
depends_on: [rabbitmq, jaeger]
api-gateway:
build: ./services/gateway
ports:
- "8080:8080"
depends_on: [consul, jaeger]
"""
print(docker_compose)
infrastructure()
Deployment and Testing
Running and verifying the system.
# deployment_testing.py
# Deployment and testing
def deployment_testing():
print("Deployment and Testing")
print("=" * 45)
print()
test_code = """
# 1. Start infrastructure
docker-compose up -d consul rabbitmq jaeger postgres
# 2. Build and start services
docker-compose up -d --build
# 3. Verify service discovery (Consul UI)
curl http://localhost:8500/v1/catalog/services
# Expected: ["api-gateway", "user-service", "order-service",
# "payment-service", "worker-service", "notification-service"]
# 4. Test REST endpoint through gateway
curl -X POST http://localhost:8080/api/orders \\
-H "Content-Type: application/json" \\
-d '{"user_id": "u1", "items": [{"product": "p1", "qty": 2}], "total": 49.99}'
# Expected: {"order_id": "ord-1", "status": "created", "trace_id": "..."}
# 5. Verify RabbitMQ queues
curl http://localhost:15672/api/queues
# Expected: payment.jobs queue with messages
# 6. Verify tracing in Jaeger
# Open http://localhost:16686 and search for trace "POST /api/orders"
# Expected: spans across gateway, order, payment, worker
# 7. Test circuit breaker
# Stop payment-service and make 5+ requests
# Expected: 5th request returns degraded response,
# subsequent requests fail fast
# 8. Test service discovery recovery
docker-compose stop payment-service
# Wait 30s for health check failure
# Expected: payment-service removed from Consul
docker-compose start payment-service
# Wait 10s for health check success
# Expected: payment-service re-registered in Consul
"""
print("Testing Commands:")
print(test_code)
deployment_testing()
Common Mistakes
Building all services at once: Start with one service and its communication channel. Verify it works end-to-end before adding more services.
Ignoring service discovery in development: Using hardcoded IPs in development creates surprises in production. Use service discovery everywhere, including local Docker environments.
Not testing failure scenarios: A system that only works when everything is healthy will fail in production. Test circuit breaker openings, service crashes, and broker outages.
Missing observability from the start: Add distributed tracing, metrics, and structured logging to every service from day one. Debugging a microservices system without observability is impossible.
Skipping Contract Testing: Changes to one service's API break other services. Implement contract tests (Pact, Spring Cloud Contract) to catch breaking changes during CI.
Practice Questions
What is the most important infrastructure component for service discovery? Consul (or similar registry). All services register here, and the gateway queries it to find service instances.
Why use both RabbitMQ and an event bus in the same project? RabbitMQ handles work queues (one job to one worker). The event bus handles pub/sub (one event to many consumers). They serve different purposes.
What is the role of the API gateway in this project? Single entry point for external requests, circuit breaker protection, service discovery integration, and request routing.
How does Jaeger help operate this system? Jaeger traces requests across all services, showing where time is spent and which services fail, making performance debugging possible.
Challenge: Extend this project with a choreography saga for the order lifecycle. Define events for OrderCreated, PaymentProcessed, InventoryReserved, OrderShipped. Implement compensating events for PaymentFailed and OutOfStock scenarios.
FAQ
Mini Project
This entire lesson is the mini project. Build the complete system described above, deploy it with Docker Compose, and verify all communication patterns work. Create a test suite that exercises each pattern: REST calls through the gateway, gRPC calls for payment, RabbitMQ jobs for workers, event bus for notifications, circuit breaker failure/recovery, and distributed tracing visualization in Jaeger.
def project_completion():
print("Project Completion Checklist")
print("=" * 45)
print()
print("[ ] All 7 services implemented")
print("[ ] Docker Compose deploys all services")
print("[ ] Consul shows all registered services")
print("[ ] REST endpoint works through gateway")
print("[ ] gRPC payment call succeeds")
print("[ ] RabbitMQ worker processes jobs")
print("[ ] Event bus delivers to notification + analytics")
print("[ ] Circuit breaker opens on payment failure")
print("[ ] Circuit breaker recovers when payment restarts")
print("[ ] Jaeger shows complete traces across services")
print("[ ] Service deregisters on crash in Consul")
print("[ ] Service re-registers on recovery in Consul")
print("[ ] Health endpoints respond on all services")
print()
print("Bonus:")
print("[ ] Saga pattern for order lifecycle")
print("[ ] Contract tests between services")
print("[ ] Rate limiting on API gateway")
print("[ ] Structured logging with trace IDs")
project_completion()
What's Next
Next: Serverless Tutorials for Serverless architecture patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro