← All notes

Microservices: what they cost and what they bought

Published3 min readmicroservicesarchitecturemessaging

I have worked on a travel platform split into about six services, and on a payroll platform where several services share one calculation library. Both taught me the same thing: splitting a system does not remove complexity, it moves it onto the network.

What the split bought

Independent deploys were the real win. Catalog could ship a change without touching Booking. A team could own Identity and keep its database private. Each service had a narrow job: Identity for accounts and tokens, Catalog for what can be booked, Booking for the reservation lifecycle, Payment for money, Search for finding things, Notification for telling people.

What it cost

  • No shared transaction. A booking spans services and there is no database transaction around all of them. Every cross-service change needs a plan for the half that fails.
  • Messages get lost and repeated. Publishing an event after a commit can fail between the two steps. Consuming an event can happen twice.
  • Calls need identity. One service calling another needs its own authentication, separate from a user's token. We added service-to-service auth and cached the identity lookups so Identity was not hit on every request.
  • Debugging crosses processes. One user action leaves traces in several services. Without correlation ids, structured logs and metrics endpoints, you are guessing.

The patterns that earned their place

  • Transactional outbox. Write the event to a table in the same transaction as the change, and let a relay publish it. The write and the intent to publish cannot disagree.
  • Idempotent consumers. Deduplicate on a correlation id. Retry with backoff. After the retries run out, send the message to a dead-letter queue where someone can see it and replay it.
  • State machines for lifecycles. A booking moves through named states with allowed transitions. Anything else is rejected, so a late or duplicate event cannot move it somewhere wrong.
  • Soft holds with an expiry. Reserving stock in Redis with a time limit protected inventory during payment without holding database locks, and abandoned checkouts cleaned themselves up.

The shared library trap

In the payroll platform, calculation rules lived in one shared library that every service imported. That gave one source of truth for tax and pension rules, which is good. It also meant a fix reached a service only when that service upgraded the dependency. A shared library is a deployment coupling wearing a code-reuse costume. If the rules change often, a service that owns them is easier to keep current. If they change rarely and need to be identical everywhere, a library is fine.

When I would not split

Start with one deployable and clear module boundaries. Split when you have a concrete reason: a part that needs to scale differently, a team that needs to own it, or a failure you want to contain. If the only reason is that microservices sound modern, the network will charge you for it and give nothing back.

The boundary worth drawing is the one around data you can own completely. If two services always read each other's tables, they are one service pretending to be two.

Ayomide Ilesanmi, Lagoscommit feec46d · built 2026-11-13 07:18 UTC