Hello everyone! Today will be all about architectural decomposition patterns; what they are and the most famous patterns followed by the community. This article unravels chapter 3 from the book: Software Architecture The Hard Parts Which is an excellent read. Let's get started
What is Architectural Decomposition?
Architectural Decomposition is all about breaking large complex monolithic applications into separate services. Monolithic applications have existed for the longest time. As startups grow and more users start using their products complex issues may arise depending on how your monolithic application is set up which then forces the development teams to separate the monolithic application into services accordingly.
Knowing whether it is even feasible to break the monolithic apart or not requires a lot of time-consuming effort. However, there are two main common approaches for approaching such things; Component-based decomposition and tactical forking.
Component-based decomposition is an extraction approach that applies various refactoring patterns for refining and extracting components to form a distributed architecture in an incremental and controlled fashion.
Tactical forking is an approach that involves making replicas of the application and chipping away the unwanted parts similar to the way a sculptor creates a beautiful work of art from a block of granite.
Choosing the most effective approach depends on your setup of the large monolithic application. Is your codebase structured with component boundaries or is it just a large ball of mud?
The following decision tree asks the following questions:
if the modularity is even justified. (will splitting your monolithic app give you benefits more than headaches?) (We will discuss architectural modularity in a separate article. discussing exactly the why of decomposition here we discuss the hows)
Is the codebase decomposable? (we'll get into this shortly) (mainly concerns code coupling)
Does your code have definable components? If so then component-based decomposition might be the best approach, If it's a big ball of mud as the book mentions then tactical forking is the better approach.
Is the Codebase Decomposable?
Codebases lacking internal structure have a colloquial name -Big Ball of Mud Anti-Pattern- Without careful governance many software systems degrade into big balls of mud with no internal structure or modularity
Evaluating internal structure is a difficult task. It is important to evaluate the internal structure to be able to decide which approach is most suitable for decomposition.
There are tools that help determine some characteristics of the codebase, particularly coupling metrics which helps in evaluating internal structure.
Afferent and Efferent Coupling
Afferent coupling measures the number of incoming connections to a code artifact. In other words, other components it calls and needs. For example, a function getting invoked from another component
Efferent coupling measures the outgoing connections (dependencies) to other code artifacts which is the opposite of afferent. For example, a class invoking another component's function is efferent.
Abstractness and Instability
Abstractness is the ratio of abstract artifacts (abstract classes, interfaces) to concrete artifacts (implementation classes). It measures the ratio of abstractness versus implementation. This allows developers to understand the internal structure better. For example, a codebase with a single
main() method and 10,000 lines of code would score zero in this metric and be hard to understand and split.
Instability measures the volatility of a codebase. Codebases with high instability tend to break more easily when changed because of the high coupling. A component's instability reflects how many potential changes might be forced by changes of related components.
If we arrived at this decision that means that our codebase is likely structured. Before moving on we need to understand what a component is
Components are related source code files that are namespaced in a codebase. For example in the figure below, assign is a component with the namespace of
When breaking monolithic applications into distributed architectures, build services from components not individual classes.
There are Component decomposition patterns which will be discussed in a separate article, which explain exactly how to follow component decomposition step by step.
Component-based decomposition will eventually help you arrive at the service-based architecture which in brief is a hybrid of microservices architecture style where the application is broken into domain services which are services that contain all the business logic for a particular domain.
Moving to the service-based architecture as a final target or a stepping stone to microservices is recommended as it allows us to identify the domains that require further levels of granularity.
There are two terms that we describe services with; Coarse-grained and Fine-grained services
Granularity is the extent to which a system is broken down into small parts, either the system itself or its description or observation. It is the extent to which a larger entity is subdivided. For example, a yard broken into inches has finer granularity than a yard broken into feet.
Coarse-grained systems consist of fewer, larger components than fine-grained systems; a coarse-grained description of a system regards large subcomponents while a fine-grained description regards smaller components of which the larger ones are composed.
Service-based architecture doesn't require the database to be broken apart. Which allows us to focus on domain and functional partitioning.
This is the approach used when our codebase is largely unstructured. All we do in this pattern is fork the code and remove any unwanted code.
Deleting code that isn't needed is a lot easier than going through the effort of code extraction.
Each team takes a copy of the codebase and starts deleting unwanted code. In tightly coupled codebases this approach is much easier than extraction. Once the functionality has been isolated just delete the code that doesn't break anything.
Tactical forking has many advantages:
Developers find it a lot easier to delete code rather than extract it. Extracting code from a chaotic codebase presents difficulties because of high coupling.
Teams can start working right away without any front-up analysis
As well as disadvantages:
The resulting services will still contain a large amount of mostly latent code from the monolith
Unless developers take additional efforts the code inside the new service won't be any better than the chaotic monolith. It's just split.
Naming inconsistencies between shared component files, which may cause difficulties in understanding the common code and keeping it consistent.
This chapter discusses different approaches in architectural decomposition of large monolith codebases that have been given the green light of architectural modularity.
In other words, they needed the benefits of splitting up the codebase into separate services. The article goes over the hows of doing so
Stay tuned for the Architectural Modularity and Component Decomposition Patterns articles up next 😉
Did you find this article valuable?
Support Amr Elhewy by becoming a sponsor. Any amount is appreciated!