API Gateways & Service Boundaries

ModernLift · ·11 min read
Part 7 of 10

An API gateway is the single entry point in front of a set of services — it routes each request to the right service and handles cross-cutting concerns like authentication, rate limiting, and TLS. In a monolith migration it doubles as the strangler facade, routing traffic to either the monolith or a new service as each slice is extracted. The gateway should route and handle edge concerns only; business logic belongs in the services, and the contracts between services are what keep boundaries stable as the system evolves.

Part 6 split the data; the services now own their own state. But a client out in the world still sends one request and expects one answer — it has no idea the system came apart behind the scenes, and it shouldn’t have to. Something has to sit at the front, take that request, and route it to the right service. That something is the API gateway, and in a migration it is also the very thing that makes the whole incremental approach possible.

The two jobs the gateway does

An API gateway is the single entry point in front of a collection of services. It does two distinct kinds of work, and keeping them distinct is most of the wisdom here.

Routing. The gateway receives every external request and forwards it to the service that handles it. Clients address the gateway; the gateway knows the map. This means clients never need to know how many services exist, where they live, or when one is split in two — the topology behind the gateway can change freely without breaking anyone outside it.

Cross-cutting edge concerns. A handful of things every request needs, regardless of which service answers it: authentication and authorization, TLS termination, rate limiting, request logging, sometimes response caching. Handling these once at the gateway is far better than reimplementing them in every service. The gateway is the natural home for the concerns that belong to the edge of the system rather than to any one domain.

That phrase — the edge of the system — is the line you must not cross.

The gateway is your strangler facade

Here is where this part connects back to the spine of the series. The strangler facade that makes incremental migration possible — the intermediary that routes each request to either the monolith or a new service — is, in practice, almost always the API gateway.

On day one the gateway routes everything to the monolith. As each slice is extracted and passes its parity gate, you change the gateway’s routing for that slice to point at the new service — and you do it gradually, a rising percentage of traffic at a time, telemetry watched, the old route one config change away for instant rollback. The zero-downtime cutover happens at the gateway. This is why the facade is real engineering you take on deliberately: it is the control surface for the entire migration, the place where old and new are blended and where reversibility lives.

The reversal worth keeping: the gateway is how you take the system apart without anyone outside noticing it came apart.

After the migration, the facade role retires but the component stays. The strangler facade is what the gateway does during the migration; the front door is what it is afterward.

Keep the gateway thin

The most common way an API gateway goes wrong is the most tempting: it slowly accumulates business logic. A little request transformation here, a little response aggregation there, a special-case rule for one client, a bit of validation that “may as well live in one place.” Each addition is reasonable. The sum is a disaster.

A gateway full of business logic becomes a new shared chokepoint — a component every team has to coordinate changes through, that every request depends on, and that can take the entire system down when it fails. You will have removed the monolith and rebuilt its worst property, coupling, at the front door. This failure is common enough to have a name: the god gateway.

The rule is simple and worth defending against steady pressure: the gateway routes and handles edge concerns; business logic lives in the services that own it. When you feel a domain rule trying to migrate into the gateway, that is a signal the rule belongs in a service, not that the gateway needs to grow.

Gateway, BFF, or service mesh

Three components sit around a decomposed system, and teams new to distribution routinely confuse them or reach for the wrong one. They carry different traffic and solve different problems. Knowing which does what is also how you keep from cramming all three jobs into the gateway.

  • An API gateway handles north-south traffic, the requests arriving from clients outside the system. Routing plus edge concerns, as above.
  • A backend for frontend (BFF) is a gateway tailored to one kind of client. A web app, a mobile app, and a partner API each want data shaped differently, so instead of one generic gateway accumulating a pile of client-specific rules, you give each client its own thin gateway that aggregates and shapes responses for it. A BFF is how you relieve the exact pressure that would otherwise grow a single gateway into a god gateway.
  • A service mesh handles east-west traffic, the calls services make to each other behind the gateway. Retries, mutual TLS, load balancing, and per-hop observability move out of application code and into a sidecar running next to each service. The mesh is about service-to-service reliability, not about the client edge.
ComponentTrafficWhat it handlesReach for it when
API gatewayNorth-south (client to system)Routing, auth, TLS, rate limiting, loggingYou have external clients and more than a couple of services behind one front door
Backend for frontendNorth-south, per client typeResponse aggregation and shaping for one specific frontendWeb, mobile, and partner clients need materially different payloads
Service meshEast-west (service to service)Retries, mutual TLS, load balancing, per-hop observabilityInter-service traffic is heavy enough that reliability and visibility must be uniform
Load balancerEither, by connection or pathTraffic distribution, health checksYou need to spread load, not route by service identity or apply auth

The short version: the gateway faces clients, the mesh faces other services, and a BFF is a gateway specialized per client. A plain load balancer distributes traffic but knows nothing about service identity or authentication, so it sits underneath these as a building block, not as a replacement for them.

Where the service boundaries actually go

The gateway routes to services, but nothing above tells you where one service should end and the next begin. That is the harder question, and getting it wrong is what produces the distributed monolith: all the cost of separate services and none of the independence, because every request has to cross three of them to get anything done.

Good boundaries come from the domain, not from the org chart and not from the current code layout. The most reliable lens is the bounded context from domain-driven design: a region of the system where each term means exactly one thing. Order means one thing to fulfillment and another to billing. That divergence is not sloppiness, it is the seam. Where the language of the business changes, a boundary usually belongs.

