Chunks of 'Clean Architecture' - Part 3/3

A series of posts on what makes a clean architecture according to Robert Martin. Part 3/3

Some of these notes are as they appear in the original book, most tailored by me, but all ideas are a creation of Martin or authors Martin quotes in his book.

This is Part 3. Check Part 1 here, and Part 2 here.

Part VI: Details

C30: 💽 The Database is a Detail

The data, and its data model, is significant. The database is not the data model, and thus, it's a detail. It's just a piece of software, a low-level detail, a mechanism to move the data back and forth between the surface of the disk and RAM.

For instance, knowledge of the tabular structure of the data should be restricted to the lowest-level utility functions in the outer circles of the architecture.

Many data access frameworks allow for database rows to be passed as objects. This is an architectural error because it couple the business rules to the relational (and perhaps temporary) structure of the data.

Your customers can expect an RDBMS in the core of your system. You could argue that they actually don't know what they would do with it, even if they are married to the Oracles and MySQLs, and that in the future, when changes arrive, such a choice can be a huge point of pain.


C31: 🕸️ The Web is a Detail

Martin makes the point that the web is just the latest in a series of oscillations that our industry has gone through since the 1960s, moving back and forth between centralizing all the computer power and distributing it.

Protect your application from marketing geniuses

The GUI is a detail. The web is a GUI. The web is an IO device.

Though difficult, it is necessary to abstract a boundary between the UI and the application. Each use case can be operating the IO of the UI in a device-independent manner. Each use case can be described on the input data, the processing performed, and the output data.


C32: 🖼️ Frameworks are Details

Get the milk without buying the cow.

When faced with a framework, try not to marry it right away. Instead, try to date it for a while before you take the plunge. Keep it behind an architectural boundary if at all possible, for as long as possible.

Your problems will likely overlap those solved by popular frameworks, proving them useful. But in using them, an asymmetric marriage takes place. The author is asking you to marry the framework: to make a huge, long-term commitment to it upfront. And yet, under any circumstance will the author make a corresponding commitment to you. You take all the risk and burden.

Risks in frameworks

  1. They tend to violate the Dependency Rule, asking you to inherit their code into your business objects (!), into the innermost circle, making it virtually impossible to take out.
  2. Your product may eventually outgrow the facilities of the framework.
  3. They may evolve in a direction that does not suit your product at all: disappearing features, forcing updates to new versions, etc.
  4. A new and better framework will come that you wish you could switch to.

Follow the Dependency Rule. Don't let frameworks into your code. Instead, integrate them into components that plug in to your core code. (Don't sprinkle your business objects with "@Autowired". Use Main to inject your dependencies).


C33: 📼 Case Study: Video Sales

When you think about a system, you should start with the main actors. For example, in a video sales application, you could have as main actors: authors, viewers, purchasers and admins. Every time some feature is added or updated, that step will be taken to serve one of those actors. This suits the Single Responsibility Principle accordingly: these four actors will be the four primary sources of change.

In this way, we want to partition the system such that a change to one actor does not affect any of the other actors.

Reality Check

Pasted image 20240511021920 A preliminary component architecture.

The previous figure looks thoughtfully partitioned to show separated concerns vertically and horizontally. Now, in our complex real world, do you really want to break you system up into all these components? It depends...

You can certainly break the compile and build environment up this way, so that you could in the future build independent deliverables. You also have the opportunity to combine all those deliverables into a smaller number of deliverables if necessary.

For the architecture in the figure, you could combine views, presenters, interactors, controllers and utilities each into a separate .jar. Then, you could independently deploy those that are more likely to change independently from each other.

Arrows

Something else to notice about the architecture pictured is the way the arrows are pointing. All dependencies cross the boundary lines in one direction, and they always point toward the components containing the higher-level policy. This effectively means it's following the Dependency Rule.

Additionally, observe that the using relationships (open arrows) point with the flow of control, and that the inheritance relationships (closed arrows) point against it.

Mix and match

Different reasons of change correspond to the actors (SRP). Different rates of change correspond to different levels of policy (DR). Structuring code this way, you can mix and match deployable deliverables in any way that makes sense.


C34:❔The Missing Chapter

Good architectures are those where the outside (details that interact with the outside world) depends on the inside (domain-focused code). Let's discuss strategies in organising code.

Domain-Driven Design

We talk about "orders" when we are having a discussion about the domain, not the "orders repository". Notice in the next figure how the policy is defined by the Orders interface, allowing an external repository to implement it separately (a detail), and not having the OrdersService depend on the database. Also, the web package here is just another swappable delivery mechanism.

Pasted image 20240512031130 "Ports and adapters" strategy: the dependencies flow toward the inside.

With the separation in the "ports and adapters" strategy, you can develop and deploy the database, domain and web modules independently, albeit with more build complexity and with the possibility of outside modules being able to import other public outside modules. Conversely, with a layered architecture you force the services and repositories modules to be public, but you also have the service layer with business rules depend on the database layer.

A different packaging strategy is that of "package by feature", where everything, from Controller to Repository is under com.mycompany.myapp.orders. This does not allow for independent deployability if you want to, for example, change the way to connect to the database.

Lastly, another sound strategy is "package by component", since it encapsulates as needed and allows for the compiler to prevent "outside code importing outside code". It is impossible for anything outside to directly import the OrdersRepository<I>. However, it has a slightly weaker horizontal layering because the repositories get packaged together with the business logic objects.

Pasted image 20240512034248 https://github.com/serodriguez68/clean-architecture/blob/master/part-6-details.md

All of the above refers to the organisation within a single (monolithic) application. However, if using micro-services, the same principles can apply to the code organisation within the service.

Parting thoughts

From experience, the health of a codebase really boils down to three things. First, the skills your colleagues bring to the table. Second, the skills you bring to the table. Third, but not least, the mechanisms the team uses to enforce good practices. In other words, if I have skill issues, my code will reflect that; and if my colleagues don’t catch that during reviews, that will reflect in the team, and thus, in the overall quality of the product or service.

Clean Architecture won’t fix that problem, nor fit all your architectural needs, but hopefully the ideas it speaks of can strengthen the mental model your team has when designing changes.

Of course, there’s the passage of time that should organically elevate one’s skills. For that reason, if over half of your team are uni graduates, you must then set your expectations accordingly, be comprehensive and have mentorship as a cultural value.

By the way, here’s an amazing list of examples of so called “Architectural Decision Records” which are exactly what the name implies.

Regardless of all of this, I have to confess that more than any other framework or mental model, I am a YAGNI person. To me, the dumbest solution often has had (and still does have) the greatest return over investment, at least compared to the over-abstracted slop AI can spit out.

We all know that corporate deadlines and management side-quests push us between a rock and a hard place, and thus steer one’s behavior to make compromises and adapt short-term, tactical solutions. But even then, we must strive to manage complexity and be able to develop low-cognitive-load code. If you have to jump between files in three packages, something is off. It should be understandable by a junior, a senior and the cheapest AI model out there. Let’s keep the bar high.

Chau, campeones

Comments

Thank you for your comment! Under review for moderation.