This post appears as part of a series of Cloud Native Transformation Patterns, which begins with Cloud Native Transformation Patterns: Introduction. These are condensed versions of the full patterns appearing the forthcoming book, Cloud Native Patterns: Architecture, Design and Culture, a joint project of Container Solutions and O'Reilly. Complete and in-depth information regarding each pattern, including case studies from real-world Cloud Native enterprise migrations, will be found in the book publishing in Spring 2019!
In software design, Modular Architecture refers to separating the different functions within an application into independent yet interrelated pieces. Java engineer/architect Kirk Knoernschild defines a software module as a deployable, manageable, natively reusable, composable, stateless unit of software that provides a concise interface to consumers. Yes, pretty much the opposite of a monolith!
The sheer size and complexity of monolithic applications limit our human capability to understand them. Dividing them into smaller and more manageable pieces makes them easier to understand, easier to implement, and faster to iterate.
Modularity is not exactly a new concept. It is, however, particularly well suited to Cloud Native, where we typically implement it in the form of microservices architecture. In this form, modular architecture has already revolutionized the way modern software applications are developed.
What’s the Context?
The Modular Architecture pattern applies when your company is moving to Cloud Native with the aim of delivering software faster to their clients.
Your engineering staff size ranges anywhere between a dozen for a small to medium business, up to a few thousand people for a large enterprise.
Naturally, as your business has grown, the complexity and scale of your software has grown along with it.
The Problem to Contend With
In the first stage of the Modular Architecture pattern, companies are often still firmly based in a monolithic plan release schedule/approach. A few of the problems that this pattern applies to include:
- Scheduling new releases for monolithic software systems is a significant challenge. It takes a great deal of coordination, eats up a lot of resources, and takes approximately forever -- these monolithic mega-systems may finally update on a yearly or even longer basis!
- When adding new functionality, faster-moving teams are held back -- to the pace of the slowest team in the organisation.
- Very few people within the organisation, even the most senior engineers or architects, are even able to grasp or understand the full complexity of the application.
Forces at Play
- Don’t rip off the bandaid: People tend to delay painful moments; since integration and delivery are typically painful, their frequency tends to decrease as system longevity increases. In other words, as a system’s lifespan goes on, updates and new releases become so cumbersome and potentially problematic that they happen less and less frequently.
- Supersize me: Larger monolithic systems become increasingly more difficult to understand as they grow in size and complexity
- Conway’s law: Architecture tends to resemble the organisational structure. So a monolithic company will build a monolithic system.
- Short term gain: Monoliths, thanks to their tightly coupled architecture, have none of the complexities or problems of distributed systems, and are also easier to firewall. Most importantly, monoliths are easier to operate. The temptation is to mistake this for a net good and just let them keep running - “don’t fix it if it ain’t broke.”
The Solution
Inevitably, the solution supported by the Modular Architecture pattern is to kill the monolith: split applications into smaller modules where each individual component can be built, tested, deployed and run independently from all the other components.
This works by limiting the complexity of each component while enriching their connections. First, break down the application’s functions to the most granular level that is possible and that makes sense, given the organisation’s needs. After that, create connections to join the components in a wider network of higher complexity. (So: complexity of components themselves is bad, but network complexity is ok and even to be expected). These connections must be well defined, with well established messaging/communication established between them, and implemented as APIs.
Each module (microservice) is assigned as the full responsibility of a single small team. This ensures that the complexity of each module will not go beyond a manageable limit, and that teams stay nimble. Able to quickly adjust their piece of the application to changing needs as they arise, and also responsively coordinate their piece with other teams.
A Brand New Context
New systems are created from a large number of small components with a complex web of connections reliably communicating with each other via APIs. Small, independent teams work on separate modules and deliver them when needed, as needed, with only limited coordination required across other teams within the organisation.
Related Patterns
Cross-functional Teams, Continuous Integration, Continuous Delivery, Common Services, Libraries & Tools, Communication Through APIs, Dynamic Scheduling, Automated Infrastructure
Want to learn more about Cloud Native? Download our free eBook below: