Message Router
"A Message Router consumes a Message from one Message Channel and republishes it to a different Message Channel depending on a set of conditions." — Hohpe & Woolf, Enterprise Integration Patterns, 2003
Intent
The Message Router decouples message producers from consumers by routing messages to the correct channel based on content, headers, or routing rules. Crucially, the router does NOT modify message content — it makes routing decisions only. This separation of concerns (route vs. transform) keeps both responsibilities independently testable and maintainable. Without a Message Router, the producer must know which consumers exist and call them directly; adding a new consumer requires changing the producer. With a router, the producer publishes to a single entry channel and the routing table handles the dispatch.
Three primary variants cover the full routing problem space. The Content-Based Router (most common) examines message headers or body fields and routes to a statically configured destination. The Dynamic Router externalises the routing table — consumers register their routing preferences at runtime, and the router updates its table without restarting. The Recipient List is a fan-out variant that sends the message to multiple destinations simultaneously; it is distinct from a Publish-Subscribe Channel (which delivers to ALL registered subscribers) because the recipient list is computed per message.
The CQRS command bus (Phase 12) is a specialised Message Router: each command type maps to exactly one registered command handler. Understanding Message Router as the generalisation makes the CQRS dispatch mechanism immediately recognisable.
When NOT to Use
- Static single-destination routing — a direct connection or function call is simpler and has no routing overhead
- Routing logic changes so frequently it becomes a maintenance bottleneck — consider an event bus with subscriber-side filtering instead (the subscribers filter, not the router)
- Fan-out to ALL consumers is always required — use Publish-Subscribe Channel instead (Recipient List is a targeted subset fan-out, not a broadcast)
- When the "router" is also doing message transformation — separate concerns: route without transforming; transform without routing. Combining them makes both sides harder to test.
When to Use
- Multiple downstream consumers with different message types (order events → order service, payment events → payment service)
- Decoupling producers from topology changes — adding or removing a consumer does not touch the producer
- Replacing long
if/elsechains in message handlers with a declarative routing table - CQRS command routing: dispatch each command type to its registered handler
- API gateway routing: route incoming HTTP requests to the correct microservice by path or content
How It Works
The router inspects each incoming message and applies routing rules to select the destination channel. The three primary variants:
- Content-Based Router — static routing table keyed on message type, header value, or body field. Simple and fast; table changes require redeployment.
- Dynamic Router — routing table updated at runtime. Consumers send "registration" messages telling the router which message types they handle. No redeployment needed for topology changes.
- Recipient List — routing logic computes a list of destination channels per message. Sends one copy to each channel. Used for selective fan-out (e.g., notify only the affected regional services).
Routing logic lives in the router, not in consumers. A consumer that inspects a message and ignores it if the type is wrong has moved routing logic into the consumer — this breaks single responsibility and makes the routing topology implicit.
Flow Diagram
flowchart TD
IN["Incoming Message"]
R{"Router<br/>inspect message type"}
IN --> R
R -->|"type = OrderCreated"| CH1["Order Channel"]
R -->|"type = PaymentReceived"| CH2["Payment Channel"]
R -->|"type = ShipmentReady"| CH3["Shipping Channel"]
R -->|"unknown type"| DLQ["Dead Letter Queue"]
subgraph Content-Based Router
R
end
style R fill:#fff3e0,stroke:#f57c00
style DLQ fill:#ffebee,stroke:#c62828
TypeScript Example
// Message Router — TypeScript (content-based routing over kafkajs message)
// Source: Hohpe & Woolf, Enterprise Integration Patterns, 2003
type Handler = (payload: unknown) => void;
function contentBasedRouter(
message: { type: string; payload: unknown },
routes: Record<string, Handler>
): void {
const handler = routes[message.type];
if (handler) handler(message.payload);
else console.warn(`No route for type: ${message.type}`);
}
// Usage: contentBasedRouter(msg, { 'OrderPlaced': handleOrder, 'PaymentFailed': handlePayment });Java Example
// Message Router — Apache Camel Java DSL (Content-Based Router)
// Source: camel.apache.org/components/4.14.x/eips/message-router.html
from("direct:incoming")
.choice()
.when(simple("${header.type} == 'order'"))
.to("direct:orderHandler")
.when(simple("${header.type} == 'payment'"))
.to("direct:paymentHandler")
.otherwise()
.to("direct:deadLetter");Lineage Backward
- Chain-of-Responsibility-Pattern — CoR chains handlers so each decides whether to handle or pass the request to the next; Message Router uses a declarative routing table to dispatch to a designated channel. In CoR the handler chain is implicit and sequential; in Message Router the routing table is explicit and the destination is a channel, not a handler instance.
- Command-Pattern — Command encapsulates a request as an object. A CQRS command bus is a Message Router that dispatches Command objects to their registered handlers; the Command pattern is the ancestor that made the dispatch relationship possible.
Lineage Forward
- CQRS-Pattern (Phase 12) — The CQRS command bus is a specialised Message Router: each command type maps to exactly one command handler. The query side uses similar routing to dispatch queries to read model handlers. Message Router is the generalisation that makes CQRS dispatch recognisable.
- Choreography-Saga-Pattern (Phase 13) — Saga choreography steps are triggered by routing domain events to the correct saga handler. Each event type routes to the next saga step, making the event-driven saga a specialised content-based routing chain across service boundaries.
Related Concepts
| Pattern | Relationship |
|---|---|
| Pipes-and-Filters | Pipes and Filters transforms the message at each stage; Message Router routes without transforming — orthogonal concerns |
| Chain-of-Responsibility-Pattern | CoR: first matching handler stops the chain; Message Router: declarative table selects one designated channel |
| CQRS-Pattern | CQRS command bus is a specialised Message Router — one command type, one handler |
| Choreography-Saga-Pattern | Saga choreography is a content-based routing chain across service boundaries triggered by domain events |
Related API Design Patterns
- REST-API-Design — API gateway routing is listed as a Message Router use case; REST API design defines the HTTP routing contract that gateway Message Routers apply
Sources
- Hohpe, G. & Woolf, B. (2003). Enterprise Integration Patterns. Addison-Wesley.
- https://www.enterpriseintegrationpatterns.com/MessageRouter.html
- https://camel.apache.org/components/4.14.x/eips/message-router.html