Beyond SOLID
Tags: webdev, programming, architecture, frontend
Published: 2023-07-26 20:07:14

Large applications contain a lot of code - that's obvious. **SOLID** is a set of rules about class content that **tells us nothing about how to group classes**.

Let's imagine that we are building an Angular application (or any other). We start putting all our Angular stuff into these folders:

- services
- components
- interfaces
- types
- modules


![Generic folders structure. It does not tell anything about the purpose of the project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lp143k6ugeyf5fp5xdp4.png)



Our application is growing fast. More programmers are coming. New items are popping up and our folders are getting huge.

After some time, a new developer joins the team and opens the project. He/she sees several generic folders that contain dozens of services, components, interfaces, guards, etc. From authentication.service through billing.service to gradient-generator.service.

One question arises?

**What is this?!**

This is where the component consistency rules come into play. They are very similar to SOLID principles. However, they are not interested about what is in one class. They can tell you what classes are in one component.

## What is a component?

Let's start by establishing what a component is (and it's not an Angular component, but an architecture component). In the rest of the article, whenever a component is mentioned, I will mean an architectural component (unless otherwise stated).

An architectural component is a small independent part of the application. It performs one specific task and is built around some business logic. Components are isolated from each other and easily replaced, allowing for flexible application development. In simple words, a component is a collection of related classes that make up some functionality. These can be Angular components, services, directives, etc.

Whether something is a single component is determined by architectural boundaries. Such a boundary can be artificial (folders in a monolith) or physical (libraries, microservices). It is worth noting that the use of libraries/microservices/microfrontends does not automatically mean that there is an architectural boundary between these elements. If a change in one class forces changes in other component’s classes, it may mean that those classes should be treated as one component. The exception here is the main component of the application (the so-called main) and abstract components.

## Component consistency rules

Let's start with a reminder of the SOLID principles:

- SRP - Single Responsibility Principle
- OCP - Open/Closed Principle
- LSP - Liskov Substitution Principle
- ISP - Interface Segregation Principle
- DIP - Dependency Inversion Principle

Similar principles can be applied at the level of architectural components. Component consistency rules say which classes should be included in one component and which should be placed elsewhere.

### CCP - Common Closure Principle

This rule is very similar to the SRP and OCP rules. Personally, I think that this is a principle that will apply to most of our daily work with business logic. The other rules may come in handy for code sharing with other teams.

According to this rule, the component should contain classes and modules that are dependent on the same actor. An actor can be a user or a stakeholder - someone/something that gives you requirements. Classes and modules should change for one reason, at one time (SRP) and should be closed to the same type of changes (OCP). Changes in functional requirements should only affect a single component, minimizing the impact on other parts of the system. Related code should be gathered together. Of course, this cannot be achieved 100% all the time, but it is the goal we should strive for.

How to do it?

The simplest way to achieve this is to group code related to business areas. The folders structure of your project could also reflect these business areas.

Let's go back to our folder structure from the introduction:

- services
- components
- interfaces
- types
- modules

You can immediately see that actually you can't see anything. You can’t tell what architectural components we are dealing with. No business areas. Any component with this structure will be spread across all folders. 

Let’s change the structure to reflect functionalities of our system (so-called use cases), e.g.

- billing
- order
- products
- cart
- etc.


![Project folders structure with business areas. The architecture becomes screaming](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ycdjvy4punwh4frx4ea0.png)



This way we have a bonus - the architecture of our application becomes `screaming`. The purpose and business areas of the system become obvious. Our new teammate, given the task _"increase padding between products in the cart"_ immediately knows where to start looking!

This rule has another effect - it can break the DRY* rule. According to Roberts C. Martin:

> “For the architecture of most applications, maintenance is more important than code reuse”

Uncle Bob says that eliminating code duplication at all costs can be harmful. Let's assume that we have two business functionalities (components) that use the same common functionality, e.g. price.service. Over time, one of these components may need some change that the other does not. This way we will end up with a lot of “if” statements all over the code.

* the DRY principle is broken only seemingly. Each use case is a different business context and the duplicated code can achieve a different goal. The similarity in this case is accidental.

### CRP - Common Reuse Principle

> “Don't create dependencies on things you don't need”

This principle strongly relates to the Interface Segregation Principle and the principle of inseparable classes. A component should contain only those classes that are necessary for each other and are related to each other. Inseparable classes are those in which it is impossible to depend on one class and avoid dependence on the other.

Why is this principle important? If you change any class in component A, it will force you to recompile and test component B which uses a different class in A. This rule is especially noticeable when dealing with libraries and Nx affected tool.


![Diagram with broken common reuse principle. Component B is dependent on Component A with unused class.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vp4ojn6qrm7wg1ro63g2.png)


The relationship between ISP and CRP rules is very clear. In ISP, code should not depend on classes with unused methods. CRP - component should not depend on component with unused classes.

How to apply this rule? Simply by not creating huge components containing everything. If we divide the code into modules or libraries, we should try to ensure that the libraries contain only the code that is entirely useful to the consumer of the component.

### REP - Reuse/Release Equivalence Principle

The principle applies to components that are published (in a local or public repository). The rule says that:

> “The code of the component intended for reuse should go through the release process.”

For simplicity, let's call such a component a package. The person using the package should be able to see what has changed between different versions (`changelog`) and decide if he/she wants to update the package to a newer version. This is the same practice we encounter when installing NPM packages.

How to determine whether classes should be included in one component according to the REP principle?

Code in one component/bundle should have one purpose. It should also be obvious why a change in any class of this component forces you to build and release the entire package. Try to think like you’re the consumer of the library.

## Summary

You may notice that these rules contradict each other. That's exactly how it is. Some of the rules are inclusive - leading to include more classes in one component. Others are exclusion rules - they limit the number of classes in one component. 

As the project life cycle progresses, other principles become more important. In some situations, it is possible to apply different rules to different components of our system at the same time. For example, we can build an invoicing component using the Common Closure Principle. Custom UI components can be built in accordance with the Common Reuse Principle. If we create a package of functionality that we intend to use in many places and publish it through an NPM repository, then we should follow the Reuse/Release Equivalence Principle.

### Bonus

The component consistency principles overlapped in part with three of the SOLID principles. What about missing LSP and DIP rules? This is where the topic of `abstract/implementation components`, the `Stable Dependencies Principle` and `Stable Abstractions Principle` comes into play. To understand them, it is important to know the `Acyclic Dependency Principle`. 

The combination of all these rules can be found in _Clean Architecture_. This approach proposes to separate components containing abstract descriptions of business rules from components containing their implementations. The _Clean Architecture_ is also the source, I’ve used for this article. 

Want to hear more about it? Check out my talk about _Clean Architecture_ basics from front-end dev perspective: https://youtu.be/6oAIw9p3tyE

Photo by <a href="https://unsplash.com/@alimor?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Mor THIAM</a> on <a href="https://unsplash.com/photos/uqtyr_EZ_As?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>