Clean Architecture With Quarkus
For my pet-project Gachou, I decided to use Java and Quarkus to implement the backend. And since I have used clean-architecture at work for a couple of years now, I decided to use the same thing here. A pity, that we always used Spring Boot and Quarkus has some quirks of its own in this respect. Read on, if you want to know how I solved the exercise...
Clean Architecture Gradle Setup / Maven Setup
When I started working at cosee, I learned about clean-architecture and began to like it. I don’t want to explain clean-architecture in detail here. If you want more, your can
- read Robert C. Martin’s blog post
- watch his talk about architecture at “Coding Better World Together”.
- watch my TechTalk about how we implemented it with Spring Boot.
I just want to repeat the basic principle: In clean architecture, you have a some domain-model and some core business-logic. And everything else database, user interface, external services; everything is grouped around this core. There are no dependencies going from the core to the database or the user interface. All dependencies are pointing to the core.
The core
is the part of the system that should change the least and everything
else communicates through it. If you have some business logic that uses the
database, you define an interface in the core logic and let the database module
implement that interface.
That way, you can change the database logic, even replace the database, without affecting the other parts of the system.
Enforcing dependency direction
How do you implement this architecture? You could write a nice documentation that tells everybody not to use JDBC directly from core. But who reads documentation?
We need a way to enforce and compile-time-check the correct architecture and at cosee, we use multi-module Maven projects to do this.
- The parent pom contains the generic setup and dependencies that are used by all modules, like JUnit for testing, Java version and so on.
- There is one
core
-module as described above. - The
database
depends on thecore
-module and on JDBC, hibernate etc. - The
rest-api
depends on thecore
-module and on RESTEasy, Jackson etc. - There is one
app
module that depends oncore
,database
andrest-api
, that contains nothing apart from the project configuration and the dependency-injection framework.
My Gradle failure
I did some research and found this post at “Sourced Blog”, which describes how to setup multiple modules with Quarkus. The blog post uses Maven to manage dependencies, but I noticed that the linked example project uses Gradle instead.
I have never used Gradle before in any significant project, but I have to admit
that the build.gradle
-file in the example looks much simpler than the
pom.xml
files that I have written most of the time. So I tried to do it with
Gradle.
Spoiler: It didn’t work well
When I added app
as a module to the project, Quarkus didn’t build the
application correctly. This may be due to my lack of Gradle knowledge. I then
tried to remove app
and use the whole project as app
module instead. This
resulted in weird errors: Every bean defined in a module seemed to occur twice
in the container.
My Maven success
I could have spent more time trying to make it work with Gradle. I am sure there is some kind of solution. But I have worked with Maven for over 12 years now and the chances of success were much bigger, so I went that way.
I don’t want to get into the details of Maven multi-module projects here, I just want to mention the things that didn’t work for me at first, and my solutions.
I started out by generating a
Quarkus project with Maven. Then I
added the extensions that I needed (rest-easy, hibernate-panache etc). After
that, I added the modules and split up the pom.xml
into parent-pom and module
poms.
quarkus-maven-plugin
in the parent pom
My first thought was that the quarkus-maven-plugin
was only required in the
app
module, because this module becomes that actual application in the end.
This didn’t work. The quarkus-maven-plugin
defines most of the build steps
that are needed to build the Quarkus application. But if it is not present in
the parent-pom, Maven just ignores it when you run mvn install
. You can run
mvn install
in the app module and get the error message that core
,
rest-api
and database
are not available, because you have to run
mvn install
in those modules as well…
My solution was to add quarkus-maven-plugin
to the parent pom, but without any
executions.
<!-- /pom.xml -->
<boild>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
</plugin>
</plugins>
</boild>
Then, the app
-module also gets the quarkus-maven-plugin
, but with the proper
executions that were initially generated by the quarkus create app
command.
<!-- /app/pom.xml -->
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Now, running mvn install
first builds all dependencies and then runs the
plugins to build the Quarkus project.
beans.xml and jandex-maven-plugin
Unlike Spring-Boot, Quarkus only scans for beans in the current project, not in
external project. However, from the point of view of our app
modules, core
,
database
and rest-api
are “external” so they are not scanned by default.
There are two alternative ways to tell Quarkus to scan for beans in those
projects
You can put an empty file src/main/resources/META-INF/beans.xml
into each
module. This will indicate to Quarkus that it is worth scanning the module for
beans.
You can also generate a class index using the jandex-maven-plugin
. I have done
this by adding the following snippet to the parent pom.
<plugin>
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>1.2.2</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
Should you use both options even if you need only one? I don’t know.
I would expect bean discovery to be slower for large projects that only use the
beans.xml
-method, but I have no performance data to back that up.
If you only use the jandex-maven-plugin
, you cannot rely on the IDE to compile
the project correctly. You must use Maven.
I decided to implement both options for now, because I don’t see a disadvantage in doing so.
You can read more about this at Baeldung.
Conclusion
It is possible to create a clean-architecture backend setup with Quarkus and Maven. There are some problems on the way, but they can be solved.
If you are looking for a complete example of a Quarkus clean-architecture project using multiple modules, have a look at the Gachou backend. At the moment, there is hardly any functionality, just a login-endpoint. But it uses multiple module and it compiles natively.
I hope you could learn from my errors. Have fun with Quarkus…