I had the opportunity a few years ago to watch a jet boat scream around Charleston Harbor. It moved at incredible speeds, and then seemed to nearly stop and turn on a dime and race away in another direction to the delight of its cargo of tourists. At the same time, there was a huge cargo ship making its way out of the river and into the harbor with the assistance of many tugboats. It then made its way through the harbor at a deliberate pace and into the open ocean with a local harbor pilot at its helm. Eventually the ship moored offshore as it was transitioned back to the ship’s captain. The harbor pilot boarded a pilot boat and made his way back to shore.
I found the contrast in speed, agility, fore planning and coordination interesting. Obviously, the high-speed tour boat requires some planning and coordination, but only by a small crew who could prep the boat, sell the tickets, take their passengers through a brief safety session, then get moving. The cargo ship required weeks of planning coordinated across many separate organizations – literally across oceans – then the support of many local specialists coordinated to move the cargo ship in and out of port. While under sail, the tour boat captain had to have a basic understanding of the topology of the harbor, but he could make split second decisions as to when to turn and where to go on a whim or based on conditions he evaluated in real-time. The cargo ship under sail required advanced planning to make even the slightest adjustment as it made its way through the shipping channel – it could not turn on a dime at a moments notice.
Hero to Molasses
We see this same contrast in the lifecycle of software systems. The latest industry discussion has been around the monolithic application with all software components logically arranged, but contained within a single deployment unit verses one that is broken into many separately deployed, reusable parts called Microservices. Monolithic applications have the reputation for becoming unwieldy and slow to change over time due to increasing dependencies and expanding features. Microservices provide a solution by separating each feature set so dependencies are broken and assembly of features is explicit.
This is a familiar story in our industry. The monolithic application is the right bet when you are developing a product and testing it against your market. It allows you to change quickly, deploy rapidly and manage simply. A small “full-stack” crew usually mans it in those early days. They can keep the entire set of top-to-bottom dependencies in their heads. The monolith grows with the market’s love of the product. Many more people are required to support the system and it sprawls in unintended ways as the teams react to the insatiable market. Technical debt is incurred as the market turns and well-intending teams react by bolting-on features that are beyond the capability of the original design. Unintended dependencies swell. Assembly of features become fixed and cannot be easily rearranged. Specialists are added to keep up with the increasingly clumsy system. Tribes are established. Finally, the monolith becomes the lumbering cargo ship – full of value and still [sort of] manageable, but only with lots of advanced coordination and planning to make the slightest of turns. It becomes more like slogging through molasses.
Meanwhile, the market waits for no one – business needs shift and evolve rapidly along with fresh, new technical opportunities and competitors. You still need the ability to nimbly pivot and present new value to lead the market – while still leveraging all the previous value you previously created – not just to get ahead or even keep up, but also to survive.
You need an architectural blueprint to ensure there is a tomorrow. Fortunately, there are proven styles and techniques available from the industry to address these challenges.
A Way Out
Edsger Dijkstra was the first to articulate the concept of “separation of concerns” in the early 1970’s as a technique to logically design and manage aspects across a system in terms of modules. He felt this was the more “intelligent” approach as opposed to the folly of trying to consider everything within a complex system as a whole.
Software architecture has long recognized the need to use “layering” to separate technical aspects such as presentation logic, business logic and data management through the three-tiered architecture and model-view-controller patterns that define the scope of each and the formal interfaces between them. These patterns have been fully embraced by web-based systems and are often realized with separate physical infrastructure that is fit to purpose and scaled to need. This lets us pick the right tools for the job, independently tune our non-functional qualities (performance, volume, etc) and take advantage of that isolation to make changes in one concern without impacting another.
Component-Based Engineering also recognized the need to treat logical sets of functional business capabilities (feature sets) as concerns. This style formalized the concept of modular representations of bounded, highly cohesive and loosely coupled functionality connected through well-defined, formal interfaces. This lets us independently evolve the functional capability while promoting substitutability, reusability and isolating the impact of changes across many business capabilities – so they become interchangeable, easily assembled/reassembled components.
Service-oriented Architecture further recognized the need to expose those components remotely to be consumed by composite applications and/or orchestrations so assembly of features could be fully separated. This provides the benefits of layering as well as the opportunity to independently evolve each business capability.
We can address our needs for agility, speed to value, independent evolution, simpler management, fit-for-purpose infrastructure, quality and organizational coordination by combining these styles and patterns into a multi-dimensional approach.
Figure 1 Qualities of layered, component-based, service-oriented architecture
This is a representation of a layered, component-based, service-oriented architecture that can address those needs. The layers provide isolation around specific technical concerns that progress from the top at the edge of contact with clients via the final delivery of product offerings downward to base, functional components near the bottom. This progression through the layered stack supports the notion that higher variation is required the closer a layer exists to the final consuming entity while higher utility and stability will exist further from the consuming entity.
Proper layering not only allows for independently managed infrastructure, but can also allow a dual-mode or two-speed lifecycle model where different teams may be applied to meet differing delivery requirements. Examples include the use of web application or mobile application teams to rapidly compose APIs of existing components to create “composite applications” that are fit to consumer needs.
Those components are derived from a lower layer of this model. Each component is a realization of a capability that is independently managed, deployed, scaled and expertly evolved by component teams. APIs provide the separation and formalization of interfaces that allow all those teams to work in relative isolation and to potentially take on full vertical lifecycle responsibilities of their concern – including product management, design, development, quality, deployment and operational support.
This combination of development and operations (devops) has been growing in popularity in recent years as a way to realign accountability, ensure quality, speed communication and improve collaboration by breaking down those tribal walls built during evolution of the monolithic application. The product can enjoy the same benefits it experienced in its early days as a monolith by identifying those logical business capabilities, installing the mechanisms that allow separation and then assigning accountability to a single team for each.
Get There From Here
A Microservice is the combination of devops with components that are bounded by business capability context and accessed through remote APIs. This then becomes the concept we can use to move away from that monolithic application that is holding us back – or to even avoid the monolithic trap from the beginning.
Employing Microservices with this architecture amplifies the need to align your API to your business model to ensure the value of a great API. The significant business capabilities within your model will inform your design to achieve the simplest access to the simplest representations for ease of use and independent evolution.
Merely exposing your entire internal data model will increase the sensitivity of your consumers to any change you make thereby also restraining your ability to continuously change (once they are tightly coupled). It also increases the risk of confusing your consumers, as they now must understand your entire data model, not just what they need to get started, then incrementally progress. Finally, this approach will not necessarily inform the decomposition of your monolithic application into the discreet capabilities around which you will build your Microservices.
The recommended approach is to provide a level of separation, starting with APIs to those business capabilities, and then extending into your realized components. The API is the language your consumers use to access your platform, so it must be designed in the context of their interactions, not necessarily that of your core implementation. Your challenge then shifts to understanding the variations in those interactions in the upper layers so the assignments of responsibilities are balanced across your APIs.
Therefore, we can start our journey by using remote APIs to separate the monolithic application into layers. The bounded APIs provide an avenue of migration by first creating facades onto the monolithic application – there is no need to completely redesign and rebuild your application at once, but the bounds must be well defined to allow that eventual separation. Component teams can then be assigned ownership of the API (and underlying business capability) so they can begin defining the technical boundaries of the component that will eventually be hosted by the Microservice. This process may require a “harmonization” period with scaffolding installed to prepare the monolithic application for separation. This assignment of ownership acknowledges that the market will not wait for this migration, so the component team can manage the evolution of the business capability with migration efforts in mind. The Microservice can then be born when the component has been fully separated – through its entire lifecycle from product management, code repository, build dependencies, deployment and operational support.
The rapid pace of the market, rapidly expanding user expectations, explosion of technology options and the uncertain future of technology requires an agile organization and product that can quickly pivot and embrace new tools. To thrive in our tomorrow, we need architecture than takes those realities into account. To survive until tomorrow we need an approach that supports a migration from the lumbering monolithic applications to that architecture. We can create a blueprint of that destination with a mix of established styles that will let us separate concerns on multiple dimensions creating manageable scopes of responsibility so our teams and our product can once again move at jet speed and pivot on a dime.