Skip to main content
Microservices: When They Help and When They Hurt

Microservices: When They Help and When They Hurt

Sep 14, 2025

"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

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.

© 2026 Tawan. All rights reserved.