Four practical signals point at the same seams:

  • Follow the language. Where the same word carries a different meaning for different parts of the business, you are looking at two contexts that want to be two services.
  • Follow the data. Data that changes together, and is read together, belongs together. A boundary that forces one business action to write across two services is cutting through a join instead of along one.
  • Follow the reason for change. Code that changes together, for the same business reason, belongs in the same service. Split it and every change becomes a coordinated two-service release.
  • Follow ownership. A good boundary is one a single team can own end to end, per Conway’s law. If a service needs three teams to agree before it ships, the line is drawn across an organization, not a domain.

The trap is cutting on technical layers instead of business capabilities. A “user service,” an “auth service,” and a “database service” look tidy on a diagram and behave terribly in production, because almost every real use case needs all three in one breath. A capability-shaped service, “checkout” or “billing,” can serve most of a use case on its own. The test is blunt: can this service change and deploy without a lockstep release from its neighbors? If yes, the boundary is in a good place. If no, you have drawn a line through something that wanted to stay whole, and the contract across it will never stop hurting.

Contracts are what make boundaries stable

A service boundary is only as stable as the contract across it — the agreed interface (the API shape, the event schema) that one service exposes and others depend on. Boundaries don’t hold because of where you drew them on a diagram; they hold because the contract across them is explicit, versioned, and respected.

Three practices keep contracts honest:

  • Make the contract explicit and owned. A service’s public interface is a deliberate, documented surface — not whatever its code happens to expose this week. The narrower and clearer it is, the less the services on either side are coupled.
  • Version changes; don’t break callers. When a contract must change, evolve it in a backward-compatible way and give consumers a window to migrate. A breaking change pushed without coordination re-couples the services it connects — the thing you split them to avoid.
  • Test the contract from both sides. Contract tests verify that the provider still honors what consumers expect, so a boundary violation is caught in the pipeline rather than in production. This is the same instinct as parity validation, applied to the seams between services.

A chatty, ill-defined contract is a boundary in the wrong place wearing a gateway as a disguise. A clean, stable contract is what lets two services genuinely evolve on their own — which was the point of separating them at all.

What a gateway can’t fix for you

A gateway is not free, and for small systems it can be overkill — a handful of services behind a simple load balancer may not need a dedicated gateway, and adding one introduces a hop and a component to operate. The gateway also becomes, by definition, something every request passes through; if it is not made highly available it is a single point of failure for the whole system, so it has to be run with the seriousness that role demands. And no gateway rescues bad boundaries: if the services behind it are chattering constantly across ill-placed seams, a tidy front door just hides the coupling rather than fixing it. The gateway organizes a well-decomposed system; it cannot decompose a badly-cut one.

Where this leads

That last warning is the door into the most important cautionary part of this series. Bad boundaries, chatty contracts, shared data, a god gateway — each is a way to end up with the worst of both worlds: all the cost of distribution and none of the independence. That outcome is common enough, and damaging enough, to deserve its own name and its own part. Part 8, The Distributed Monolith Anti-Pattern, is about recognizing it, avoiding it, and recovering if you are already in it.

Frequently asked questions

What does an API gateway do in a microservices migration?
It is the single front door for clients, routing each request to the service that handles it and taking care of cross-cutting concerns — authentication, TLS termination, rate limiting, request logging — in one place instead of every service. During a monolith migration it also serves as the strangler facade: it routes traffic to either the monolith or a newly extracted service, shifting that routing gradually as each slice goes live, so clients never see the system being taken apart.
Is an API gateway the same as a strangler facade?
They overlap. The strangler facade is the migration role — the intermediary that routes between old and new so extraction is incremental and reversible. The API gateway is the component that commonly plays that role and then remains as the system's permanent front door after migration. The facade is what it does during the migration; the gateway is what it is afterward.
What should not go in an API gateway?
Business logic. The gateway should route and handle edge concerns; the moment domain rules start living in it, it becomes a new shared chokepoint that every team must coordinate through and that can take the whole system down — a centralized version of the coupling the migration was meant to remove. Keep the gateway thin, keep business logic in the services that own it, and watch for the gateway quietly growing into a second monolith.
How do you decide where service boundaries should go?
Boundaries come from the domain, not from the org chart or the current code layout. The most reliable guide is the bounded context from domain-driven design: a region where each term means exactly one thing, so where the same word ("order," "account") means something different to another part of the business, a seam usually belongs. Reinforce it with data that changes and is read together, code that changes for the same business reason, and a scope a single team can own end to end. The test of a good boundary is whether the service can change and deploy without a lockstep release from its neighbors.
What is the difference between an API gateway, a BFF, and a service mesh?
They carry different traffic. An API gateway faces clients outside the system (north-south) and does routing plus edge concerns like authentication and rate limiting. A backend for frontend is a gateway specialized for one client type, shaping and aggregating responses so a web app, a mobile app, and a partner API each get what they need without one generic gateway growing client-specific logic. A service mesh handles traffic between services (east-west), moving retries, mutual TLS, and observability into a sidecar beside each service. The gateway faces clients, the mesh faces other services, and a BFF is a per-client gateway.
Do you need an API gateway for a small system?
Not always. A handful of services behind a simple load balancer may not justify a dedicated gateway, and adding one introduces another hop and another component to run and keep highly available. The gateway earns its place once you have enough services, enough external clients, or enough shared edge concerns like authentication, rate limiting, and TLS that handling them once in front beats repeating them in every service. It organizes a well-decomposed system, but it cannot fix a badly cut one.
All 10 parts of Monolith to Microservices →