⏱ 21 min read
Most routing is accidental architecture.
Teams rarely set out to make routing a strategic mistake. They inherit an API gateway, add a few path rules, sprinkle in some Kafka topics, wire up service discovery, and call it a day. A year later, nobody can explain why /customer/{id}/orders lands in one service while OrderPlaced fans out through three others, two of which think a “customer” is an account holder and the third thinks it is a legal entity. The routes work. The business does not.
This is the quiet failure mode of many distributed systems: requests and events move correctly, but meaning does not. The packets arrive. The semantics rot.
Domain-centric routing is an attempt to put the meaning back into movement. It says a route is not merely a network concern, nor just an integration concern. It is a domain decision. The path a command takes, the topic an event lands on, the queue that absorbs a state transition, the policy that chooses one bounded context over another — these are all expressions of the business model. If your architecture claims to be domain-driven, your routing had better be too.
That sounds obvious. In practice, it is anything but. Enterprises are full of systems where routing follows org charts, deployment histories, or whichever team got there first. A customer update goes to CRM because “that’s where customer lives,” except support stores its own customer profile, billing maintains payment identities, and fulfillment has shipping recipients that the business also calls customers. The router becomes a diplomatic compromise between inconsistent models. Diplomacy is useful in politics. In architecture, it usually means the domain has gone missing.
So this article argues a simple, opinionated point: routing should align with bounded contexts and domain semantics first, and only then with technical topology. That applies to synchronous APIs, asynchronous messaging, Kafka event streams, command dispatch, orchestration, anti-corruption layers, and migration paths from legacy estates. It is not just a style choice. It changes ownership, coupling, reconciliation, observability, and the way failures propagate through the enterprise. event-driven architecture patterns
And yes, there are places where you should not use it. Not every route deserves a philosophy. But when your business model matters, and especially when your architecture spans multiple teams and systems of record, domain-centric routing is one of the few patterns that makes the software tell the same story as the business.
Context
Domain-Driven Design gave us a language for handling complexity without pretending complexity would go away. Bounded contexts, ubiquitous language, aggregates, context maps — these are not decorative concepts. They are mechanisms for drawing lines around meaning.
Routing cuts across all of them.
In a monolith, routing often feels mechanical. A URL maps to a controller; a method invokes an application service; the database transaction does the rest. The domain model can stay coherent because the whole system shares one deployment unit, one data consistency regime, and usually one team’s mental model.
In distributed systems, that coherence fractures. What used to be an internal method call becomes an API request. A transaction becomes a saga. A local domain event becomes a Kafka topic with retention rules and replay semantics. The route itself becomes part of the architecture. Once that happens, the question “where does this request go?” is no longer technical trivia. It becomes shorthand for “which model is authoritative for this decision?”
That is where domain-centric routing starts.
Instead of routing based solely on endpoint shape, service availability, geography, or protocol, the architecture routes according to domain meaning. A command to approve a loan goes to the lending context, even if customer data was supplied by onboarding. An event about inventory reservation belongs to fulfillment semantics, not generic stock semantics. A query for product availability might compose data from catalog, inventory, and pricing, but the route should still reflect the domain responsibility for answering that business question.
The key shift is this: the router is not deciding who can technically respond. It is deciding which bounded context owns the decision, state transition, or interpretation.
That sounds stricter than many enterprise environments are used to. Good. Soft boundaries make expensive systems.
Problem
Most enterprise routing is infrastructure-led.
Paths are defined at the API gateway because that is where path rules belong. Event topics are defined by middleware teams because that is where brokers live. Integration flows are defined by ESB or iPaaS teams because that is where mappings happen. The result is a patchwork where routing logic sits far from domain experts and even farther from domain language.
Then the trouble starts.
The first problem is semantic leakage. A route designed around technical endpoints starts exposing internal service structures rather than business capabilities. You see URLs like /svc-billing-v2/customerSync or Kafka topics like common.customer.updated, which tell you more about platform history than domain meaning. Every consuming team interprets the payload differently. “Updated” means billing address changed for one service, tax identity changed for another, and KYC verification changed for a third.
The second problem is authority confusion. If several services can answer a question or react to an event, the route often becomes the accidental authority model. Whichever system receives the message first behaves as though it owns the concept. In time, duplicate validations and duplicate state emerge. Enterprises call this resilience. Usually it is drift.
The third problem is migration paralysis. Legacy estates do not disappear cleanly. During modernization, old and new systems coexist. If routing is tied to technical topology rather than bounded contexts, every migration becomes a brittle cutover exercise: switch all traffic from old to new and pray. Domain-centric routing gives you a subtler lever: route specific commands, entities, decisions, or customer segments to the context that now owns them.
The fourth problem is reconciliation debt. Distributed systems live with delayed consistency. But not all inconsistency is equal. Some are acceptable lags; others are breaches of domain integrity. Without domain-aware routing, teams cannot tell which is which. They build generic reconciliation jobs to compare databases after the fact. This is not architecture. This is archaeology.
And finally, there is the human problem. When routes are not aligned to domain semantics, nobody knows where to put new behavior. Teams ask, “Which service should handle this?” and the answer is usually “the one that already has something similar.” Similar is how domains collapse.
Forces
Several forces pull against each other here.
Business meaning versus technical convenience.
It is easier to route by URL path prefixes, topic names, tenant shards, or deployment locality. It is harder to route by domain ownership because that requires actual modeling. But business coherence is worth more than infrastructure neatness.
Autonomy versus consistency.
Bounded contexts want independence. Enterprises want one version of truth. You do not get both in absolute form. Domain-centric routing helps because it makes authority explicit: one context owns the decision, others consume the outcome.
Latency versus correctness.
A direct route to a local service is fast. A route that respects domain boundaries may require an extra hop, an anti-corruption layer, or asynchronous propagation. That cost can be justified. Sometimes it cannot.
Reuse versus clarity.
Teams often create “shared” routers, “common” topics, and “generic” schemas to avoid duplication. The trouble is that shared semantics are usually fantasy semantics. They are vague enough for everyone to adopt and precise enough for no one to trust.
Migration urgency versus architectural patience.
Transformation programs are under pressure. Leaders want to retire legacy systems quickly. Domain-centric routing usually recommends progressive strangler patterns, coexistence, dual reads, reconciliation, and careful redirection of business capabilities. It is slower at first. It is faster than a failed big bang.
Solution
Domain-centric routing means requests, commands, events, and queries are directed according to domain ownership and bounded-context semantics, not just network topology or service structure.
There are several practical rules behind that sentence.
First, route commands to the context that owns the invariant. If a business action can violate a core rule, the command must land where that rule lives. “Approve claim,” “close account,” “allocate inventory,” “activate subscription” — these are not integration events; they are domain decisions. Their routes should reflect that.
Second, publish events from the context that experienced the state change. Not a central integration team. Not a synchronization service. The source context emits the fact in its own language. Other contexts translate as needed using anti-corruption layers or consumer-specific adapters.
Third, route queries according to information responsibility, not data location alone. Sometimes that means a dedicated read model or composition layer. A reporting store may physically hold the data, but it is not always the right semantic owner of the answer.
Fourth, make routing policies explicit in the architecture. If premium customers are handled by a new underwriting context while the rest remain in legacy, that policy should be visible and governed. Hidden conditional routing becomes institutional folklore.
Fifth, treat translation as part of routing. In real enterprises, routes cross bounded contexts with different models. The move from “Customer” in CRM to “PolicyHolder” in insurance or “Borrower” in lending is not payload mapping. It is semantic translation. Put it where the boundary is visible.
A simple way to think about it: the route should answer, “Who gets to interpret this?” not merely “Who is listening?”
A conceptual routing model
The important detail is not the gateway or Kafka. It is the policy layer expressing domain decisions. In a mature architecture, that policy may live partly in gateways, partly in message headers, partly in orchestration flows, and partly in service contracts. The implementation can vary. The principle should not.
Architecture
A useful domain-centric routing architecture typically has five elements.
1. Entry-point routing by business capability
At the edge, channels should address business capabilities, not internal service decomposition. That means APIs such as /claims/{id}/approve or /subscriptions/{id}/suspend are better than endpoints that mirror deployment units. The gateway’s responsibility is then to route based on capability semantics and version policy, not merely static path rewriting.
This is where teams often make the first wrong turn. They expose “customer” as a universal concept because every system has one. But in DDD, universal concepts are where meaning goes to die. The entry point should represent a use case in a bounded context, not a master noun with fifty interpretations.
2. Command routing to invariant owners
A command should land in the bounded context that enforces the business rule. This sounds trivial until you examine enterprise systems with overlapping authority. For example, a telecom company may have CRM, billing, product catalog, order management, and provisioning. A “change plan” request touches all five. Which one owns the command? If the commercial validity of the new plan is the critical invariant, then product/order semantics must lead, with downstream effects propagated to billing and provisioning.
This is why command routing is architecture, not plumbing. It encodes who is allowed to say yes.
3. Event routing with semantic integrity
Kafka fits naturally here, but only if you resist using it as a giant synchronization bus. Topics should represent meaningful event streams from bounded contexts. Avoid “global customer events” unless the enterprise genuinely has a single shared customer model, which it usually does not.
A better pattern is context-specific streams: lending.loan-approved, fulfillment.inventory-reserved, billing.invoice-issued. Consumers derive what they need. If another context needs a translated event, create an explicit translation flow. Hidden semantic adaptation in every consumer guarantees drift.
4. Translation at context boundaries
Anti-corruption layers are not old DDD nostalgia. They are survival equipment. If your route crosses from a legacy policy administration system into a modern claims platform, the translation layer should sit on the seam and preserve each model’s integrity.
This is where many migrations fail. Teams route traffic directly into the new service and dump legacy payloads inside it “temporarily.” Temporary semantic corruption lasts longer than most executive sponsors.
5. Reconciliation as a first-class concern
Domain-centric routing does not eliminate inconsistency. It makes inconsistency legible.
If a command is routed to the new context but a downstream legacy system still needs synchronized state, you need reconciliation mechanisms: idempotent consumers, outbox patterns, replayable event logs, compensating actions, and periodic verification of critical aggregates. Reconciliation is not a batch afterthought. During migration, it is part of the runtime architecture.
Here is a typical architecture view.
Notice what is absent: there is no generic enterprise router pretending all flows are interchangeable. There are capability routes, context boundaries, and explicit translation and reconciliation.
That is deliberate.
Migration Strategy
The migration story is where domain-centric routing earns its keep.
Most enterprises do not get to design from a blank sheet. They have a monolith, maybe several. They have package systems, vendor platforms, ESBs, nightly batch jobs, and Kafka introduced halfway through the modernization program as though event streaming alone could absolve years of semantic debt.
It cannot.
A sensible migration uses a progressive strangler approach, but with routing aligned to domain seams, not UI pages or random APIs. The sequence usually looks like this:
Step 1: Identify bounded contexts and decision ownership
Do not start with services. Start with invariants, authority, and language. Ask: which part of the business gets to decide this? Where do terms diverge? Which workflows are actually one capability wearing several technical names?
If you skip this and carve out services directly from code modules, you will just externalize your monolith’s confusion.
Step 2: Introduce a capability router
Place a routing policy layer in front of legacy and new implementations. This can be an API gateway plus a command dispatcher, a workflow engine, or a dedicated orchestration component depending on complexity. The point is to create a stable business-facing contract while the implementation behind it evolves.
Step 3: Move one domain decision at a time
Route a specific command or capability to the new bounded context first, not all read and write traffic at once. A classic example is moving “loan approval” before “loan reporting” or moving “inventory reservation” before “inventory history.”
This limits blast radius. It also forces the team to prove the new context can own an invariant, not merely store a copy of the data.
Step 4: Publish events and reconcile downstream state
Once the new context becomes authoritative for a decision, emit events and feed downstream systems. During coexistence, legacy systems may still require updates. That means dual-running semantics without dual-writing from the entry point. The new authority publishes; adapters synchronize; reconciliation checks the estate.
Step 5: Expand routing scope gradually
Increase the portion of traffic or cases routed to the new context. This can be by product line, geography, customer segment, legal entity, or feature. The trick is to choose routing dimensions that the business understands. “Blue-green by hash modulo” is operationally neat and domain-blind. “New mortgage products in region A” is ugly but meaningful.
Step 6: Retire legacy authority, then legacy read dependency
Too many migrations stop halfway. Writes move to the new context, but reads still depend on legacy databases because the new read models are incomplete. That leaves routing permanently split and ownership perpetually ambiguous. Finish the journey: move authority first, then information responsibility.
Progressive strangler routing
The hard part is not the route split. It is maintaining semantic consistency while two worlds coexist.
Reconciliation during migration
Reconciliation deserves blunt language: if you are migrating domain authority and you have no reconciliation strategy, you are not migrating; you are gambling.
Reconciliation should cover:
- key aggregate state comparisons
- event delivery gaps
- duplicate processing detection
- idempotency verification
- stale reference data
- business-level mismatches, not just row counts
A bank, for example, might reconcile approved loan counts, disbursed amounts, and customer exposure calculations between the new lending context and legacy general ledger feeds. Merely confirming message delivery is not enough. You need to confirm that the business consequences match.
Enterprise Example
Consider a large insurer modernizing claims handling.
The legacy estate has a policy administration system, a claims mainframe, a CRM package, and a billing platform. Over the years, each system grew its own idea of customer, policy, incident, and coverage. The firm wants to build a modern claims platform with microservices and Kafka. microservices architecture diagrams
The naive approach is predictable: create services named Customer, Policy, Claim, Payment, Document; route API calls by noun; publish enterprise topics like customer.updated and claim.changed; synchronize everything everywhere. This looks modern in a PowerPoint and collapses in production. Claims adjudication starts depending on CRM customer records; billing interprets claim status transitions as financial triggers; the policy team insists their coverage model remains authoritative; integration logic spreads into every service.
A better approach starts with bounded contexts:
- Policy Administration owns policy terms and coverage interpretation.
- Claims Handling owns claim intake, assessment, adjudication, and lifecycle.
- Billing owns financial obligations and payouts.
- Customer Engagement owns contact preferences and service interactions.
- Fraud/Risk owns suspicion scoring and investigation triggers.
Now routing becomes different.
A Submit Claim command routes to Claims Handling, not Customer or Policy. Claims then queries policy coverage through a translated view or policy service contract. A Coverage Determined event may be internal to claims processing or emitted with careful semantics depending on enterprise needs. A Payout Authorized event goes to Billing because billing owns the financial consequence. Customer Engagement receives notifications as a downstream concern, not as the owner of any claim decision.
During migration, some product lines still adjudicate on the mainframe. So the capability router uses product and region as routing dimensions. Motor claims in one country go to the new Claims Handling context; complex commercial claims still go to legacy. Both publish domain events into Kafka, but consumers do not pretend they are identical. Translation components normalize where necessary for enterprise reporting, while reconciliation verifies paid amounts and open-claim exposures across old and new flows.
This is what enterprise architecture looks like when it respects the domain: not one universal process, but one coherent story per bounded context.
Operational Considerations
A domain-centric routing design changes operations in specific ways.
Observability must include domain identity.
Tracing by service name is not enough. You need correlation IDs, causation IDs, aggregate identifiers, routing-policy versions, and business keys. When a command was routed to the wrong context, the incident report should say more than “HTTP 200 followed by stale Kafka consumer offset.”
Kafka topic governance matters.
Topic names, schemas, retention, replay rights, and consumer contracts should reflect bounded contexts. Shared “enterprise event” topics quickly become semantic landfills. Prefer clear ownership and explicit downstream transformations.
Idempotency is non-negotiable.
Once routing becomes conditional and migrations are progressive, duplicates will happen. Replays will happen. Consumers must survive them.
Routing policies require change control.
If routing by product line or legal entity determines which system is authoritative, those rules belong under architectural governance and business sign-off, not buried in gateway config pushed on Friday evening. EA governance checklist
Read models need freshness policies.
Queries often use projections from multiple domains. Teams must define acceptable staleness and fallback behavior. Without this, users interpret an eventually consistent screen as a bug, and they are often right.
Security follows domain boundaries too.
Authorization should be based on business capability and bounded-context ownership, not just endpoint access. A route into a context is a statement about which team and role may invoke which decision.
Tradeoffs
This pattern is powerful, but not free.
The biggest tradeoff is complexity up front. Domain-centric routing forces architectural decisions early: who owns what, what terms mean, where translation lives, which context is authoritative. That is real work. Technical routing avoids it for a while. “For a while” is expensive.
Another tradeoff is additional hops. A route that preserves semantics may involve a capability router, an anti-corruption layer, and event propagation. That can add latency and failure points. If your use case is ultra-low-latency market making, this may not be your first concern.
There is also a governance burden. Domain-centric routing only works if the enterprise actually maintains context maps, schemas, contracts, and ownership. Without discipline, it degrades into ad hoc rules with better vocabulary.
And there is the classic tradeoff of local optimization versus enterprise coherence. A team may be able to answer a request faster by bypassing the context that owns the invariant. They should usually resist. Shortcuts become architecture faster than policies do.
Failure Modes
There are several recurring ways this goes wrong.
1. The faux domain router.
The architecture claims domain-centric routing, but the routes still mirror service topology. Business names are painted on technical endpoints. Nothing changes except the labels.
2. Shared canonical delusion.
An enterprise creates one canonical schema for customer, order, product, or claim and routes everything through it. Translation is suppressed in the name of standardization. In reality, every context still means something different by the same words. The canonical model becomes vague and bloated.
3. Routing by data ownership instead of decision ownership.
Teams route commands to whichever system stores the entity. That is often wrong. The system that stores customer contact details does not necessarily own customer credit decisions.
4. Hidden split authority.
During migration, both legacy and new systems can make the same decision depending on edge cases no one documented. This produces contradictory outcomes and impossible reconciliation.
5. Event abuse.
Kafka becomes a substitute for thinking. Services emit every state mutation as an enterprise event, consumers infer commands from events, and routing turns into emergent behavior. If nobody can say which context owns the decision, you do not have event-driven architecture; you have gossip.
6. Reconciliation theater.
Teams build dashboards that show message throughput and call it reconciliation. Meanwhile, business totals diverge. Real reconciliation checks business effects, not middleware health.
When Not To Use
Do not use domain-centric routing everywhere.
If you have a small monolith with a coherent domain model and one team, adding an explicit routing layer may be needless ceremony. The domain can be protected within the codebase without external routing complexity.
Do not force it onto pure CRUD back-office applications where there are few meaningful invariants and little ambiguity about ownership. Sometimes a table maintenance screen is just a table maintenance screen.
Be cautious in ultra-high-throughput or ultra-low-latency systems where every hop matters and the domain boundaries are already deeply embedded in the runtime model. In those environments, the concepts may still apply, but the implementation will look different and much leaner.
And do not use it as a substitute for domain modeling. If your organization cannot agree on bounded contexts, routing rules will not save you. They will simply fossilize the disagreement.
Related Patterns
Several patterns sit naturally beside domain-centric routing.
Bounded Contexts are the foundation. Without them, routing has no semantic anchor.
Anti-Corruption Layer is essential when routes cross legacy or external models.
Strangler Fig Pattern is the migration mechanism that allows routing to shift progressively from old to new implementations.
Outbox Pattern supports reliable event publication from authoritative contexts.
Saga / Process Manager can coordinate long-running cross-context workflows, though it should not erase ownership by centralizing all decision logic.
CQRS often complements domain-centric routing because query routes and command routes usually have different semantic responsibilities.
Context Maps help make upstream/downstream, conformist, and partnership relationships explicit. They are not diagrams for architects alone. They explain why certain routes exist.
Summary
Routing is one of those architectural concerns that looks technical until the day it breaks the business.
When requests and events flow without respect for bounded contexts, domain meaning dissolves. Teams route to whoever can answer, not whoever should decide. Integration multiplies. Authority blurs. Migrations stall. Reconciliation becomes a permanent tax.
Domain-centric routing is the corrective. It routes commands to invariant owners, events from semantic sources, queries by information responsibility, and translations at context boundaries. It works especially well in enterprise modernization, where progressive strangler migration, Kafka-based propagation, and reconciliation are unavoidable realities rather than optional patterns.
The idea is simple enough to remember: route by meaning before mechanism.
That does not make the architecture simpler. It makes it honest.
And honest architectures age better.
Frequently Asked Questions
What is enterprise architecture?
Enterprise architecture aligns strategy, business processes, applications, and technology in a coherent model. It enables impact analysis, portfolio rationalisation, governance, and transformation planning across the organisation.
How does ArchiMate support architecture practice?
ArchiMate provides a standard language connecting strategy, business operations, applications, and technology. It enables traceability from strategic goals through capabilities and services to infrastructure — making architecture decisions explicit and reviewable.
What tools support enterprise architecture modeling?
The main tools are Sparx Enterprise Architect (ArchiMate, UML, BPMN, SysML), Archi (free, ArchiMate-only), and BiZZdesign. Sparx EA is the most feature-rich, supporting concurrent repositories, automation, scripting, and Jira integration.