The real cost of a microservice
The standard pitch for microservices is organizational agility: independent deployability, autonomous team ownership, and isolated fault domains.
What architectural conference talks rarely mention is that introducing a network boundary between two pieces of logic replaces simple in-memory function calls with the full taxonomy of distributed systems failures: network timeouts, serialization overhead, eventual consistency races, and cascading retry storms.
The latency tax of the network hop
An in-process method call takes under 10 nanoseconds. An inter-service RPC over gRPC/HTTP within the same AWS availability zone takes 1.5 to 3.5 milliseconds—a \(300,000\times\) latency penalty.
When a single user request fans out into a call graph with a depth of 6 services, your latency budget is consumed entirely by TCP handshakes, TLS termination, and JSON/Protobuf marshaling.
// Transactional Outbox Pattern in Go: Atomic state + event persistence
func (s *OrderService) CreateOrder(ctx context.Context, order Order) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
// 1. Insert domain state
if err := s.insertOrder(tx, order); err != nil {
return err
}
// 2. Insert outbox event in identical local ACID transaction
outboxPayload, _ := json.Marshal(order)
if err := s.insertOutbox(tx, "OrderCreated", outboxPayload); err != nil {
return err
}
return tx.Commit()
}
Every microservice boundary you draw is a distributed transaction problem you have chosen to solve with asynchronous message queues.
The death of ACID: Sagas and Outbox patterns
In a monolith, transferring money or updating an inventory balance is an atomic SQL transaction: BEGIN; ... COMMIT;.
If the server crashes mid-flight, the database engine guarantees zero data corruption.
Once inventory and billing are split into separate services with separate databases, atomic transactions are impossible without slow two-phase commit (2PC) locks. You are forced to implement:
- Transactional Outbox: Persisting events to an outbox table in the local database to guarantee at-least-once message delivery.
- Saga orchestrators: Writing complex compensating action workflows to undo partial state changes when a downstream payment fails.
- Idempotency keys: Tracking unique message IDs to handle duplicate message deliveries gracefully.
When decomposition actually pays off
Microservices are not an engineering optimization; they are an organizational mechanism for companies with 150+ engineers where merge conflicts and release coordination on a single codebase become unbearable.
If your engineering team has fewer than 25 developers, a well-factored modular monolith running on a single Postgres cluster will ship features 5× faster with 90% fewer late-night on-call incidents.
Refactoring microservice boundaries or designing outbox pipelines? Reach out.