Clean Architecture With Quarkus

April 29, 2022

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

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.

Diagram showing dependencies between modules

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.

Diagram showing core and database module with dependencies

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 the core-module and on JDBC, hibernate etc.
  • The rest-api depends on the core-module and on REASTEasy, Jackson etc.
  • There is on app module that depends on core, database and rest-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 look 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 multi-module clean-architecture project, 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…


Profile picture

Written by Nils Knappmeier who has been a full-time web-developer since 2006, programming since he was 10 years old.