⏱ 20 min read
Microservices were sold with the promise of independence. Small teams. Fast releases. Clean boundaries. A sensible antidote to the giant, slow-moving monolith. And in the right hands, that promise is real.
But in many enterprises, microservices did not reduce coupling. They industrialized it. microservices architecture diagrams
What used to be one ugly dependency graph inside a monolith became a fleet of polite, well-documented services depending on each other’s data in increasingly creative ways. The coupling did not disappear. It escaped into the network, multiplied through events, and started charging interest in operations. The result is a topology of dependency explosion: dozens or hundreds of services, each allegedly autonomous, yet all quietly entangled through shared business facts, replicated state, and semantic leakage.
This is not mainly a technology problem. It is a domain design problem.
If the boundaries are wrong, microservices amplify mistakes. They turn local design confusion into distributed systems pain. A weak module boundary in a monolith is inconvenient. A weak service boundary across a Kafka mesh, multiple teams, and independent deployment pipelines becomes a long-running enterprise tax. event-driven architecture patterns
That is the central point: microservices increase data coupling unless domain semantics are designed first. Not perfectly. Not academically. But deliberately enough that each service owns a meaningful business capability rather than merely hosting a table behind an API.
A service boundary is not a database split. It is a semantic contract.
And when enterprises forget that, they create a brittle topology where every change to a customer, policy, order, account, entitlement, or product ripples through event streams, caches, joins, and reconciliation jobs like a shockwave through wet concrete.
Context
Most large organizations arrive at microservices through pressure, not philosophy. They have a monolith that is too hard to change, release cycles measured in weeks or months, teams stepping on each other’s toes, and an infrastructure group pushing cloud-native platforms. The move seems obvious: break the monolith into smaller services, put APIs in front of them, maybe add Kafka for asynchronous integration, and let teams move independently. cloud architecture guide
This often starts well. The first decompositions produce visible gains. A team extracts pricing, identity, recommendations, payments, notifications, or catalog. Deployment becomes easier for those areas. Local ownership improves. The architecture diagrams look modern.
Then reality arrives.
A customer change now affects CRM, billing, risk, marketing preferences, identity, service eligibility, support tooling, and reporting. Product data is needed by sales, ordering, fulfillment, invoicing, channel platforms, and customer self-service. “Simple” business operations span half a dozen bounded contexts, but nobody did the bounded context work. So services start reaching into each other for truth. Some call synchronously. Some subscribe to events. Some maintain copies. Some use CDC. Some, despite everyone swearing otherwise, query each other’s databases through side doors in the data platform.
Soon the enterprise owns not a system of microservices, but a data dependency network.
This is where architecture gets real. Not in the code generators or service mesh settings. In deciding what business meaning lives where, what can be copied safely, what must remain local, what “truth” means for each context, and how inconsistency is detected and repaired rather than wished away.
Domain-driven design matters here not because it is fashionable, but because distributed systems expose semantic mistakes with unusual cruelty.
Problem
The standard microservices narrative focuses on service autonomy. The lived enterprise experience often looks like this:
- Every service needs customer data.
- Every workflow spans multiple services.
- Every team publishes events.
- Every downstream team asks for one more field in the event.
- Every schema evolves under pressure.
- Every production incident ends with someone saying, “Which system is actually authoritative for this?”
That last question is the tell.
When service boundaries are cut by technical layers, org charts, or database tables instead of business meaning, data coupling rises. Services become dependent on external state to do local work. They either fetch that state on demand or replicate it into local stores. Both approaches have costs.
Synchronous retrieval gives you runtime coupling: lower latency tolerance, cascading failures, retry storms, and user journeys held hostage by distant systems.
Asynchronous replication gives you semantic coupling: stale reads, duplicate events, out-of-order updates, schema drift, reconciliation gaps, and endless debates about eventual consistency that mostly mean “we don’t know when the numbers will match.”
The irony is sharp. Teams adopt microservices to reduce coordination and discover they now coordinate around data contracts, event semantics, consumer-driven schema demands, replay handling, idempotency, and backfills.
The dependency explosion topology emerges when many services hold partial knowledge of the same business concepts, but no one owns the concept in a domain sense. There may be an “owner” system in an operational sense, yet the business invariants are distributed across several services. At that point, changing a single concept requires negotiation across a network.
It is coupling, just with better branding.
Forces
A useful architecture article should not pretend the wrong choices are irrational. Enterprises do what they do for reasons. There are real forces at work.
1. Shared business concepts are inherently cross-cutting
Customer, product, order, account, policy, claim, employee, location—these concepts matter in many places. It is unrealistic to assume one service can hide them completely. The question is not whether the concept appears in multiple places. It is whether the meaning is stable and well-bounded in each place.
A billing service does not need the same customer as a marketing service. If both consume one giant canonical customer model, they are already in trouble.
2. Team autonomy pushes toward local copies
Teams want to avoid synchronous dependencies. Sensible. So they subscribe to events and maintain local projections. This is often the right move. But local copies are not free. Once data is copied, it must be refreshed, versioned, audited, and repaired.
3. Platform tooling makes replication easy
Kafka, CDC pipelines, stream processors, lakehouse ingestion, event routers, schema registries—modern platforms make it trivial to spread data around the enterprise. What they do not do is decide whether the replicated data still carries the same business semantics.
Easy movement of data often masks hard questions about ownership.
4. Reporting and analytics quietly reshape operational architecture
Operational services often start pure enough, then BI, fraud, customer support, regulatory reporting, and ML use cases demand richer historical views and broader joins. Enterprises respond by exposing more events or broadening APIs, and operational services start carrying data they should never have owned.
5. Legacy migration pressures reward expedience
In a strangler migration, the immediate goal is usually to carve out value without halting delivery. That often leads to anti-corruption layers, dual writes, replicated reference data, and temporary orchestration. Temporary architecture has a habit of becoming permanent, especially when it works just enough.
6. The network punishes chatty decomposition
Small services can become too small. A decomposition based on CRUD entities rather than business capabilities creates excessive call chains and event chatter. What looked elegant in a whiteboard session becomes a latency map in production.
These forces are not a case against microservices. They are a case for disciplined domain design before decomposition, and for migration plans that account for semantic coupling rather than merely infrastructure separation.
Solution
The answer is not “go back to the monolith” any more than the answer to poor database design is “stop storing data.”
The answer is to design service boundaries around business capabilities and invariants, not shared nouns.
This is domain-driven design doing practical work. Not sticky-note theater. Not endless event-storming workshops with no decisions. Real design choices:
- What business decisions belong together?
- Which invariants must be enforced transactionally?
- Which data can be replicated safely?
- Which facts are authoritative only within one bounded context?
- Where do terms change meaning across contexts?
- Which workflows need orchestration, and which should be choreographed?
- What is the reconciliation plan when, not if, state diverges?
A bounded context is useful because it limits semantic coupling. It says: inside this context, this model means this. Outside, other models may exist. Translation is expected. That translation is not overhead; it is architecture acknowledging reality.
The move that reduces data coupling is not “each service has its own database.” That is table stakes. The move is “each service owns a coherent slice of business behavior and the data necessary to make those decisions.”
That sounds simple. It is not.
A customer profile service that merely stores contact details is not the owner of “customer” in every sense. Identity, legal entity verification, billing party, marketing consent, service eligibility, and support view may each live in different bounded contexts. If one “Customer Service” becomes the source for all those meanings, you have built a distributed monolith in the shape of a master data fantasy.
Instead:
- Keep transactional consistency where the business truly needs it.
- Replicate reference or read-optimized data where latency or autonomy require it.
- Publish domain events that reflect meaningful business facts, not table mutations.
- Maintain local projections for local decisions.
- Reconcile explicitly when asynchronous flows drift.
- Accept that some concepts are shared by reference, not by common model.
The architecture should optimize for semantic autonomy, not just deployability.
Architecture
A healthy microservices topology has strong internal cohesion and deliberate external relationships. The center of gravity is the bounded context, not the entity.
Here is the bad pattern many enterprises accidentally build:
This picture is familiar because it grows naturally when one service becomes the source of broadly useful data. Every other service starts depending on it. Then they depend on each other. Soon every user journey traverses several runtime edges, and “autonomy” exists mostly in slideware.
Now compare that with a bounded-context-oriented topology:
This is not dependency-free. Nothing serious ever is. But the dependencies are shaped by business capability. Customer engagement does not own billing semantics. Billing account does not need the full customer profile. Support may maintain a read model assembled from events, but that read model does not dictate operational ownership.
A few architecture principles matter here.
Domain events, not change data exhaust
If a service publishes “CustomerAddressUpdated” because the business meaning matters, downstream services can decide whether that fact concerns them. If it publishes every table mutation as integration data, it forces consumers to reverse-engineer business semantics from storage behavior.
CDC can be useful in migration and data platform integration. It is a poor substitute for domain design.
Local models, translated at boundaries
A pricing service may receive product identifiers and order context, but it should own pricing rules and the data necessary to evaluate them. It should not call catalog, promotions, customer, and tax services in a daisy chain for every price calculation unless the domain truly demands fresh state from each.
Event-driven where autonomy matters, synchronous where immediacy matters
Kafka is excellent for distributing business facts, building projections, and decoupling downstream processing. It is not magic. If a user journey needs an immediate authorization decision, pushing that over asynchronous events may simply move complexity elsewhere. Use async to reduce runtime coupling where the business can tolerate delay and drift. Use sync where the business needs instant response and clear ownership.
Reconciliation is part of the design
In distributed architecture, reconciliation is not a cleanup script somebody writes after go-live. It is a first-class capability. If billing account ownership lags behind customer legal entity changes, how is divergence detected? What metrics expose it? What workflow repairs it? Which system wins in conflict? Without answers, eventual consistency becomes eventual confusion.
Migration Strategy
Enterprises rarely get to redesign from scratch. They migrate from something already making money, already messy, already politically defended. That means the practical path is a progressive strangler migration guided by domain seams.
The mistake is to carve out services based on the easiest technical extraction first and assume the domain will sort itself out. It usually will not.
A better migration sequence looks like this:
- Identify business capabilities and bounded contexts in the monolith.
- Map current transactional boundaries and business invariants.
- Find seams where one context can be extracted with a clear ownership model.
- Introduce anti-corruption layers so new services are not infected by legacy semantics.
- Publish domain events from the legacy system where needed.
- Create local projections in new services rather than broad synchronous dependencies.
- Run reconciliation and observability from day one.
- Gradually transfer command ownership, then read ownership, then retire legacy paths.
Here is the shape of that migration:
There are two hard truths in strangler work.
First, for a while you will have overlap. The old and new worlds both know something about the same business process. Dual reads, temporary dual writes, event mirrors, and compensating flows may be necessary. Purity is not the goal. Controlled transition is.
Second, migration should follow business risk, not architectural vanity. Extracting notifications first may be technically easy, but if your real coupling problem is around order-to-cash semantics, the enterprise value lies there. Domain-driven design helps prioritize what matters.
A pragmatic migration playbook often uses Kafka in three ways:
- as a bridge for legacy-emitted business events,
- as a distribution mechanism for state needed by downstream projections,
- and as an audit trail for reconciliation and replay.
But replay is not free. If event semantics changed over time, or if downstream services baked in assumptions never made explicit, replay becomes archaeology. This is why event governance matters. Not because architects enjoy committees, but because five years later somebody will need to rebuild a projection under regulatory pressure. EA governance checklist
Enterprise Example
Consider a telecommunications enterprise modernizing a large BSS stack. The original platform had one customer master, one product catalog, one ordering engine, and one billing system tightly coupled in a sprawling suite. The modernization program split these into dozens of microservices across customer onboarding, product offer management, order capture, provisioning, billing account management, invoicing, collections, and digital channels.
On paper, this looked excellent.
In production, the teams discovered they had created three different meanings of customer:
- the party who has legal identity,
- the subscriber receiving service,
- the billing account holder who pays,
- and, soon enough, a fourth one: the digital profile used by web and app channels.
The first wave of services had all consumed a common customer event stream from the old CRM. Convenient, but dangerous. The event payload grew into a giant canonical model. Ordering needed serviceability attributes. Billing needed invoicing preferences. Marketing needed consent flags. Support needed contact methods and account relationships. Every team lobbied for more fields. The “customer” event became a semantic landfill.
Then failure started to show up in the cracks.
A legal name correction updated party records. Some downstream services treated it as customer profile data and refreshed immediately. Billing account systems only updated on nightly reconciliation because invoices had legal controls. Digital channels updated profile displays within seconds. Support screens merged old and new names depending on which read model had refreshed. Customers saw one name in the app, another on their invoice, and a third in call center tooling. Everyone blamed eventual consistency, which was true in the way “gravity” is true when a bridge collapses.
The real issue was not delay. It was lack of context-specific ownership.
The architecture was revised around bounded contexts:
- Party and Identity owned legal identity and verification.
- Customer Engagement owned contact preferences and digital profile.
- Billing Account owned payer relationships and invoice controls.
- Subscription owned service instance relationships.
- Order Management owned fulfillment intent and state transitions.
Rather than a universal customer payload, the enterprise defined smaller domain events:
- PartyLegalNameChanged
- BillingAccountResponsibilityTransferred
- ContactPreferenceUpdated
- SubscriptionActivated
Downstream services subscribed only where the business fact mattered. Support built a composite read model specifically for agent workflows, acknowledging that support needed a cross-context view but did not own the underlying facts.
Most importantly, they introduced reconciliation capabilities. If Billing Account still showed an old legal name after a Party event, a monitored reconciliation flow flagged the mismatch. In some cases it auto-repaired. In regulated cases it raised a work item for controlled correction.
This did not eliminate complexity. It made complexity visible and governable.
That is what good architecture often does. It replaces hidden chaos with explicit tradeoffs.
Operational Considerations
Once data is distributed, operations is no longer just CPU, memory, and uptime. It becomes semantic observability.
You need to know not only whether services are healthy, but whether business facts are converging correctly.
Data lineage and ownership
Every replicated data element should have a known source, reason for replication, freshness expectation, and conflict policy. “Because another team needed it” is not an architecture decision.
Schema evolution discipline
Kafka plus schema registry helps, but it does not solve semantic drift. Backward compatibility at the schema level can still hide business incompatibility. Adding a field may be harmless structurally and disastrous semantically if consumers infer meaning they should not.
Idempotency and ordering
Events will duplicate. Delivery order will break under enough load, partition movement, or retries. If state transitions rely on strict global ordering, you have likely chosen the wrong integration pattern or the wrong aggregate boundary.
Reconciliation loops
Every important asynchronously replicated relationship should have:
- a comparison method,
- expected convergence timing,
- alert thresholds,
- repair logic,
- and auditability.
Reconciliation is especially critical for money, entitlements, compliance, and customer communications.
Composite read models
User-facing journeys often need data from multiple contexts. Resist the temptation to make every operational service query every other service live. Build purpose-specific read models instead. They can be event-fed, cache-backed, or assembled through backend-for-frontend patterns. The point is to isolate user experience needs from operational ownership.
Runtime topology control
Dependency explosion is partly an operational graph problem. Service catalogs, contract inventories, stream dependency maps, and architecture fitness checks matter. If no one can explain what breaks when a topic schema changes, the architecture is already too implicit.
Tradeoffs
There is no free lunch here, only better bills.
Benefit: better local autonomy
Well-bounded services let teams change business rules independently.
Cost: more explicit integration design
You must invest in event contracts, translation, reconciliation, and ownership models.
Benefit: scalable team structure
Teams can align to capabilities rather than layers.
Cost: harder cross-context reporting and workflow visibility
You need read models, process views, and operational tooling.
Benefit: failure isolation
In theory, one bounded context can degrade without taking down everything.
Cost: data drift
The more you replicate, the more you must detect and repair divergence.
Benefit: evolutionary migration
A strangler approach lets you modernize incrementally.
Cost: prolonged hybrid state
For a while, the monolith and services both matter, and that is operationally expensive.
The key tradeoff is straightforward: microservices reduce implementation coupling while often increasing integration coupling. Domain design determines whether that increase remains manageable.
Failure Modes
This architecture fails in recognizable ways.
The canonical model trap
A shared enterprise data model becomes the hidden monolith. Services are nominally separate but semantically chained to one giant schema.
CRUD service decomposition
Teams split by entity tables—Customer Service, Product Service, Address Service, Invoice Service—without considering invariants or workflows. This creates chatty dependencies and no real autonomy.
Event soup
Everything publishes everything. Topics proliferate. Consumers rely on accidental fields. Nobody knows the difference between a domain event, an integration event, and CDC exhaust.
Reconciliation by hope
Architects say “eventual consistency” and never define eventual. Mismatches accumulate until a financial close, audit, or major incident exposes them.
Shared database by stealth
Operational reporting, data science sandboxes, or legacy integrations start reading service-owned databases directly. Contracts dissolve. Local refactoring becomes dangerous.
Synchronous call chains in user journeys
A front-end request traverses six services, each dependent on another. Tail latency and cascading failure become routine.
Over-fragmentation
Services are made too small because platform tooling makes creation cheap. The system becomes organizationally expensive to reason about.
These are not edge cases. They are the common ways microservice programs get into trouble.
When Not To Use
Microservices with distributed data ownership are not the right answer for every problem.
Do not use this style when:
- the domain is simple and unlikely to grow in organizational complexity,
- a modular monolith can provide adequate team isolation,
- transactional consistency across functions is critical and frequent,
- your organization lacks the maturity to manage contracts, observability, and operational ownership,
- or the primary need is internal modularity rather than independent deployment.
A modular monolith often beats poorly designed microservices. This is not old-fashioned. It is disciplined. If you cannot define bounded contexts and data ownership inside one deployable unit, distributing them across the network will not improve matters. It will merely make each mistake harder to debug.
Likewise, if your enterprise is not prepared to run reconciliation processes, schema governance, event versioning, and platform-level dependency visibility, an event-driven microservices estate may become a machine for producing plausible but inconsistent data.
Sometimes the right architecture is a monolith with strong modules, explicit domain boundaries, and a well-designed integration perimeter. Start there if the domain and team structure warrant it.
Related Patterns
Several patterns sit close to this problem space.
Bounded Context
The core DDD pattern for limiting semantic sprawl. Essential.
Anti-Corruption Layer
Critical in migration to prevent legacy models from polluting new services.
Strangler Fig Pattern
The practical way to migrate incrementally while keeping the business running.
CQRS and Read Models
Useful when operational ownership and user-facing queries require different shapes.
Saga / Process Manager
Helpful for long-running workflows spanning bounded contexts, though often overused.
Event Sourcing
Powerful in some domains, especially where audit and temporal behavior matter, but not necessary for most microservice estates. Do not adopt it just to feel event-driven.
Master Data Management
Relevant, but dangerous if used as an excuse to force a single global operational model across contexts. Shared identity is useful. Shared semantics everywhere is usually not.
Data Mesh
Helpful for analytical ownership, but do not confuse analytical data products with operational service boundaries.
Summary
Microservices do not automatically reduce coupling. In many enterprises, they increase data coupling by moving it from code to contracts, from modules to networks, and from local assumptions to enterprise-wide semantic confusion.
That is why domain design comes first.
If you decompose around bounded contexts and business capabilities, microservices can create genuine autonomy. If you decompose around shared entities, table ownership, or organizational convenience, you get dependency explosion topology: a graph of services tied together by replicated business facts they do not truly own.
The practical discipline is clear:
- design around domain semantics,
- keep ownership local to business decisions,
- replicate data deliberately,
- prefer domain events over raw data exhaust,
- build reconciliation into the architecture,
- and migrate progressively with strangler patterns and anti-corruption layers.
Kafka helps. APIs help. Cloud platforms help. None of them can rescue a weak domain model.
A monolith with good boundaries is often healthier than microservices with bad ones. And a well-designed microservice estate is not one with the most services. It is one where teams can answer, plainly and confidently: what do we own, what do we depend on, and what happens when the data disagrees?
That is the architecture test that matters.
Frequently Asked Questions
What is a service mesh?
A service mesh is an infrastructure layer managing service-to-service communication. It provides mutual TLS, load balancing, circuit breaking, retries, and observability without each service implementing these capabilities. Istio and Linkerd are common implementations.
How do you document microservices architecture for governance?
Use ArchiMate Application Cooperation diagrams for the service landscape, UML Component diagrams for internal structure, UML Sequence diagrams for key flows, and UML Deployment diagrams for Kubernetes topology. All views can coexist in Sparx EA with full traceability.
What is the difference between choreography and orchestration in microservices?
Choreography has services react to events independently — no central coordinator. Orchestration uses a central workflow engine that calls services in sequence. Choreography scales better but is harder to debug; orchestration is easier to reason about but creates a central coupling point.