Note: I created a talk based on this article that you can view here.
“Clean Architecture” is a software architectural pattern coined by Uncle Bob Martin in his book called, naturally, Clean Architecture 😅. It’s one way to structure software code that is an example of hexagonal architecture.
TLDR;
The usual way that developers implement clean architecture is horrible:
- Too much context-switching
- Pieces of the same features are too far apart
- It doesn’t set you up well to further carve out boundaries in the future
- Extracting smaller services from your codebase in the future is harder
- It faces the same issues that the industry has had for years with the classic N-Tier architecture
I continue these thoughts with more real-world examples in another article about how to architect and structure .NET applications.
Ports And Adapters
The basic idea of a hexagonal architecture, otherwise known as a “ports and adapters” architecture, is that your domain logic and domain objects live in the “center” of your application. Other concerns like persistence, caching, etc. are treated as an add-on or “adapter” to the domain code.
Generally, this is done by having your domain code expose this infrastructural or application-level code as an interface.
This is conceptually similar to how we might design a computer, for example. A computer has a dedicated core purpose but allows outside devices to “plug in” via some kind of interface (a USB port, HDMI port, etc.)
Principles Of Clean Architecture
Clean architecture has a number of principles that I’ll summarize here:
- Independent of Frameworks
- Testable
- Independent of UI
- Independent of Database
- Independent of any external agency
I think most software developers would agree with these principles.
The most important principle that comes from both clean architecture and domain-driven design is to keep your domain objects and domain logic separate from other concerns.
We need to decouple the domain objects from other functions in the system, so we can avoid confusing the domain concepts with other concepts related only to software technology or losing sight of the domain altogether in the mass of the system.
Domain-Driven Design pg. 67 (Kindle)
How We Tend To Move From Conceptual Patterns To Implementation
The thing I don’t like about clean architecture is not necessarily on principle, but around how most developers move from thinking about these principles to implementing them into actual software projects.
The typical structure is to create solutions or services that have separate projects or modules that deal with different layers:
- Application layer
- Domain layer
- Infrastructure layer
- UI layer
Often these projects are called Infrastructure, “Core”, API, Web, Application, Domain, Business, Domain, etc.
We tend to take these concepts and “one-by-one” just implement them as dedicated .NET projects, Java packages, node modules, etc.
I believe that this way of moving from conceptual thinking to applying that as a specific “blueprint” of how you ought to structure your code is faulty.
Problems I’ve Faced With Clean Architecture
In one of the companies I’ve worked for, I brought in clean architecture as one of many ways to start improving development processes, development speed and product quality.
We implemented clean architecture for new features moving forward. While this was way better than the non-existent conventions and architecture that existed previously, there were aspects that caused issues.
One of the biggest ones was context switching.
Context Switching
For example, if you were working on a feature and needed to work on any combination of data access, domain or application logic, you had to often switch contexts into a different software project with a different folder structure.
This was inefficient.
I decided that putting everything relating to a specific feature together, in the same folder, brought more benefits than not.
While respecting the dependency rule, this idea is the logical conclusion of following the principle of high cohesion: things that change together ought to live together.
But, doesn’t that lead to a muddling of concerns? Application, domain and database logic in the same place?
Loose Coupling
If you understand the principles of high cohesion and loose coupling, then this shouldn’t be a problem.
You can still achieve the “ports and adapter” like approach conceptually but within each individual feature.
For example, the typical approach I take is something like this:
If we “zoom out” and look at this from the view of having multiple features or products, it looks like this:
Notice that I’ve grouped the “application” and “domain” layers under the same bucket. That’s because, in many cases, these can simply exist within the same folder or software artifact.
Within the same feature folder, if you keep your data access and other IO behind an interface, then “Bob’s your uncle.” 😋
Vertical Slices
At the time, I had come up with this approach on my own by using the principles of loose coupling and high cohesion.
As I stumbled through this way of thinking, I came across what is commonly known as “vertical slices” architecture.
Jimmy Bogard, as far as I can tell, is responsible for codifying this approach in the .NET space:
In my experience, this approach is simpler.
When adding or changing a feature in an application, I’m typically touching many different “layers” in an application. I’m changing the user interface, adding fields to models, modifying validation, and so on. Instead of coupling across a layer, we couple vertically along a slice. Minimize coupling between slices, and maximize coupling in a slice.
https://jimmybogard.com/vertical-slice-architecture/
It focuses not on the separation of concerns from a technical perspective (like domain vs. infrastructure vs. application logic) but from the perspective of reflecting the business domain’s features.
Again, keeping data access separate from domain logic is key, no doubt.
But, should that drive us to separate these things as separate software artifacts?
I think it makes more sense to separate business capabilities as separate software artifacts instead.
And now, we end up in the land of SOA. But that’s another topic for another day 😅.
Next Steps
Check out my in-depth article that digs more into this topic with more technical and real-world examples.
Bonus: Do We Ever Swap Out Databases?
Proponents of the architectural style I outlined as the typical “clean architecture” might raise some objections to the vertical slices approach.
The primary one is: What if you need to swap out your database from MySQL to Oracle?
My response would be two-fold:
- How often does that actually happen? This sounds like premature optimization. I believe we can gain immensely more from keeping highly cohesive pieces together.
- A vertical slices approach can actually make this easier!
About that last point: since we focus on making separate features in our system loosely coupled and isolated from each other, it follows that we can totally “re-do” an individual feature and keep that change isolated.
All too often, these migrations end up being a Frankenstein data access layer because it’s “halfway” into its migration.
18 replies on “Clean Architecture Disadvantages”
Thank you for another interesting article, James!
This topic is not well understood and the discussion should be kept warm! Implementing principles literally and dogmatically is always harmful…
Your idea reminds me Simon Brown’s “package by component”, do you see any contradictions in his and your approach?
Another thing which always confused me is the term “use case”. I can think about constructs like PlaceOrder or RegisterAccount. But, shouldn’t those be part of the domain? On the other hand, a controller is for me a concern from the application layer as its responsibility is not more than to validate requests, execute use cases and transform responses into the presentation mechanism.
Thanks!
Ya, I generally really like Simon Brown’s package by component approach. **If you create a component that aligns to a business function/capability, or in DDD terms a sub-domain, etc.
The part about “how do we deploy each component” is interesting also. I think that depends on the needs of that specific business function.
So, you might have a larger system – a modular monolith like Simon Brown advoctates (which I really like). This would be a collection of various components packaged as Java packages. So they are compiled and deployed together.
But, you could deploy those components using gradle (or whatever is more common with Java) as a downloadable package.
Then, one step up might be to deploy a particular component as an entirely different process.
So long answer is that yes, it’s very similar to his approach 😊
I like to use the term “use case” to refer as some behavior of the system that an external party (user, system, etc.) can perform on it. Thinking of how a user uses a system, for example, it’s fairly natural to think of an action a user can perform as a command or a query.
These essentially become the entry point into our application/domain logic (although they might be exposed by a facade or something like that, depending on your needs?)
So an MVC controller would “use” a use case to perform some domain command, for example. That same use case might be used somewhere else, like in a mobile API, to perform that same action.
Hopefully that clarifies things?
Thanks! Yes, I think our understanding of a use case is very similar, yours might be a bit more general. Behavior, an entry point to the domain logic. The same use case could be called from a REST controller, MVC controller or simply from a command line runner.
If it belongs to the application or domain layer is not so important (and maybe too academic) as the direction of dependencies is well understood.
I tried to implement this idea in one of my pet projects, you may check it out, the multiple (maven) artifact approach is implemented as well, in a separate branch: https://github.com/ttulka/ddd-example-ecommerce
Cheers!
It sounds like we agree 👍 Will check that out!
This may be heresy, but I find it challenging to keep the domain model separate from the data model. Perhaps our customers (insurance companies and enterprises/municipalities that manage insurance) live in a data driven world where data drives the domain to react to changing realities. I’m getting dangerously close to believing it is best to model your domain as conceptual interfaces with operations but no attributes just to keep the domain model focused on concepts and relationships rather than data. For us, different customers require different “implementation details” so the domain is not all that stable, which I know is contrary to what Uncle Bob has to say about it.
You might find this old content from 2016, based on older content from 2013, interesting in this context:
https://serialseb.com/blog/2016/01/21/vest-tenets/
Thanks for the article, James!
In practice (at least from my experience), modularization/packaging by features can lead to a lot of problems. Usually developers tend to share code across features, so if you want to avoid it, you have to duplicate some logic or introduce domain events to coordinate changes across the features. As an example you can see that you have “core” feature, which is quite isolated and basically include full onion with domain, adapters and frameworks. Then another feature needs to query some part of business logic from “core” feature, in practice developers usually simply share the repo or service/use case and that’s it. All dependencies at the end…ball of mad 🙂
I always start with layering and increase cohesion within the layers, when I see that some part can be sliced into separate modules/features I do that. If you start in new domain and in new application, starting with the features is a bit awkward, you simply do not know the domain and you assumptions about the bounded context is not full.
Regarding UseCases, I actually do not like this naming. If you take classic DDD book – application services is much better naming from my point of view, but in that case clean architecture lacking pretty import concept as domain service…but that’s another story.
Ya, different approaches to development or design have different names for the same things lol. “Command” vs. “Service” vs. “Use Case” vs. “Action” vs. ‘Behavior” vs. “Function”… 😅
Hi James, maybe I’m totally missing the point but I don’t understand how you can defer technology decision with this kind of architecture. Let’s imagine I’m starting a new software and I don’t know yet which persistence technology I’ll use. To start, I decide to go for an in-memory collection, but only later I decide to go for Entity Framework. How do you handle such a scenario in your tests? If I check Jimmy Bogard’s vertical slices architecture repo, he only has “integration tests”. Does this mean that, with a vertical slices architecture, you’ll only have “integration tests”? If for some reaon, finally, entity framework is not adequate for a use case, he’ll have to change all his tests for that particular use case…
You can put an interface behind any persistence logic and (a) stub it out with a fake (like an in-memory store) and (b) just mock it out. As long as you make sure that the code that is persisting things is behind an interface and is not doing any business logic…then you don’t need a database, etc. at that moment.
It’s possible and might be the right choice for your team, project, budget, scope, etc. It’s not a single faceted decision.
You might have opted to do something like Jimmy Bogard’s example. The benefit of a vertical slice architecture is that you can use different persistence technologies PER slice very easily. Instead of having one re-usable “repository” or whatever, each vertical slice would just have its own data persistence code. That way you aren’t coupling each slice to the other. If one needs to change, as you suggested, then you can change a slice (and its tests if you are doing integration tests) and know that it won’t break or affect another slice.
Hopefully, that helps 🙂
Clean architecture is based on its interfaces and dependencies rules, this is not an alternative to clean architecture because CA does not limit assemblies to layers, even more I remember Uncle Bob keeping this point open with the possibility of keeping all related layers together.
Yes, I agree. This is why in the article I said:
“The thing I don’t like about clean architecture is not necessarily on principle, but around how most developers move from thinking about these principles to implementing them into actual software projects… I believe that this way of moving from the conceptual thinking to applying that as a specific “blueprint” of how you ought to structure your code is faulty.”
Wow! That was straight to the point I wanted to raise.
I don’t understand why people make this so difficult.
If you change your database or ORM, guess what? You’re going to have to change your data access methods, and probably portions of your models. No matter how much you want to separate those things; you can’t.
Your entities or models or pocos are specifically modelling your database. Your repositories are specifically accessing the database and populating those models… Or, thinking about it completely logically, your repositories/data access methods are the methods that would be included in your models if you didn’t make them pocos… And, coincidentally, your repositories/data access methods are the enterprise logic that will never change if another app needs access to your database…
Have you ever read any of Uncle Bob’s code?
Not good.
I agree that the persistence code is closely tied to the storage of the models you’ve created. If someone uses something like an ORM then migrations may not take that long, though.
My main point in the article to highlight what you said he – the typical “clean architecture” approach doesn’t really have the big benefits that it claims.
I prefer using de-coupled feature slices. If I ever do want to migrate (ever…) then each slice can be addressed on and individual basis in an isolated fashion. The reason for wanting to do that is exactly the point you’ve raised – data access code is usually tied to some specific way that database X works.
I think the best arguments for keeping domain logic and persistence logic separate is more around being able to test domain logic without dependencies on I/O and being able to just reason about domain logic in some use-case or let’s say aggregate/entity.
But again, how those objects are accessed and persisted… doesn’t matter so much? As long as that dependency is isolated to outside the core domain models.
What do you think John?
Actually, the approach was defined much earlier, by James Coplien and Gertrud Bjørnvig back in 1992, in their paper “Software Development with C++: Maximizing Reuse with Object Technology”.
“A package-by-feature organization arranges the system around its features rather than its functions. Each feature contains a slice through the entire application: presentation, user interaction, data access, and data model. All elements that are necessary to implement a single feature are contained within a single package. This organization makes it easier to reuse code and easier to maintain the system as a whole.” (Coplien and Bjørnvig, 1992, p. 98)
[…] You could opt to use a monolithic structure using clean architecture (I’ve written why I’m not so fond of this approach). […]
[…] written about this a few times (1, 2, 3). I’m not the biggest fan of clean architecture. I think many of the principles found in […]