Your Microservices Are Too Dependent on Each Other

by Eric Hanson, Backend Developer at Clean Systems Consulting

The test your architecture is probably failing

Can you deploy any single service in your system without coordinating with the team that owns any other service? If the answer to this question involves exceptions — "yes, except for Service A, which needs a new field from Service B" or "yes, but we have to deploy them in a specific order" — your services are too coupled. The entire promise of microservices — team autonomy, independent deployment, isolated failure domains — depends on genuine independence. Partial independence just means you have a distributed system with deployment coordination requirements on top.

Coupling in microservices is not always obvious. The most common forms don't look like coupling until they cause an incident or a deployment failure.

Shared database coupling

The most severe and most common: multiple services read from or write to the same database tables. This creates hidden coupling at the schema level — any schema change affects every service touching those tables, and those services must be deployed together or in a carefully coordinated sequence.

The tell is a shared database user with write access across schema namespaces, or a single database with tables owned by multiple services. The fix is data isolation: each service owns its schema, and other services access that data only through the owning service's API. If another service needs frequent access to data it doesn't own, consider event-driven replication of a read model into the consuming service's own storage.

Platform library coupling

Shared libraries across services are fine for infrastructure concerns: logging utilities, HTTP client wrappers, authentication helpers. They become coupling when they contain domain logic — entities, business rules, event definitions.

If your common-domain library defines the Order class and six services import it, you've created a deployment dependency. When Order needs a new field, you update the library, republish it, and every service must adopt the new version. If Service A adopts v2 of the library but Service B is still on v1, and they share event schemas defined in the library, they can't communicate correctly.

Domain types should live in the service that owns them. Other services should have their own internal representation of shared concepts, translated at the boundary:

// Inventory Service's internal representation of an order item
// NOT imported from a shared library
public record OrderItem(
    String itemId,
    int quantity,
    String sku
) {}

// Translation from Order Service's event payload
public OrderItem fromEvent(OrderItemPayload payload) {
    return new OrderItem(payload.itemId(), payload.qty(), payload.sku());
}

This looks like duplication. It is duplication, and it's correct duplication. The Inventory Service's concept of an order item is exactly what Inventory needs — no more, no less. When Order Service changes its model, Inventory Service's translation layer is updated independently.

Synchronous call chain coupling

When Service A calls B, which calls C, which calls D, you have a call chain where A's behavior depends on B, C, and D all being available and performing adequately. This is temporal coupling — availability coupling across time.

Map your synchronous call chains. If any chain is longer than two hops (A → B → C), audit whether the intermediate calls are genuinely necessary. Common findings:

  • B calls C just to pass data through — B should own or replicate that data locally
  • The chain exists because the domain model is wrong — B shouldn't be calling C's domain at all
  • Some calls in the chain are fire-and-forget and should be converted to async events

For chains that are genuinely necessary, isolate each hop with a circuit breaker and ensure each service has a fallback. But the long-term answer is reducing the chain depth through better domain modeling.

Deployment order coupling

If your deployment runbook says "deploy Service A before Service B because B expects A's new API to be available," you have coupling. This is usually caused by one of:

  • Service B consuming a new field from Service A before Service A supports it (versioning failure)
  • Service A and B sharing a database migration that must run in sequence (shared schema)
  • A behavioral dependency where B's initialization calls A's API

Eliminate deployment order requirements by designing each service to tolerate the absence or older version of services it depends on. New fields should be optional with documented defaults. Schema migrations should be backward compatible for at least one deployment cycle. Initialization logic should not have hard dependencies on other services being current.

The practical audit

Take your most critical service. Enumerate every external dependency it has: databases, other services, shared libraries, message brokers. For each dependency, answer:

  1. Can this service deploy without this dependency being deployed first?
  2. Can this service function if this dependency is unavailable at runtime?
  3. Does this dependency's schema or API contract control when this service can deploy?

Any "no" answer identifies a coupling that reduces your deployment independence. Prioritize eliminating the database and library coupling — those are the highest-impact forms. The synchronous runtime coupling you handle with circuit breakers and fallbacks, but the design coupling you have to address at the architectural level.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

When Your API Integration Explodes in Production

Everything worked fine in testing. Then production hits—and suddenly your API integration turns into a disaster you didn’t see coming.

Read more

Why Seoul's Startup Scene Is Thriving But Its Backend Talent Is Locked Up in Chaebols

Seoul's startup ecosystem has real momentum. The backend engineers who could staff it are mostly somewhere else.

Read more

What Happens When You Accidentally Delete the Production Database

One wrong click in production. Data disappears. Panic sets in. But who’s really at fault? Spoiler: it’s not the developer.

Read more

How Taipei Startups Are Solving the Backend Hiring Gap With English-First Async Remote Contractors

Taipei's senior backend hiring market is thin and slow. Some startups have found a working model that doesn't require solving that problem before shipping.

Read more