Published November 10, 2019
by Doug Klugh

Build for the Future

Avoid one of the biggest technical debt patterns around — the monolith.  Build modular systems by employing engineering practices to decouple your architecture and encapsulate functionality, while embracing modern cloud platforms to optimize reliability, resiliency, durability, scalability, and security for compute and storage systems.  Software systems, especially cloud-native applications, should be built as a composite of small components and/or services that can be autonomously developed and independently deployed.

What is a Monolith?

A Monolith is a software application that is built with very little modularity and is (usually) wholly contained within a single component or executable (such as a DLL, JAR, or gem).  The functionality of the application is tightly coupled and highly dependent on other functionality with very little cohesion within its code.  This creates a maintenance nightmare.  Change one piece of functionality in one part of the system and break five others in another part.  Not only does this impede maintainability, but many other software qualities such as extensibility, testability, scalability, portability, resiliency, security, etc., etc., etc. — the list goes on and on.

Decoupling the Monolith

There are many ways to decouple a monolith.  The trick is to do it in such a way that it optimizes your quality model — those software qualities that best enable you to meet your customers' needs today and well into the future.

Separation of Concerns

Separation of Concerns (SoC) is a design principle that encapsulates cohesive information and/or logic in a function, class, module, or component.  SoC is an effective mehtod for decoupling software systems and can be achieved by applying a variety of other engineering principles.

Command-Query Separationlaunch

Write functions in such a way that if they change state, they do not return values; and if they return values, they do not change state.  This is a very effective way to manage side effects within your code by controlling how and where they occur.  At an absolute minimum, Getters and Setters should always adhere to this principle.

Law of Demeterlaunch

Build code that has limited knowledge of other parts of the system and interacts directly only with closely related classes.  Methods should be protected from understanding the internal structure of other methods or classes.  This is also known as the Principle of Least Knowledge, a proven strategy for promoting information hiding and building high quality, loosely coupled software.  This can be achieved through the practice of Tell, Don't Ask.

Tell, Don't Asklaunch

To decouple functionality, write methods that tell objects what to do.  Do not ask for their state, then perform functions on behalf of those objects.  This will reduce query functions, encapsulate knowledge, and ensure compliance with The Law of Demeter.

Polymorphic Dispatchlaunch

Build different implementations of a single method or class to allow your software to dynamically choose which type of object best fulfills the current need.  This may include calling a particular instance of an overloaded method based on argument types or instantiating a particular type of object to realize a specific use case.

SOLID Principles of Object-Oriented Design

The five design principles of SOLID, when applied appropriately, are a very effective way to decouple monolithic applications. 

Single Responsibility Principlelaunch

A class should have only one responsibility with only one reason to change.

Open Closed Principlelaunch

A class should be open to extension but closed for modification.

Liskov Substitution Principlelaunch

An object should be replaceable with its subtypes without altering any system behavior.

Interface Segregation Principlelaunch

Many specific interfaces are preferable to a single generic interface.

Dependency Inversion Principlelaunch

A class should only depend upon abstractions, not implementations.

Component Cohesion Principles

The Component Cohesion Principles help determine which classes are best grouped together within a component and which classes we should keep apart.

Release Reuse Equivalency Principle

Components need to be large enough to justify the cost of managing the release cycle.

Common Closure Principle

Classes that change for the same reason should be grouped together in the same component.

Common Reuse Principle

Create components where if one class in the component is used, they’re all used.

Component Coupling Principles

The Component Coupling Principles help to effectively manage dependencies between components.

Acyclic Dependencies Principle

The dependencies between components must not form a cycle.

Stable Dependencies Principle

Components should depend upon each other in the direction of increasing stability.

Stable Abstractions Principle

The more stable a component is, the more abstract it should be — so that it can conform to the Open/Closed Principle.