- Published on
Preventing the Distributed Monolith
- The Creator
- Benefits and Drawbacks
- Dependency Abstraction
- Abstract Non-Functional Requirements
- Conservative Functional Requirement Abstraction
- Common Components Libraries 🚩
You maintain a frontend monolith in your company and your team decides “we need to break up our monolith”.
This is extremely common, and comes with many benefits, but also many pitfalls.
Benefits and Drawbacks
Some benefits of breaking up a frontend monolith:
- Teams can work on specific business verticals with a greater degree of isolation
- Limited scope of regression
- Independent deployments
- Fewer centralized points of failure
- More challenging infrastructure management
- Sharing code becomes more difficult
- Distributed systems require good logging and monitoring
- Organization must adapt
Dependency abstraction can make or break the move to a more distributed frontend architecture.
As software developers we’re all acutely familiar with the DRY (don’t repeat yourself) principle.
The DRY principle is stated as "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system". The principle has been formulated by Andy Hunt and Dave Thomas in their book The Pragmatic Programmer. They apply it quite broadly to include database schemas, test plans, the build system, even documentation. When the DRY principle is applied successfully, a modification of any single element of a system does not require a change in other logically unrelated elements. Additionally, elements that are logically related all change predictably and uniformly, and are thus kept in sync. REFERENCE: https://en.wikipedia.org/wiki/Don't_repeat_yourself
Making conservative decisions about where to apply DRY as you’re breaking up a monolith is critical.
Creating NO shared abstractions and repeating everything for each broken up part of the monolith can lead to complete chaos and long-term maintenance difficulties.
No matter how good the communication is between the teams that become responsible for the different verticals, the likelihood of creating divergent solutions to the same shared problems is high.
Abstract Non-Functional Requirements
Some things you may want to abstract to avoid chaos are commonly called NFRs (non-functional requirements).
Broadly, functional requirements define what a system is supposed to do and non-functional requirements define how a system is supposed to be. REFERENCE: https://en.wikipedia.org/wiki/Non-functional_requirement
Some examples of non-functional requirements could be:
- CI/CD pipeline configurations
- Observability (such as transaction tracing)
- Infrastructure management (Infrastructure as code such as Terraform modules)
- Security / authentication / authorization
- Core interfaces for how the different verticals are stitched together
- UI component design system
The term non-functional requirement can be a bit confusing considering all of these things are required before your product / application can be functional.
Conservative Functional Requirement Abstraction
For functional requirements, this is where extra care must be taken when it comes to making abstractions.
After all, the core idea behind breaking up a monolith is to take advantage of the benefits mentioned before.
For UI components specifically, one thing a team can do is create some architectural guidelines to follow.
Here’s an example:
- If the component is extremely generic (doesn’t contain business logic or conditions that affect it’s behavior based on application details) - then add it to the design system
- If the component is commonly repeated inside the vertical application, and is significantly more complex that what exists in the design system, then consider making it an abstract component inside of the vertical. In other words, only share that abstraction inside of the application - not across many different applications.
- If the component is a specific feature in the application, just maintain it in the application and consider not sharing it at all.
Considering the following about a component:
- Considerably more complex that the design system components
- Likely to be required across many different vertical applications
- The cost of cognitive and development overhead of maintaining many different separate instances of the same component is greater than the cost of maintaining it in one place
If the above conditions are mostly true, then a conversation about creating a shared abstraction for the component - potentially in the form of an NPM package or some form of federated module could be valid.
Some examples of a valid cross-application shared component abstraction:
- A header component
- A footer component
- A chatbot widget
- A cart component in an e-commerce application
By following these guidelines, we can limit scope of regression or in other words keep the blast radius of issues as local to the experience as possible.
Common Components Libraries 🚩
These things exist everywhere in companies.
Common components libraries exist somewhere in the abstraction hierarchy between the design system and the feature code that lives inside the application.
Potentially the monolith supports multiple lines of businesses, and these lines of businesses consist of applications that have their own UI component needs.
Sometimes teams decide that the organization’s design system doesn’t quite support their abstraction needs so they create their own library to share higher level abstractions across their applications.
This kind of behavior leads to a distributed monolith. Feature code starts to creep into the common components library, and all of the sudden every time someone needs to make a change in one page, they need to be aware of all possible consumers of the common components libraries.
At this point the frontend teams are in a special kind of hell where bugs become distributed, and you need to make multiple changes in multiple codebases.
Just draw a line at the design system for creating packaged abstractions - unless there is REALLY valid reason for creating a package for something.
- Abstract non-function requirements to prevent overall system chaos
- Be extremely conservative about creating abstraction for non-functional requirements
- Create architectural guidelines for teams to follow
- Think thrice before creating a common components library, a design system is a adequate abstraction
- Adopt “AHA” (avoid hasty abstractions) programming
Consider watching this GOTO conference talk by Monica Lent.