Micronaut: multi-module project with Maven

Just recently, I decided to split a project that I’ve been working on for a few months now into modules. Since microservices is not yet justifiable, I’ve split it into modules to achieve a higher level of organization and separation of concerns. This project is powered by Micronaut.

During the split, I tried different approaches without success. Ultimately, what we’ll see in this post worked fine for me and I hope it’ll work for you. Of course, if the necessity of applying such a thing emerges.

Micronaut CLI and folder structure

As an example of how to separate a Micronaut project into a multi-module one, first, we need a base project. Micronaut provides us with a CLI, making it a simple task. The only thing here is to ensure that Micronaut’s CLI in the path. If it’s missing, we can configure it by following this instructions. Once we have it, let’s create our base project:

mn create-app --build maven micronaut-multi-module

The execution of the command above will create a new Maven project with this folder structure:

micronaut-multi-module
|- src
|- pom.xml

Now, let’s consider we’re moving from this simple project into a multi-module consisting of a module called account and another called core. In this case, a folder structure that we’re looking for seems like this:

micronaut-multi-module
|- account
|  |- pom.xml
|- core
|  |- pom.xml 
|- startup
|  |- pom.xml
|- pom.xml (1)

Okay, next we’ll see how each part plays its game.

Parent pom

The parent pom should be represented in this way:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
		 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- parent pom -->
  <groupId>es.usoar.tutorials</groupId>
  <artifactId>micronaut-multi-module-parent</artifactId>
  <version>x</version>
  <packaging>pom</packaging>

  <modules>
    <module>account</module>
    <module>core</module>
    <module>startup</module>
  </modules>

  <!-- Hidden for brevity -->

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-bom</artifactId>
        <version>${micronaut.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <dependency>
        <groupId>es.usoar.tutorials</groupId>
        <artifactId>micronaut-multi-module-account</artifactId>
        <version>${app.version}</version>
      </dependency>
      <dependency>
        <groupId>es.usoar.tutorials</groupId>
        <artifactId>micronaut-multi-module-core</artifactId>
        <version>${app.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <!-- Hidden for brevity -->
</project>

Within the parent pom, we can define all the modules that belong to the project as a whole. Also, we can inform dependencies, properties and configuration that will be shared with all the modules. In the parent pom is where we’ll define our annotation processors.

One of the key aspects of Micronaut is the ability to know ahead of time which code should be generated to make the application work properly. It does that by using annotation processors. When using Maven, these annotation processors are attached to the maven-compiler-plugin plugin. Considering how this multi-project is organized, the annotation processors should be inside of the parent pom, as we can see here:

<build>
  <pluginManagement>
    <plugins>
      <!-- Hidden for brevity -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <compilerArgs>
            <arg>-parameters</arg>
          </compilerArgs>
          <annotationProcessorPaths>
            <path>
              <groupId>io.micronaut</groupId>
              <artifactId>micronaut-inject-java</artifactId>
              <version>${micronaut.version}</version>
            </path>
            <path>
              <groupId>io.micronaut</groupId>
              <artifactId>micronaut-validation</artifactId>
              <version>${micronaut.version}</version>
            </path>
          </annotationProcessorPaths>
        </configuration>
        <executions>
          <execution>
            <id>test-compile</id>
            <goals>
              <goal>testCompile</goal>
            </goals>
            <configuration>
              <compilerArgs>
                <arg>-parameters</arg>
              </compilerArgs>
              <annotationProcessorPaths>
                <path>
                  <groupId>io.micronaut</groupId>
                  <artifactId>micronaut-inject-java</artifactId>
                  <version>${micronaut.version}</version>
                </path>
                <path>
                  <groupId>io.micronaut</groupId>
                  <artifactId>micronaut-validation</artifactId>
                  <version>${micronaut.version}</version>
                </path>
              </annotationProcessorPaths>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

With this dependency in place, Maven will ensure that the annotation processors work the way it’s intended for all modules.

Centralizing module

Once we start moving towards multi-module with Micronaut, we’ll need to have a centralizing module. In this example, the module is called startup and contains reference to all the modules in the application. Here is the pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <artifactId>startup</artifactId>

  <parent>
    <groupId>es.usoar.tutorials</groupId>
    <artifactId>micronaut-multi-module-parent</artifactId>
    <version>x</version>
  </parent>

  <properties>
    <exec.mainClass>es.usoar.tutorials.micronaut.Application</exec.mainClass>
  </properties>

  <dependencies>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
      <scope>runtime</scope>
    </dependency>

    <dependency>
      <groupId>es.usoar.tutorials</groupId>
      <artifactId>micronaut-multi-module-account</artifactId>
    </dependency>
    <dependency>
      <groupId>es.usoar.tutorials</groupId>
      <artifactId>micronaut-multi-module-core</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>${exec.mainClass}</mainClass>
                </transformer>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <configuration>
          <executable>java</executable>
          <arguments>
            <argument>-classpath</argument>
            <classpath/>
            <argument>-noverify</argument>
            <argument>-XX:TieredStopAtLevel=1</argument>
            <argument>-Dcom.sun.management.jmxremote</argument>
            <argument>${exec.mainClass}</argument>
          </arguments>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

It’s important to notice that our exec-maven-plugin and maven-shade-plugin are here now. The parent pom holds our annotation processors. This separation is important to make it work. Also, inside of the startup module, we can put our application.yml, logback.xml or any other configuration file used by the application.

Conclusion

Alright, this is how we go from a simple Micronaut project to a multi-module one. The other modules such as core and account are normal modules referencing the parent, as we can see in this snippet:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <artifactId>micronaut-multi-module-core</artifactId>

  <parent>
    <groupId>es.usoar.tutorials</groupId>
    <artifactId>micronaut-multi-module-parent</artifactId>
    <version>x</version>
  </parent>
</project>

When the project starts growing, a multi-module project can be an option for better separation of concerns. It’s working great so far :)

The source code for this post can be found on GitHub.

See you next time!