Smart endpoints and dumb pipes

This page is published under the terms of the licence summarized in the footnote.


In a monolithic application, the various components are just a procedure call away.

But in a distributed software architecture, components are distributed across (virtual or real) machines.

So, how to integrate application components in a distributed software architecture?


Martin Fowler’s 2014 paper defining microservices recommends integrating application components using principle of “smart endpoints and dumb pipes”.

The example below is edited from an article by Nathan Peck (Sep 1 2017) that elaborates on Fowler’s concept, and explains modern techniques for integrating application components.

For compatibility with TOGAF and Avancier Methods I’ve replaced almost all microservice by application component.




Overview.. 1

Integration design patterns. 1

Integration technologies. 2

Integration considerations. 2

Integration patterns - an example. 3

The use case: sign up to create an account 3

An anti-pattern: Hub and Spoke. 4

Integration technologies. 5

Request-Reply technologies. 5

Observer technologies. 7

Conclusion. 8



Integration design patterns

The two primary forms of communication between application components are here called Request-Reply and Observer.


Synchronous request-reply

A client component invokes a server component by making an explicit request, usually to store or retrieve data.

The client component waits for a response, be it a resource or a simply an acknowledgement.


Asynchronous observer

One component publishes an event.

One or more observers (watching for that event) respond by running logic asynchronously, outside the awareness of the event producer.


For some, loose-coupling via messaging has become a mantra, but there are always trade offs, as this table shows.


Couple via synchronous request/reply for

Decouple via asynchronous messages/events for




Scale (very high volume and/or throughput)

Adaptability/Manageability (of smaller distributed components)

Availability (of smaller units of work) and Partition Tolerance


Integration technologies

Martin Fowler points out that many put significant intelligence into the integration mechanisms.

An Enterprise Service Bus (ESB) includes sophisticated facilities for message routing, choreography, transformation, and applying business rules.

Components are integrated using complex protocols such as WS-Choreography or BPEL.


By contrast “smart endpoints and dumb pipes” means integrating application components that own their own domain logic.

And components are integrated (choreographed or orchestrated) using simple protocols and/or lightweight messaging.


The simple protocols are ones the world wide web (and to a large extent, Unix) is built on.

Typlically, messages are sent using HTTP to remote components/resources that are identified using domain names.

Oft-used resources can be cached locally (with little effort).


The lightweight messaging technology is dumb - acts as a message router only.

Simple messaging products (as RabbitMQ or ZeroMQ) do little more than provide reliable message queues.

Integration considerations

The term micro implies subdividing something that could have been macro.

As always, there are trade offs.


Component size

Process length

Message quantity

Macro pattern

larger components perform

longer processes

with less inter-component messaging

Micro pattern

smaller components perform

shorter processes

with more inter-component messaging


Distributing components tends to mean designers have to consider two things


Reducing message frequency

Local procedure calls are simple, fast, reliable and cheap.

Designers don’t hesitate to design frequent communication between fine-grained operations.


Remote procedure calls and messaging technologies are more complex, slower, less reliable and more costly,

Designers need to consider making communication less frequent communication and operations coarser -grained.


Compensating transactions for abortive business processes

Asynchronous integration tends to mean accepting user commands and embarking on business processes that later have to be abandoned.

Contrary to design for simplicity, designers need to consider compensating transactions.

This issue is explored in our paper on CQRS.


Integration patterns - an example

Understanding where and when to use a Request-Reply model versus an observer model is a key to designing effective application component integration.

To illustrate these two types of communication consider the following software architecture for a basic social media application.

The use case: sign up to create an account

The user enters their basic profile details such as name, password, email address, etc.

The users’s client device makes a synchronous (?) POST request to an API endpoint


Request-reply pattern

The API endpoint sends the request on to two backend components.

·         a core user metadata component that stores the basic profile information,

·         a password component that hashes the password plaintext and stores it for later comparison when the user logs in.

These two initial requests are explicit Request-Reply communications.

The user signup endpoint can’t return a “200 OK” response to the client until both of these pieces of data have been persisted in the backend.


Observer pattern

By contrast, there are two more application components that run in the background to process the user sign up..

·         one component emails the user asking them to click a link to verify their email address.

·         another component starts searching for friends to recommend to the user.

The client shouldn’t have to wait for a verification email to be sent out, or friend recommendations to be generated.

For these two features it is better to have an observer pattern where the backend components are watching for the user signup event and are triggered asynchronously.

So, the API endpoint also publishes an event that backend application components can process asynchronously.


User signup for a social media application

Client component

Data flow

API end point

Data flow

Server components

Mobile device


User Signup





User Signup Event

Email Verification

Friend Recommendation


Diagram version


An anti-pattern: Hub and Spoke

Building a complex centralized communication bus that runs logic to route and manipulate messages is an architectural pitfall that tends to cause problems later on.

Instead, microservices favor a decentralized approach: components use dumb pipes to get messages from one endpoint to another endpoint.




