Last week, we began looking into Uncle Bob’s Clean Architecture. I shared some UML for those that don’t already know it, but just enough to help us understand the diagrams I’ll be sharing.
We ended with a very high-level diagram of Uncle Bob’s Clean Architecture. Here it is again.
I think we can all agree that this architecture puts us in a pretty ideal position. Who, after all, would not like to have their application be able to exist independent of any facilitating technology?
This week, I want to start digging a bit deeper into what makes the Clean Architecture clean and how most Java applications stack-up against it.
Clean Architecture: It’s About Design
If you think about it, application architecture (not to be confused with enterprise architecture) is really just the high-level design of an application. So, then, Clean Architecture is a specific way to design an application at its highest levels.
More specifically, Clean Architecture gives us a way to design an application so that its layers – the most coarse-grained element of application design – are as independent as possible. Or, to say it differently, Clean Architecture helps us understand how to control dependencies so that our layers are as independent as possible.
So, how does Clean Architecture stack up against Java best practices? Let’s have a look.
Clean Architecture vs Java Best Practices: Logical and Data Flow
If you’re like me, you learned the same bit of wisdom when it comes to layered application architecture: dependencies go down, and data goes up. Here’s an example of that same principle in both a common Java application and Uncle Bob’s Clean Architecture.
As you can see, there is no difference. In both approaches, logic flows from the client (the view), to the business logic, and onward to persistence. Then, data is returned for the business logic to operate on, and some result is returned to the view.
That’s how a layered application architecture works, after all.
Clean Architecture vs Java Best Practice: Source Dependencies
Where things really start changing in the comparison between Uncle Bob’s Clean Architecture and Java best practices is source dependencies. Let’s have a look.
As you can see, this is where most Java applications depart from the Clean Architecture. In Java architecture, source dependencies follow logical flow, but in Clean Architecture source dependencies oppose the direction of logical flow when business logic calls persistence logic.
As it turns out, this may be a bit of a cheap trick in some applications, because a lot of people mistakenly believe that the interfaces used in the persistence layer belong to the persistence layer. In fact, they belong to the business logic.
I’m sure we’ll talk more about why the interfaces implemented in the persistence logic belong to the business logic instead of the persistence logic. If you’re looking for a head start, have a look at the Abstract Server design pattern.
Back to our scenario:
If you move those interfaces up to the business logic, many Java applications would then meet this requirement of the Clean Architecture. That’s why I said it was a cheap trick.
Some would still fail though.
You see, in an effort to write less code, many applications move the Entities into the persistence layer. They add annotations to their Entities to make it simpler to read them from and write them to whatever the chosen platform is. They do this because it provides the greatest short-term gain.
In really bad cases, the Entities even know about database-specific types. That’s really bad. It’s so bad, in fact, that it makes the view (the client) become coupled to a specific database platform, resulting in source-level dependencies going from the client all the way to the persistence layer – like this:
Are you starting to see why the Clean Architecture is clean?
Stop and reflect on the effort required to replace the persistence layer in the Java architecture and compare it to the Clean Architecture. Do the same for the client/view.
Now are you starting to see why the Clean Architecture is clean?
In the Clean Architecture, if we want to replace the persistence layer, we can do that with no impact on the remainder of the system. Simply plugin in the new layer and go.
The same goes for the client/view layer.
We don’t really worry much about the dependencies on the business logic. That layer is the entire reason our application exists. Everything else is (or should be) there to serve that layer.
Clean Architecture vs Java Best Practice: Project Dependencies
From a source code perspective, the highest-level view we can look at for an application is the project structure. As with the other source dependencies, there are some differences between Java architecture and Clean Architecture.
I love this diagram, because it’s a great example of how first impressions without deeper digging can be misleading.
At first glance, the folks following Java architecture are feeling like they’re ahead. After all, there are fewer project dependencies so it must be better! Right?
If we follow the Java approach, we have a project dependency from the business logic to persistence logic. This means that we don’t need to specify that the persistence logic is needed in the client, but it also means that we have to modify the project holding the business logic in order to change which set of persistence logic is included in our application!
You should not have to touch your business logic to change from MySQL to PostgreSQL or Jooq to Hibernate!
More than that, what if you wanted to include multiple sets of persistence logic and let the user choose among them? Before you answer, remember that the interfaces for the DAOs are in the persistence project! There’s a good chance you’d get an odd ClassCastException (or something similar) if you tried to include multiples.
Compare that to the Clean Architecture: You could include as many different persistence layers as you needed!
More than that, the client makes the decision of which persistence layer is included. So, if you needed to build your application for multiple different databases (presumably to support end-client needs) you could do that very easily.
You could sell a product that supported tons of different persistence layers with very little effort.
Cleaning Up Java’s Architecture
Now that we’ve looked at the problems with most Java applications from a high level, let’s zoom in a bit and start talking about how to clean it up. We’ll start with a re-statement of the problem architecture drawn at a more granular level.
In this diagram the client/view is red, business logic is green, and persistence logic is yellow.
Again, this is the worst case. Many people – even the ones who happily allow their Entities to have dependencies on their chosen persistence platform – avoid entanglement with the view. That is, unless there is a good bit of view logic. Then, they often do.
Why Entities Don’t Belong to the View
If you think about the view a bit, you’ll quickly realize that the values here are usually all strings. Because our Entities typically use different types of objects, some conversion needs to be done.
In JSP applications, we have tons of ways to format these values. However, because an Entity may be shown in multiple views, we may have to reproduce these same formatting rules in multiple views.
That’s not very DRY.
Most applications that use AJAX or are built upon a REST service also have to do these conversions, but they are delegated to some other framework, depending on the format that is being emitted – XML or JSON.
However, in these types of applications, you typically want to do more than just return bare entities. There is still some type of alteration that is done. Generally, packaging entities into some larger object that provides information about success or error state and possibly including pagination data.
Of course, once these values go over the wire, they’re all turned into strings anyway, but some fields you might like to have in an entity may need special attention when being converted, such as “pick one” or “pick many” fields where the entity represents only a single value out of the allowed set.
Did you think those should be hard-coded into the view?
Not DRY. Plus, do you want to have to go change every view when the set of values changes? That doesn’t seem very cool, but maybe there are a very small set of values where this is appropriate. It’s unlikely though, because those values need to be sent back to the business logic and will be validated. Where does that set of values (the one used for validation) come from?
A better approach is to formalize the idea of the selectable list into whatever you return to the view and teach the view to use that abstraction. There really is no good reason all of this information can’t be translated into a form directly consumable by the view and built directly from something originating in the business logic layer.
I could go on and on about this, but the net-net is this: if you have special things you want to do in the view, create abstractions that support those special things that are separate from your Entities.
Why Entities Don’t Belong in the Persistence Layer
We’ve already talked a lot about how having Entities in the persistence layer causes the business logic to be dependent upon the persistence layer – and why that’s a bad idea.
There’s more to talk about though.
Just as we saw with the view, the types in the Entities are wrong for the database. Oh, a lot of them may be fine, but the one sticker here is date/time and timestamp fields. Enumerated types can also be a challenge.
A good ORM can solve these problems with a little bit of work. However, you should prefer to use XML configuration – or another approach – that allows your Entities to live in the business layer and avoid having any dependency on persistence.
If you really want to use those fancy-pants annotations, your only option is to create objects specific to the database layer and map to and from Entities. Depending on the complexity of your objects and mappings, you may want to do this anyway.
I’m not saying annotations are bad, and I’m not saying that ORMs are bad. I’m saying that if you let those annotations creep into your Entities you’re departing from Clean Architecture, and that your business logic will become coupled to your persistence layer.
What do you do when the organization of your Entities does not map nicely to tables?
A Cleaned-Up Java Application
Adding classes for the view and persistence concerns cleans things up a lot. Again, if you have a simple application, you may not need the extra persistence classes. For anything more than a simple CRUD application (and for some of those) you really should consider custom objects for the view though.
Now we have view objects with string properties, our Entities are not dependent upon anything, and our persistence layer has its own objects focused on supporting persistence.
Wrapping it Up
We’ve done a lot to clean-up the design, but one of the key points of Uncle Bob’s Clean Architecture is the fact that business operations are highly visible in the design.
Our design? It’s still a bit database focused.
Take a look at our service and persistence classes. What do they model? That’s exactly right – they model tables in the database.
Our work here is not done.
I hope you’ve enjoyed this week’s article – a little deeper look into Uncle Bob’s Clean Architecture and how it stacks-up against the typical “best practice” Java architecture. It wound up being a bit longer than I’d imagined, so thanks to those of you who stuck it out to the end.
I sincerely hope you’ve found it valuable, and that – even if you change nothing – you now choose a different approach because you believe in that approach and not simply because it’s what someone showed you.
I think next week will end this series, but I’m not entirely sure. Part of me thinks it would be neat to actually build something for you using Clean Architecture, so you have a real code example.
I guess I need to figure that out over the next week, huh?
Either way, that’s all for this week. Until next week, this is Eddie Bush for Craftsmanship Counts reminding you to keep your tests passing and your code clean!
(Thanks in advance for remembering to like, share, or tweet this article to your own network! Comments are also very appreciated!)