"We should use microservices" is probably the most expensive sentence in software engineering. Ive seen teams turn a working monolith into a distributed nightmare because microservices seemed cool.
Lets talk about when they actually make sense.
The Honest Tradeoffs
Microservices trade simplicity for flexibility. Make sure you need that flexibility.
When Microservices Make Sense
1. Different Scaling Needs
If one part of your system needs 10x the resources, microservices let you scale just that part.
2. Team Independence
Teams can deploy without coordinating. But this requires clear API contracts.
3. Different Tech Requirements
Maybe your ML pipeline needs Python while your API is in Node. Microservices let each service use the right tool.
When to Avoid Microservices
- Small team (< 10 devs) - You dont have enough people to own multiple services
- Startup finding product-market fit - You need to move fast, not manage infrastructure
- Tightly coupled domain - If everything needs to know about everything else, splitting hurts
- No DevOps maturity - You need solid CI/CD, monitoring, tracing first
Key Patterns That Work
1. API Gateway
Single entry point for all clients:
// API Gateway routes
app.use('/api/users', proxy('http://users-service:3000'));
app.use('/api/orders', proxy('http://orders-service:3000'));
app.use('/api/products', proxy('http://products-service:3000'));
2. Service Discovery
Services find each other dynamically:
// Using Consul or Kubernetes DNS
const ordersServiceUrl = await serviceRegistry.discover('orders-service');
const response = await fetch(`${ordersServiceUrl}/orders/${orderId}`);
3. Saga Pattern for Distributed Transactions
When a business process spans multiple services:
async function createOrderSaga(orderData: OrderInput) {
const compensations: Function[] = [];
try {
// Step 1: Create order
const order = await ordersService.create(orderData);
compensations.push(() => ordersService.cancel(order.id));
// Step 2: Charge payment
const payment = await paymentService.charge(order.total);
compensations.push(() => paymentService.refund(payment.id));
// Step 3: Reserve inventory
await inventoryService.reserve(order.items);
return order;
} catch (error) {
// Compensate in reverse order
for (const compensate of compensations.reverse()) {
await compensate();
}
throw error;
}
}
4. Event-Driven Communication
Services communicate through events, not direct calls:
// Orders service publishes
await eventBus.publish('order.created', {
orderId: order.id,
userId: order.userId,
items: order.items,
total: order.total
});
// Inventory service subscribes
eventBus.subscribe('order.created', async (event) => {
await reserveItems(event.items);
});
// Notification service subscribes
eventBus.subscribe('order.created', async (event) => {
await sendOrderConfirmation(event.userId, event.orderId);
});
The Database Question
Shared database: Simpler, but couples services Database per service: More isolation, harder consistency
Start with shared if youre new to microservices. Split later when you understand the boundaries.
Observability Is Non-Negotiable
You cant debug distributed systems without:
// Distributed tracing
const span = tracer.startSpan('process-order');
span.setTag('order.id', orderId);
try {
// Call other services with trace context
await paymentService.charge(amount, span.context());
span.finish();
} catch (error) {
span.setTag('error', true);
span.log({ event: 'error', message: error.message });
span.finish();
throw error;
}
Essential tools:
- Tracing: Jaeger, Zipkin, Datadog
- Logging: ELK stack, Loki
- Metrics: Prometheus, Grafana
Quick Checklist
Before going microservices:
- [ ] Clear service boundaries identified
- [ ] Teams assigned to own each service
- [ ] CI/CD pipeline can handle multiple deploys
- [ ] Monitoring and tracing in place
- [ ] Team understands distributed systems challenges
- [ ] Accepted the operational complexity
Further Reading
- Building Microservices by Sam Newman
- Microservices Patterns by Chris Richardson
- monolith-first by Martin Fowler
Start with a monolith. Extract services when you feel the pain. Most teams that started with microservices wish they hadnt. Most teams that migrated from a monolith are glad they waited.
