It’s usually a good idea to keep your Java application’s architecture “clean”, especially if you plan to expand your software for years. But what does “clean architecture Java” really mean? How to organize it well and avoid common mistakes? What is ArchUnit and how can it help you do that? I’m Dariusz Wawer – a Software Architect from Pretius – and I will try to answer these questions for you.
What is application architecture?
In the context of software development architecture is a structure, the way things connect and depend on each other. Architecture defines how the information flows, how applications are organized, and there are many different types of it. You choose what you need for your project by analyzing your goals and needs.
We generally aim to make things well-organized – you don’t want your application to resemble an abandoned, crumbling building but rather an elegant, new house. Sadly, often enough the application architecture looks like a tangled mess, where things connect in confusing and unnecessary ways. Look at the screenshot below, if you want a good example of what I’m writing about.
Why is it like this? In a way, this is Java’s and Spring’s original sin. Over the years Java found ways to implement such tangled mess architecture successfully. In fact, sometimes developers decide not to “clean” their architecture up – they weigh the pros and cons, such as convenience, and implementation time, and decide to go with the tangled mess.
It’s worth pointing out that things can get quite confusing. I’ve heard of cases with lists of 18 or even 20 auto-wired services. Below, you can see an example of what Spring can do. Services built like that, with such mutual dependencies, are 100% valid in Spring. In a way, this makes things easier for the developer… but does it really?
Architecture – confusing definitions, approaches, and information
If you search on the web, you can see many people have a hard time even defining what architecture really is. For example, here’s an image you can find, which supposedly presents the popular Model View Controller (MVC) architecture. Would you consider this graph informative, and most importantly, does it really show application architecture? I certainly don’t think so.
Even big companies have some problems with this. For example, here’s how Microsoft defines 3-tiered architecture:
The layers themselves are quite fine, but does this image really present architecture? No, it’s more like code segregation. To show architecture you need to present dependencies, and directions – ways in which things connect. There’s none of that here. What’s more, it’s not just that one case. If you want proof of that, here’s an Onion View of Clean Architecture Layers, also by Microsoft.
This seems to have more sense – we see several layers here, represented by colored circles. We also see some things that could be interpreted as modules, but we still don’t have any inclination of dependencies and directions. We don’t know what depends on what, and we have no idea in which direction the information flows.
How does a “clean architecture” look then?
So, we know how not to present application architecture, but what’s the right way? Take a look at the image below (taken from the dev.to community). Even at first glance, it seems much better than the previous examples. We’ve got arrows here that show dependencies, and everything is explained quite well.
However, when you look closer, things can get a bit confusing. You might ask yourself: is the database really dependent on the controller? Isn’t it the other way around – after all, it’s my service that calls the database, so how can the architecture look like this?
The main thing I’d like you to get from this article is that while it may seem like a mistake, in reality, it isn’t one. This is the right dependency direction, it really is “cleaner” than the alternative. It’s an element of so-called “plug-in architecture”, and an effect of using the SOLID principles. This is how it should look. Our implementation of writing data into the database should depend on the core of our business logic, and not the other way around.
Architecture – modules, and dependencies
The image above shows an example of a very simple, standard application. You could say it’s built from three layers. Controllers call Services, which in turn call DAO to use the database. And all of these layers are dependent on the Model. The UML Arrows in this image show dependencies. For example, the Controller depends on Services, Services depend on DAO, and so on.
Now, let’s show that diagram in a “clean architecture” circle. There’s only one dependency that doesn’t fit, and it’s the dependency between Services and DAO. Writing/reading from DAO should be a detail that doesn’t affect the core of business logic. Thankfully, there’s a simple trick you can use to solve this problem.
You need to create an interface for this DAO without implementation. It needs to be in a layer that’s kinda above – treat it as a specification of a certain contract, that’s imposed by the Services layer. And then, implementation needs to depend on that interface, and therefore on the Model (though not necessarily, but that’s a story for a different time). Now, all dependency directions are as they should be. The difference is huge because if you did everything right and didn’t make your interface dependent on the technical layer, you can now swap the DAO – implementation dependency as you see fit.
Common mistakes developers make
As developers, we often make mistakes in our work, at least as seen from the standpoint of a “clean architecture”.
One common mistake is making Services dependent on DAO or Outputs, like web services, writing to a file, REST interfaces, and so on. This dependency is very often directed in the wrong way and you can fix that using the same trick I’ve shown you before.
What Abstraction it will be isn’t that important – it can be interfaces, templates (Listener, Observer, even a Queue). The main thing is that you can keep the direction right.
The second common mistake is using a Model that the Service depends on in the Controller’s input. For example, you might have a “my operation” request DTO and a Service’s argument with the exact same DTO.
To fix that problem you need to use a different trick, one that might not be as obvious as those I’ve shown you before. Basically, you can have classes, instead of interfaces, in the core Model. For example, “Customer Data Provider” or just “Customer Data” and lots of getters on it. Thanks to that, in the core Model you’ll have defined what’s supposed to be in that data, while the DTO model (class) implements that interface. This way you can make sure the dependency directions will be right. I know it doesn’t look like it, but it can actually help, and not just make things more difficult.
“Clean” architecture Java
How does a “clean architecture Java” look? How to do it properly? There’s a couple of ways to achieve that.
1. Modules in Maven
The first thing you can do is use modules in Maven. Often, your project will consist of several different modules, and each of them will belong to a certain subset. Maven will look for problems, such as cyclic dependencies, building in the right, and so on. It’s probably not the easiest or the most convenient way – it requires a lot of work that sometimes might not be worth it.
Thankfully, there’s also a second way to keep things clean – using packages. They can serve not only as folders that hold your classes and interfaces, but can also be used to clean up and organize your code. For example, in MVC – Java applications, even huge ones – you often have a Mappers catalog, with around 20 DB access interfaces inside. They do pretty much everything – they read, write, configure, they’re for users, for administrators, for everyone. Technically, you could call that code organization, but do we really want that? You can go one step further and use packages as substitutes for modules.
Here’s an example of an app with dependencies when you use the traditional method – without the division, without interfaces, and we’re not trying to make things “clean”. We approach the dependencies directly. This is the Session-tracker app – a simple piece of software that pretty much only does two things.
We have a Model and a Mapper. DAO uses the Mapper, Services use DAO, and Controller uses Services. There’s also a Performance module that is dependent on Controllers. To make things simpler, let’s assume these are all transitive dependencies. So… does all of this work? Yes. It’s certainly not elegant or “clean”, however – every change you make on a given layer will affect all the layers below it.
And here’s the same application made in a “clean” way. By planning your dependencies this way, creating interfaces, dividing modules, you can create an environment in which, for example, Pertester will depend only on the Controllers-model, and not on everything else. DAO Implementation doesn’t depend on Services, but only on higher layers. You can even change the Core Module without affecting something like the Perftester, or modify the Mapper without affecting the Core DAO.
Clean architecture Java – how can ArchUnit help you?
ArchUnit is a library that allows you to scan the entire code of your projects, see existing dependencies, and gives you ways to change them.
- ArchUnit presents you with a set of general coding rules. It’s not overly extensive. Some of these rules you can see on the screenshot below – things like NO_CLASSES_USE JODATIME, NO_CLASSES_SHOUD_THROW_GENERIC_EXCEPTIONS, or NO_CLASSES_SHOULD_ACCESS_STANDARD_SYSTEMS.
- You can define layers using packages (one, several, a selector).
- You can create rules. A lot of the things that Sonar takes note of – in passive mode, or on the quality gate on Jenkins – you can define here. You can name your rule, and specify a reason. You can even paste a link. For example, I’ve defined a rule that says autowire is not allowed in a Spring field and I’ve pointed to a StackOverflow thread with a discussion on the topic. It’s a handy tool that can be used to easily share knowledge with junior developers.
- You can specify multiple rules. You can create them for Controllers, and other things – the ones I show you below are just a couple of examples.
One thing that’s missing in ArchUnit is a rule that would make it impossible for a parent module or a core module to call on a different layer. The library is still under development, however, so that option might be added one day. And even now, without it, this is a very useful tool that can make it easier to “clean” your app’s architecture.
“Clean up” your app’s architecture
A “clean” architecture Java application will cause you far less headaches and problems than a less diligently built system. A good structure, can be quite beneficial for your application, especially in the case of big projects or long-term plans. It’s much easier to make changes and add new features when you don’t have to modify almost everything along with the thing you actually want to affect. Sure, it can sometimes be a bit problematic or time-consuming, but if you get in the right mindset, are mindful of the most common problems, and use tools such as ArchUnit, you can get things in order without too much of a hassle. Also, check out my other articles on the Pretius blog:
- JVM Kubernetes: Optimizing Kubernetes for Java Developers
- Project Valhalla – Java on the path to better performance
- Java 17 features: A comparison between versions 8 and 17. What has changed over the years?
Are you looking for talented software architects?
Pretius has a lot of experience with big enterprise-grade projects that can really benefit from a clean, orderly architecture. Do you want to create a well-organized application? Drop us a line at firstname.lastname@example.org (or use the contact form below). We’ll get back to you in 48 hours and tell you how we can help.