Architecture Realities: Coupling Doesn’t Disappear — It Moves
Introduction:
Decoupling is one of the most pursued goals in software architecture. Microservices decouple deployment. Event-driven architectures decouple services from each other's availability. Message queues decouple producers from consumers. The vocabulary of modern architecture is saturated with mechanisms designed to reduce coupling — and the implicit promise is that applying these mechanisms produces systems where components are genuinely independent.
That promise is only partially true. Coupling does not disappear when you apply decoupling patterns. It moves. The tight coupling between two services that make synchronous calls to each other is replaced by a different kind of coupling — between the event schema that producer and consumer share, between the message format that the queue expects, between the contract that both sides must honour for the system to work correctly.
This is not a failure of decoupling patterns. It is a fundamental property of systems where components must communicate to produce a coherent result. Understanding where coupling moves when you apply decoupling mechanisms is what distinguishes architectural decisions that genuinely reduce the cost of change from decisions that relocate coupling to places where it is harder to see and harder to manage.
Temporal Decoupling Introduces Schema Coupling:
Synchronous service calls couple the availability of the caller to the availability of the callee — if the downstream service is unavailable, the upstream service cannot complete its operation. Replacing synchronous calls with asynchronous messaging through a queue or event bus removes this temporal coupling. The producer publishes a message and continues without waiting for the consumer to process it.
But the producer and consumer are now coupled through the message schema. The producer must produce messages in a format the consumer can parse. The consumer must be able to handle every message the producer sends. When the producer needs to change its message format — adding a field, changing a type, restructuring the payload — it must do so in a way that does not break consumers that have not yet been updated.
Schema coupling in asynchronous systems is often more expensive to manage than the temporal coupling it replaced. Temporal coupling fails loudly — the synchronous call returns an error and the problem is immediately visible. Schema coupling fails silently — a producer publishes messages in a new format, consumers parse them incorrectly, and the problem may not be detected until data corruption surfaces downstream.
Microservices Move Coupling to the Network:
Monolithic applications have internal coupling — modules depend on each other through function calls, shared data structures, and internal interfaces. Decomposing a monolith into microservices removes this internal coupling by placing service boundaries between components that were previously tightly coupled.
But the coupling does not disappear. It moves to the network boundary between services. Services that previously communicated through in-process function calls now communicate through network calls that can fail, time out, and return errors that internal calls never produce. The internal coupling that was invisible and reliable has been replaced by network coupling that is explicit and unreliable.
This trade is often worthwhile — network coupling enables independent deployment, independent scaling, and independent failure of services in ways that internal coupling does not. But it is a trade, not an elimination. Teams that decompose monoliths expecting to reduce coupling without accounting for the operational complexity of managing coupling at network boundaries consistently underestimate the cost of the decomposition.
Shared Databases Create Hidden Coupling:
One of the most common architectural mistakes is decomposing services at the API level while maintaining a shared database. Services have separate codebases, separate deployments, and separate APIs — all the visible markers of decoupling are present. But they share a database schema, which means they are coupled through every table, every column, and every index that both services depend on.
When one service needs to change the database schema — adding a column, changing a data type, restructuring a table — it must do so without breaking the other services that share the schema. This requires coordinated deployments, careful migration strategies, and ongoing awareness of which parts of the schema each service depends on. The coupling that was removed from the API layer has reappeared in the data layer, where it is less visible and more dangerous because data migrations are significantly harder to roll back than API changes.
Genuine data decoupling requires each service to own its own data store — a requirement that introduces its own complexity around data consistency and cross-service queries but that produces the independent deployability that shared database architectures cannot achieve.
Event-Driven Architectures Move Coupling to Event Ordering:
Event-driven architectures decouple services from each other's implementation details — a service that publishes events does not need to know which services consume them or what they do with them. This is a genuine and valuable form of decoupling that enables independent evolution of producers and consumers.
But consumers in event-driven systems frequently depend on the ordering of events to maintain correct state. A consumer that processes an OrderCreated event before an OrderUpdated event produces correct results. A consumer that processes them in reverse order produces incorrect results. The services are decoupled from each other's availability and implementation, but they are coupled through the assumption that events will be processed in the order they were produced.
Event ordering guarantees vary significantly between messaging systems. Some provide strong ordering within a partition. Others provide no ordering guarantees at all. Consumers that assume ordering without verifying that the messaging system guarantees it are accepting a coupling that may not be visible in the system's architecture diagrams.
Coupling to Infrastructure Is Coupling Too:
Services that are decoupled from each other may still be tightly coupled to the infrastructure they run on — the specific managed service that provides their message queue, the specific database engine that stores their data, the specific caching layer that serves their hot data. This infrastructure coupling is often treated as acceptable because infrastructure changes are rare and controlled.
But infrastructure coupling has real costs. A service that uses Kafka-specific features — consumer groups, topic compaction, partition assignment — cannot be migrated to a different messaging system without significant rework. A service that relies on PostgreSQL-specific features cannot be migrated to a different database without rewriting the queries that depend on those features.
Infrastructure coupling is not inherently wrong — using platform capabilities appropriately is good engineering. But it should be a deliberate decision made with awareness of the migration cost it creates, not an accidental accumulation of dependencies on platform-specific features that were adopted for convenience.
Conclusion:
Coupling is a property of systems that must communicate to achieve a shared goal. It cannot be eliminated — it can only be moved to different places in the system, expressed in different forms, and managed with different tools. Decoupling patterns that move coupling from visible, loudly-failing forms to invisible, silently-failing forms are not improvements unless the team understands where the coupling moved and has mechanisms to manage it in its new location.
The architectural decisions that genuinely reduce the cost of coupling are the ones made with explicit awareness of where coupling will exist after the pattern is applied — what schema contracts will need to be managed, what ordering assumptions will need to be honoured, what infrastructure dependencies will constrain future choices. That awareness is what separates architectural decisions that make systems easier to change from decisions that make systems look decoupled while remaining as constrained as before.
Enjoyed this post?
Stay in the loop
New posts + weekly digest, straight to your inbox.
Create a free account
- Save posts to your vault
- Like posts & build history
- New-post alerts
No comments yet. Be the first to comment!