Search code examples
mavenquarkusmulti-module

Quarkus multi-module Maven project with integration tests, problems with including quarkus-maven-plugin multiple times, for each module?


At work I'm splitting up an existing Quarkus application into several modules:

  • three "application modules" A, B and C containing controllers/API endpoints
  • a single shared module for shared code and database access
  • a deployment module that bundles all modules together for deployment

The idea is that I can combine different versions of A, B and C in production (perhaps I am not ready to deploy my changes to A to production, even though I want to deploy the changes in B now).

Strictly speaking, the quarkus-maven-plugin is only required for the deployment module because that is the only module that I will want to run as a Quarkus application.

However, because each of the application modules is responsible for a set of endpoints, it seems logical to put the integration tests (@QuarkusTests) for those in the application modules themselves. However, this will require the quarkus-maven-plugin for each of the modules, only for the purpose of being able to run the integration tests.

Are there any adverse effects of doing this?

An alternative I'm considering is to put the integration tests for each application module in seperate modules (A-tests, B-tests and C-tests) and include the quarkus-maven-plugin in those. Is that better?


Solution

  • It can be solved but it is not straight forward.

    The following tasks have to be done

    1. Create Multi Module Maven project where
      • deployable and non-deployable (shared) modules should be configured differently
      • shared modules should be allowed to use as dependency in other modules
    2. Write tests where
      • Each module should contain their own tests
      • Test classes must be shared
      • Integration tests of shared modules should run during the deployable module's integration test phase

    At first, I have to clarify this

    the quarkus-maven-plugin is only required for the deployment module because that is the only module that I will want to run as a Quarkus application.

    One of the most important concept of Quarkus to favor build time work over runtime. That's why quarkus-maven-plugin does much more than just build deployable artifact. So, this plugin is always required by each module wheter that module is deployable or not.

    Create Multi Module Maven project

    Let's say there are three modules (four pom.xml files)

    • *-parent - An aggregator which contains common settings, plugin templates, dependencies used by all submodules, etc.

    • *-module-[a-z] - application modules contains business logic, endpoints, shared entities. There modules are not deployable artifacts.

    • *-deployment - Deployable Quarkus application which can use other modules as dependencies, can contain other business logic and / or endpoints, entities, whatever.

    aggregator

    <?xml version="1.0"?>
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
             xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <modelVersion>4.0.0</modelVersion>
        <groupId>io.github.zforgo</groupId>
        <artifactId>so-78088326-quarkus-multi-module-testing-parent</artifactId>
        <version>0.1.0-SNAPSHOT</version>
        <packaging>pom</packaging>
        <properties>
            <compiler-plugin.version>3.12.1</compiler-plugin.version>
            <maven.compiler.release>17</maven.compiler.release>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
            <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
            <quarkus.platform.version>3.8.1</quarkus.platform.version>
            <skipITs>true</skipITs>
            <surefire-plugin.version>3.2.5</surefire-plugin.version>
        </properties>
        <modules>
            <module>so-78088326-module-a</module>
            <module>so-78088326-module-b</module>
            <module>so-78088326-deployment</module>
        </modules>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>${quarkus.platform.group-id}</groupId>
                    <artifactId>${quarkus.platform.artifact-id}</artifactId>
                    <version>${quarkus.platform.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-arc</artifactId>
            </dependency>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-junit5</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.rest-assured</groupId>
                <artifactId>rest-assured</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>test-jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>${quarkus.platform.group-id}</groupId>
                        <artifactId>quarkus-maven-plugin</artifactId>
                        <version>${quarkus.platform.version}</version>
                        <extensions>true</extensions>
                        <executions>
                            <execution>
                                <id>generate-code</id>
                                <goals>
                                    <goal>generate-code</goal>
                                    <goal>generate-code-tests</goal>
                                </goals>
                            </execution>
                            <execution>
                                <id>build-deployable-app</id>
                                <goals>
                                    <goal>build</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>${compiler-plugin.version}</version>
                        <configuration>
                            <compilerArgs>
                                <arg>-parameters</arg>
                            </compilerArgs>
                        </configuration>
                    </plugin>
                    <plugin>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>${surefire-plugin.version}</version>
                        <configuration>
                            <systemPropertyVariables>
                                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                                <maven.home>${maven.home}</maven.home>
                            </systemPropertyVariables>
                        </configuration>
                    </plugin>
                    <plugin>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <version>${surefire-plugin.version}</version>
                        <inherited>true</inherited>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>integration-test</goal>
                                    <goal>verify</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <systemPropertyVariables>
                                <native.image.path>${project.build.directory}/${project.build.finalName}-runner
                                </native.image.path>
                                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                                <maven.home>${maven.home}</maven.home>
                            </systemPropertyVariables>
                        </configuration>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </project>
    

    There are two remarkable things in the <pluginManagement>section.

    1. The maven-jar-plugin configured to create another artifact which contains test classes (more precisely all classes and resources under test folder). This configuration allows us to share even integration tests between other modules.
    2. The quarkus-maven-plugin configured a little differently than as usual. The build goal moved under another <execution> section and got an <id>tag. From now, the build (which creates a deployable artifact) goal can be eliminated when it is needed,

    non-deployable application modules

    <?xml version="1.0"?>
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
             xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>io.github.zforgo</groupId>
            <artifactId>so-78088326-quarkus-multi-module-testing-parent</artifactId>
            <version>0.1.0-SNAPSHOT</version>
        </parent>
        <artifactId>so-78088326-module-a</artifactId>
        <properties>
        </properties>
        <dependencies>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-resteasy-reactive</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>${quarkus.platform.group-id}</groupId>
                    <artifactId>quarkus-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>build-deployable-app</id>
                            <phase>none</phase>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>
    

    A non-deployable Quarkus module can be referenced as a dependency, it can be started during the test phase but it cannot be started like a standalone Quarkus application. By binding the build-deployable-app execution to the none lifecycle phase, quarkus:build goal will be disabled.

    Don't forget to make these modules discoverable to the main (deployable) module's CDI bean discovery mechanism by configuring jandex-maven-plugin or by adding a beans.xml to every non-deployable submodules.

    deployment deployable module

    In the Quarkus ecosystem the deployment module means totally different. Every Quarkus extension has a deployment submodule that handles build time processing and so on.

    In this case a deployment module has to be build as a deployable Quarkus application using other submodules and probably some own code (e.g. other Rest endpoint)

    <?xml version="1.0"?>
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
             xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>io.github.zforgo</groupId>
            <artifactId>so-78088326-quarkus-multi-module-testing-parent</artifactId>
            <version>0.1.0-SNAPSHOT</version>
        </parent>
        <artifactId>so-78088326-deployment</artifactId>
        <properties>
            <skipITs>false</skipITs>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>${quarkus.platform.group-id}</groupId>
                    <artifactId>quarkus-maven-plugin</artifactId>
                </plugin>
                <!-- ... -->
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-resteasy-reactive</artifactId>
            </dependency>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-smallrye-openapi</artifactId>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>so-78088326-module-a</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>so-78088326-module-b</artifactId>
                <version>${project.version}</version>
            </dependency>
            <!-- ... -->
        </dependencies>
    </project>
    

    Here, the build goal of quarkus-maven-plugin enabled and bound to package lifecycle phase by default.

    Write, share and run tests

    Let's say every module has own tests. Every test class that annotated with @QuarkusTest should run during the submodule's test phase, but the @QuarkusIntegrationTests should be shared and run during integration-test phase of deployment module.

    Share tests between modules

    In the aggregator module maven-jar-plugin has already configured to create test jar artifact. These artifacts are available like:

    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>so-78088326-module-a</artifactId>
        <version>${project.version}</version>
        <scope>test</scope>
        <type>test-jar</type>
    </dependency>
    

    Enable / disable integration tests

    In the aggreagator module the integration tests disabled by default by using <skipITs>true</skipITs> property. Well, disabling the skipping of integration test will enable them, won't it? In the deployment module skipITs property has to be set to false

    Run integration tests from dependent modules

    In order to run integration tests from external artifacts the maven-failsafe-plugin has to be configured properly. The dependenciesToScan parameter allows to specify and execute integration tests from dependencies.

    <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-failsafe-plugin</artifactId>
       <configuration combine.children="append">
           <dependenciesToScan>
               <dependency>${project.groupId}:so-78088326-module-a:test-jar</dependency>
               <dependency>${project.groupId}:so-78088326-module-b:test-jar</dependency>
           </dependenciesToScan>
       </configuration>
    </plugin>
    

    Important! All those included dependencies must already be defined in <dependencies>

    For further options see the official documentation.