Hammering at Clean Architecture

Who doesn’t like sharpshooting wood elves, chaos infested wastes and dwarfs digging deep under the mountain for treasures? Daring Sigmarite warrior priests and armour-clad chaos Chosen and all manner of other fantastic creatures? I know this might sound weird, but this is an article about programming! I’ve recently gained interest in trying to understand a bit more about Uncle Bob’s clean architecture design and, to do that, I’ve created a small app that I believe showcases some of the main strengths that I believe it has. Disclaimer: All Warhammer references are the property of Games Workshop and I also do not claim to be some clean architecture guru. But maybe this will help someone else with their first steps in grasping the concepts of this cool new toy I’ve found.
Clean architecture, hexagonal architecture, onion architecture, screaming architecture! So many names, so little time! The concepts revolving around these proposed theories are somewhat similar and I think attempting to hack away at one of them will give you insight about the others as well. I will try to start bluntly by displaying the picture that you’ve probably already seen around the inter-webs (Taken from The Clean Coder blog):
It looks like a lot of stuff, doesn’t it? The idea is pretty clever though and I would say not all that complicated. The goal is to separate the stuff that rarely changes from the expendable stuff. You may wonder what it is that rarely changes in the world of software development and you would be right to think that. I would say that, at least for the lifecycle of an app, the thing that doesn’t change all that often is the domain, the core concept of the app itself. If you are building an accounting app, for example, it’s highly unlikely that after two weeks the client should ask you for a video game. Modelling the intrinsic concepts of your application is usually something that can be done in the incipient phases of design. Sure, you will never do all your design upfront and it doesn’t mean your core entities will never change. But as long as you stay organized and try to minimize impact, you should be ok.
There is also the matter of frameworks. Frameworks are tools and they should not drive the inner workings of your app. This sounded a bit weird to me at first, because I’ve done some Java EE development in my time and was super accustomed to adding annotations on table entities and it always seemed to me like the recommended way to go. But if you think about it, all these kinds of operations are doing is to tie your domain object to a certain type of database and a certain framework. Should you decide to change later, you might need to come here and take a good look and what you’ve done previously and start hacking at the heart of your application. We all know that this risk is usually something stakeholders are not prepared to take as it might impact production, or even impede development. So framework flexibility is not something often encountered in the software engineering world, once passed the initial decision-making process.
For the example that I want to show you, I have chosen something called NestJs, which the authors describe as: “…a framework for building efficient, scalable Node.js server-side applications.”. Since it has first-class support for Typescript, which has kind of grown on to me in recent months, I decided to dive in and prepare a small app to support this article. It began with the forging of the Great Rings….wait, sorry, the wrong universe :) It started with me designing some core domain objects that I intended to use in this “Bazaar set in a fantastic universe” app. After I had bootstrapped the NestJs app, which was mainly done to have some prepared project “skeleton”, I started hacking away slowly, starting with the foundation.
Exactly, the domain models! What do these look like? Well, it’s pure Typescript code. No implementing of framework methods, no fancy decorators, no overriding of that special method in a framework superclass. Just how Sigmar intended! :)
You might argue that the language itself is a framework. That might be the case, but still, I think it has quite some purity to it. If we need to use exceptions here, it might be possible to also define some business-related exceptions as part of the domain, that can be re-thrown up in the infrastructure level. For the sake of simplicity, I have used some NestJs exceptions, but this is in no way necessary (and probably not recommended either :P).
While the domain represents the core of the “onion”, the next layer belongs to what DDD refers to as use cases. These use cases represent what the app can do, and what the users would like it to do. Business rules and implementations belong here. Again, make sure you use nothing else but your core language to implement these. A huge advantage here is testability. It’s much easier when core functionalities can be tested without framework setup or other ceremonies.
These elements dependencies point inward, as all modules in clean architecture have to! We make use of only model stuff in here, the rest is pure Typescript code. Again, please ignore the framework exception :)
Further explanations are required here. How do we make dependencies point inward? How would the use case do any relevant work without a dependency injection container, or without connecting to the database, or without talking to external systems? The answer is ports. We reverse dependencies, by forcing external layers that utilize the use case to implement the missing functionality. The if clauses here are business rules, but sellItemService needs to be filled in by some framework. At this level, we should not care about how, or which framework to use, as this is an outer layer’s responsibility. The key takeaway here is: all actions that are not related to pure business functionality will be externalized to the appropriate levels.
In my small app’s architecture, going one layer outward will bring us to the infrastructure level. This is where all the wiring happens. This level can be replaced entirely if we decide to do so later on, without much hassle, impact on the business rules or the domain objects. This can prove to be a huge advantage in real-world projects.
This is where we need actual interactions. Call web services via HTTP, talk to databases and configure them, stream events down the pipes or whatever else we need to do. In our case, NestJs integrates nicely with Mongoose, an elegant modelling tool for MongoDB. My database runs locally in Docker, but again, that’s a pure choice. It could just as easily might have been a Postgres server, running remotely.
We implement the ports defined in the use case, doing actual database operations here. It might seem like more code, but the decoupling will most likely pay off in the long run. The interface to the outside world, the controller, will also belong to the infrastructure level.
As you can see, the use cases are being used here, so again, the dependencies point inward. Dependency injection is done manually here, to keep framework decorators away from the inner layers. The only responsibility these controllers have is communicating through HTTP with external clients. Our business rules are isolated. Adding some data for testing can be done via cURL:
curl -d ‘{“race”: { “mainRaceName”: “MANKIND”, “subrace”: “Empire” }, “name”: “Karl Franz”, “itemsOwned”: [{“name”: “Ghal Maraz”, “itemKind”: “HAMMER”, “worthInGold”: 999999}], “friends”: [], “goldOwned”: 0}’ -H “Content-Type: application/json” -X POST http://localhost:3000/
Then, navigating to http://localhost:3000 will show us all saved characters:
[{"_id":"5d18678e59e031da82880ced","race":{"_id":"5d18678e59e031da82880cee","mainRaceName":"MANKIND","subrace":"Empire"},"name":"Saltzpyre","itemsOwned":[],"friends":[],"goldOwned":50,"__v":0},{"_id":"5d187eb48e139bdd93fcde28","race":{"_id":"5d187eb48e139bdd93fcde29","mainRaceName":"MANKIND","subrace":"Empire"},"name":"Karl Franz","itemsOwned":[{"_id":"5d187eb48e139bdd93fcde2a","name":"Ghal Maraz","itemKind":"HAMMER","worthInGold":999999}],"friends":[],"goldOwned":0,"__v":0}]
It was very useful for me to see some of the advantages of clean architecture in this little exercise. I hope this will prove useful to someone else, as well. In conclusion, there are a couple of advantages you might consider when designing large scale applications:
1. Framework independence: no longer need to mingle our inner business logic with any library-specific functionality.
2. Testability: our business logic becomes much easier to test.
3. Infrastructure independence: it’s much easier to replace the database layer, HTTP layer, async queue systems or whatever else we might need as these reside in the outer layers.
4. Flexibility: it is much easier to add new business logic to this kind of architecture. Just add more use cases, or change the existing ones when customers need something new or different. It’s easy, you only need to code in your language of choice and not worry about anything else.
On the off-side, there might be some disadvantages like:
1. writing more code
2. a bit of a learning curve in the beginning, since this approach is different from the traditional approach
3. fear of trying something new that might seem unproven
Whether this architecture will become mainstream in the future or not, it remains to be seen, but I can say that I see a lot of potential in it.
Please find the code at this GitHub repo. Feel free to ask anything in the comments below. I appreciate any kind of feedback you might have.

Tags

Comments

More by Alexandru Macavei

Topics of interest