What Does ‘Smart Endpoints and Dumb Pipes’ Mean? Posted in Platforms Kristopher Sandoval January 27, 2022 The tech development space is dominated by mantras — sayings that make sense in context but are repeated away from their original source. These expressions may carry meaning for the initiated but are often seen as complicated jargon to others. One such concept is “smart endpoints and dumb pipes.” Accredited to Martin Fowler, the idea is helpful to consider when designing microservices architectures. The basic premise of “smart endpoints and dumb pipes” is that microservices should carry their own communication logic (the “endpoints”), and the carrier superstructure that transmits these messages (the “pipes”) should have as light a structure as possible. Why is this so? What’s the justification, and what’s the background upon which this conversation on pipes and endpoints exists? Below, we’ll attempt to clarify what the saying means. We’ll look at where it came from, how it applies to microservice development, and see why it’s a helpful expression for modern software architects to understand. The Problem of the Monolith We’ve talked at length about both monoliths and microservices, but it bears significant importance to this discussion, so we will again define them. Understanding Monolith The traditional approach to development is often referred to by a singular term — the monolith. Monolithic development adopts a centralized data storage and response. A holdover from the era of mainframes and clients, the basic idea is to create a vertical stack wherein everything is located and guided by a single, all-powerful system. This non-distributed approach meant that everything existed in one body. Software applications were self-contained and non-modular, with requests being handled by a singular entity. Often, these solutions were purpose-built, with many organizations utilizing a monolith due to their startup phases being singularly focused. This resulted in APIs that were non-modular, specific in intent and purpose to the point of rejecting extensibility and scalability in favor of stability and a functional response. There are some strengths to this kind of approach. First, it’s often cheaper to engage with at the start. Building something to do one thing — and only one thing — is often more affordable than building a system that allows for many things. In such a monolithic approach, the service is a one-to-one relationship — the server owns the data, how it’s transmitted, and how it’s serviced, making a clear communication pathway. Unfortunately, this means that monoliths are heavily siloed, lacking a malleable underlying codebase. Alterations or replacements carry an enormous cost in both time and resources. This can result in a sort of code paralysis just due to the sheer weight of the system. It can also result in a high cost to develop, maintain, and run. As a monolith grows larger, it remains constrained to its original design, incurring ever-growing expenses to add new components. This results in late-stage development that carries massive deployment burdens and slower release schedules. Understanding Microservices Microservices were designed to solve this problem. In essence, the microservice paradigm represents a sea change from isolated singular monoliths to a collection of smaller, single-purpose services that work in concert. With each service doing a specific thing, the idea is that those services would then work together to facilitate the core function. This shift from non-distributed to distributed, from centralized to decentralized, delivers significant benefits. First, the system becomes much more scalable. An application can slot in new services or change existing ones without adverse end-user experience changes. Iteration does not freeze new development, as you’re changing a small part of the greater subset. You can compare this to changing clothes — if your entire outfit was all sewn together, as in a monolith, you would be unable to change your jacket or put on a hat without a huge effort. A microservice allows you to, metaphorically speaking, change hats or jackets at will. This is possible because the individual parts are only connected by necessity, not enforcement. This also means microservices do not carry with them the problem of siloing. With components architected as microservices, the overall system can change, mold itself, and adapt to circumstances without affecting any other part. Business logic can be represented on the node with the closest affinity to its function, rather than being centralized into a singular node of control. This system does introduce additional complexity, as we’ll cover below, yet the benefits are so substantial that many software architects have adopted it whole-heartedly. The Solution: Smart or Dumb Pipes? While microservices fix many of the issues of the monolith, some unique problems arise. Microservices don’t isolate everything into a single vertical silo, which is great — systems can be spread across multiple nodes and instances, creating a lot of variability in resource location. This also, unfortunately, creates some communication complexity. Think about it this way — imagine you are trying to do a group project, but instead of everyone sitting at the same conference table, you are all several miles apart. This would introduce significant communication barriers, even while introducing more extensibility and scalability to your group. Microservices face this exact problem when it comes to facilitating inter-microservice communication. How do you solve that communication problem? There are really only two options: either you create smart pipes, or you create smart endpoints. You could solve the microservices communication problem by creating smart pipes, and many implementations have employed this solution. An excellent example of this type, the Enterprise Service Bus, allows for the logic and processing governing communication to be placed in a singular system, a facilitation center, to make communication stable and efficient. However, the problem with doing this is that you’ve created a vertical system of centralization — while the entities doing the work are decentralized, all communication is centralized to this ESB, resulting in a sort of quasi-monolithic approach. While an ESB is not itself bad — and to put a finer point, not necessarily a monolithic approach — taking it too far can certainly lead to a lot of the same issues incurred with a monolithic strategy. Smart Endpoints and Dumb Pipes A great solution is to go the other way. Instead of making your pipes smart, make your endpoints smart! In this system, you’re shifting the paradigm away from the idea of a communications bus and closer towards self-sufficient nodes. In essence, you are taking that business logic, communication logic, and general governance away from the communication node and are instead placing it firmly in the entities doing the talking. The core idea of “smart endpoints and dumb pipes” is that the microservices, when designed correctly, don’t need a bus to govern this communication. The services themselves can govern the logical breakout of communication without going through an intermediary. If the User Service needs to talk to the Authentication Service, for instance, all the logic for how that works can be held by those entities — including a third entity may increase governance and control, but it may be antithesis to the microservice solution. Martin Fowler’s coined this concept in his commentary on microservices. In it, he stated as thus: “The microservice community favours an alternative approach: smart endpoints and dumb pipes. Applications built from microservices aim to be as decoupled and as cohesive as possible – they own their own domain logic and act more as filters in the classical Unix sense – receiving a request, applying logic as appropriate and producing a response. These are choreographed using simple RESTish protocols rather than complex protocols such as WS-Choreography or BPEL or orchestration by a central tool.” To be clear, this doesn’t mean that no logic is located within the communication systems. The point is where the bulk of that logic resides. It’s one thing to have the “pipes” holding onto basic logic for managing asynchronous communication. Still, it’s a whole secondary thing to have them hold the complete logic for all communication and governance internally. Not only is it highly inefficient, but it may also be ineffective in actually facilitating communication. Lightweight logic systems can, in fact, be useful. Again, from the same commentary from Martin Fowler: “The second approach in common use is messaging over a lightweight message bus. The infrastructure chosen is typically dumb (dumb as in acts as a message router only) – simple implementations such as RabbitMQ or ZeroMQ don’t do much more than provide a reliable asynchronous fabric – the smarts still live in the endpoints that are producing and consuming messages; in the services.” This is really a solution that is more concerned with removing blockers than demanding a particular approach. The beauty of microservices lies in the freedom to adapt and change for different circumstances — adopting the concept of “smart endpoints and dumb pipes” simply allows that change and adaptation to be more fluid and independent of the communication controls and protocols more typical of centralized systems. Conclusion The best way to think about “smart endpoints and dumb pipes” is to think about where the logic exists. When you create dumb endpoints and smart pipes, you depend on the pipes for everything. You depend on the pipes to know where the data is coming from, where it’s going, and how it should be transmitted. You have centralized all communication power into a system of organization and governance. That’s not necessarily a bad thing in all cases — in secure data situations, you may very well need such a system — but it’s only truly appropriate in a handful of situations. “Smart endpoints and dumb pipes” is an inversion of this principle. The microservices know what they’re doing, and if that’s true, why should you need to control their communication? If you’ve built out a robust microservice ecosystem, then you should be able to let them communicate over a system that is minimally controlling, with minimal logic built-in for core functions. What do you think about this concept? Let us know below!