Microservices Architecture with Docker and Message Queues
Design patterns for building microservices — service decomposition, async communication with RabbitMQ, circuit breakers, distributed tracing, and observability.
Microservices enable independent deployment and scaling. This guide covers patterns Ventra Rocket uses in production microservice systems.
When to Use Microservices
Use microservices when:
- Components have different scaling needs
- Teams need independent deployments
- There are distinct domain boundaries
Start with a monolith and extract services when boundaries become clear.
1. Service Decomposition
E-commerce Platform
├── user-service — authentication, profiles
├── product-service — catalog, inventory
├── order-service — orders, cart
├── payment-service — billing
├── notification-service — email, SMS
└── analytics-service — reporting
Each service:
- Has its own database (database-per-service)
- Exposes a well-defined API
- Can be deployed independently
2. Docker Compose for Local Dev
version: "3.9"
services:
user-service:
build: ./user-service
environment:
- DATABASE_URL=postgres://user-db/users
depends_on:
- user-db
order-service:
build: ./order-service
environment:
- DATABASE_URL=postgres://order-db/orders
- RABBITMQ_URL=amqp://rabbitmq
depends_on:
- order-db
- rabbitmq
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "15672:15672"
3. Async Communication with RabbitMQ
import amqp from "amqplib";
// Publisher — order-service
async function publishOrderCreated(order: Order) {
const conn = await amqp.connect(process.env.RABBITMQ_URL!);
const channel = await conn.createChannel();
await channel.assertExchange("orders", "topic", { durable: true });
channel.publish(
"orders",
"order.created",
Buffer.from(JSON.stringify(order)),
{ persistent: true }
);
}
// Consumer — notification-service
channel.consume("notification.order.created", async (msg) => {
if (!msg) return;
const order = JSON.parse(msg.content.toString());
await sendOrderConfirmationEmail(order);
channel.ack(msg);
});
4. Circuit Breaker Pattern
class CircuitBreaker {
private failures = 0;
private state: "closed" | "open" | "half-open" = "closed";
private nextAttempt = Date.now();
constructor(private threshold = 5, private timeout = 30_000) {}
async call<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === "open" && Date.now() < this.nextAttempt) {
throw new Error("Circuit breaker open");
}
try {
const result = await fn();
this.failures = 0;
this.state = "closed";
return result;
} catch (err) {
this.failures++;
if (this.failures >= this.threshold) {
this.state = "open";
this.nextAttempt = Date.now() + this.timeout;
}
throw err;
}
}
}
5. Distributed Tracing
import { trace, SpanStatusCode } from "@opentelemetry/api";
const tracer = trace.getTracer("order-service");
async function createOrder(userId: string, items: OrderItem[]) {
return tracer.startActiveSpan("createOrder", async (span) => {
span.setAttributes({ "user.id": userId, "items.count": items.length });
try {
const order = await db.orders.create({ userId, items });
await publishOrderCreated(order);
span.setStatus({ code: SpanStatusCode.OK });
return order;
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw err;
} finally {
span.end();
}
});
}
Conclusion
Microservices pay off when domains are large enough to justify the complexity. Use message queues for events, circuit breakers for resilience, distributed tracing for observability. Ventra Rocket has migrated multiple monoliths to microservices, achieving independent deployment cycles.
Related Articles
Kubernetes Deployment Guide for Node.js Applications
Step-by-step guide to deploying Node.js on Kubernetes — Deployments, Services, HPA, health checks, and zero-downtime rollouts.
Docker and CI/CD Pipeline for Next.js Applications
A complete guide to containerising Next.js with Docker multi-stage builds and setting up a fully automated CI/CD pipeline with GitHub Actions for zero-downtime deployments.
CI/CD with GitHub Actions: Build, Test, and Deploy Pipelines
Automating software delivery — build pipelines, testing, Docker image builds, deployment automation, and secrets management with GitHub Actions.