At first glance, a hub and spoke diagram with a central bus looks less scary than a network of many direct point-to-point connections.

However, the tangle of connections still exists in the central bus - embedded in one monolithic component.

This central monolith has a tendency to become excessively complex and can become a bottleneck both from a performance standpoint as well as an engineering standpoint.


Decentralized application component integrations enable teams to work in parallel on different edges of the architecture without breaking other components.

Each application component treats other components as external resources with no differentiation between internal components and third party resources.

This means each application component encapsulates its own logic for formatting its outgoing responses or supplementing its incoming requests.

This allows teams to independently add features or modify existing features without needing to modify a central bus.

Scaling the application component integrations is also decentralized so that each component can have its own load balancer and scaling logic.

Integration technologies

Request-Reply technologies

Request-Reply communication is used anywhere one component sends a request and expects either a resource or acknowledgment response to be returned.

The most basic way to implement this pattern is using HTTP, ideally following REST principles.

A standard HTTP based communication pipeline between two application components typically looks like this:


Client Component


Load Balancer

Server component 1

Server component 1

Server component 3


Diagram version


In this approach, a simple load balancer can sit in the middle.

The originating component can make an HTTP request to a load balancer.

The load balancer can forward the request to one of the instances of the backing application component.


However, in some cases there is extremely high traffic between components, or teams want to reduce latency between application components as much as possible.

In this case they may adopt thick client load balancing.

This approach typically uses a system such as Consul, Eureka, or ZooKeeper to keep track of the set of application component instances and their IP addresses.

Then the originating application component is able to make a request directly to an instance of the backing component that it needs to talk to.


Client Component

DNS Record


Server component 1


Server component 1


Server component 3



Diagram version



Consul maintains a DNS record that resolves to the different backend application component instances.

So that one component can talk directly to another component, with no load balancer in-between.


Another framework is GRPC, which has emerged as a strong convention for polyglot applications.

GRPC can operate using an external load balancer, similar to the HTTP approach above, or it can also use a thick client load balancer.

The distinguishing characteristic of GRPC, however, is that it translates communication payloads into a common format called protocol buffers.


Diagram version




Protocol buffers allow backend components to serialize communication payloads into an efficient binary format for travel over the wire.

But then deserialize it into the appropriate object model for their specific runtime.

Observer technologies

Observer communication is critical to scaling out components.

Not every communication requires a response or an acknowledgement.

In fact, in many workflows there are at least a few pieces of logic that should be fully asynchronous and non-blocking.


The standard way for distributing this type of workload is to pass messages using a broker component, ideally one that implements a queue.

RabbitMQ, ZeroMQ, Kafka, or even Redis Pub/Sub can all be used as dumb pipes that allow an application component to publish an event,

while allowing other application components to subscribe to one or more classes of events that they need to respond to.


Diagram version



Organizations running workloads on Amazon Web Services often favor using Amazon Simple Notification Service (SNS),

and Amazon Simple Queue Service (SQS) as a fully managed solution for broadcast communication.

These components allow a producing component to make one request to an SNS topic to broadcast an event.

While multiple SQS queues can be subscribed to that topic, with each queue connected to a single application component that consumes and responds to that event.



The huge advantage of this approach is that a producing component need not how many subscribers there are to the event, or what they are doing in response to the event.

In the event of a consumer failure most queuing systems have a retry / redelivery feature to ensure that the message is eventually processed.

The producer can just “fire and forget”, trusting that the message broker’s queue will ensure that the message eventually reaches the right consumers.

Even if all consumers are too busy to respond to the event immediately, the queue will hold onto the event until a consumer is ready to process it.



Another benefit of the observer model is future extendability of a system.

Once an event broadcast is implemented in a producer component, new types of consumers can be added and subscribed to the event after the fact without needing to change the producer.

For example, in the social media application at the start of the article there were two consumers subscribed to the user signup event (for email verification, and friend recommendation).

Engineers could easily add a third component that responded to the user signup event by emailing all the people who have that new user in their contacts to let them know that a contact signed up.

This would require no changes to the user signup component, eliminating the risk that this new feature might break the critical user signup feature.


The observer model is a very powerful tool, and no software architecture can reach its full potential without having at least some observer-based communications.


The principle of smart endpoints and dumb pipes is easy to understand when you embrace the concept of decentralization of architecture and logic.

Despite using “dumb pipes” application components can still implement essential messaging primitives without the need for a centralized Message Bus.

Instead, application components should make use of the broad ecosystem of frameworks that exist as dumb pipes for both Request-Reply and observer communications.



Footnote: Creative Commons Attribution-No Derivative Works Licence 2.0                        10/01/2019 12:44

Attribution: You may copy, distribute and display this copyrighted work only if you clearly credit “Avancier Limited:” before the start and include this footnote at the end.

No Derivative Works: You may copy, distribute, display only complete and verbatim copies of this page, not derivative works based upon it.

For more information about the licence, see