Skip to content

Multi-module setup with outlet overrides

This guide shows how to use JoinedWorkz in a multi-module Maven project and route generated artefacts (Java sources, OpenAPI, diagrams, …) into different modules using outlet overrides and layers.

The goal is:

  • keep models in a dedicated module
  • keep generated code close to the modules that use it (domain, API, documentation)
  • avoid copying or manually moving generated files

It builds on:


1. Example project structure

We’ll use a typical layered project as running example:

text
my-app/
  pom.xml                 (parent)
  model/                  (CMN models + JoinedWorkz plugin)
  backend/                (domain + business logic)
  webapp/                 (Spring Boot application, controllers, HTTP APIs)
  docs/                   (optional: documentation / diagrams)

High-level responsibilities:

  • model
    • contains CMN models (.cmn)
    • runs the JoinedWorkz Maven plugin
  • backend
    • contains domain code (entities, services, persistence)
    • receives generated domain-related Java code
  • webapp
    • contains the Spring Boot application and hand-written controllers/handlers
    • receives generated API DTOs, controllers and OpenAPI
  • docs (optional)
    • receives generated diagrams or HTML OpenAPI viewers

All routing of generated outputs is controlled via:

  • layers (e.g. core, backend, api)
  • joinedworkz.properties in the root of the model module

2. Parent POM and module order

In my-app/pom.xml you declare the modules in a sensible order:

xml
<modules>
    <module>model</module>
    <module>backend</module>
    <module>webapp</module>
    <module>docs</module>
</modules>

Important:

  • The model module must appear before the modules that consume generated artefacts (backend, webapp, docs), so that generation runs before they are compiled.

Maven will then:

  1. build model (runs the JoinedWorkz plugin)
  2. place generated outputs into the configured directories (possibly in sibling modules)
  3. compile backend, webapp, docs using the generated code/resources

3. Model module setup

In model/pom.xml you configure:

  • the JoinedWorkz facilities (e.g. spring-boot)
  • the cmn-maven-plugin
  • the model/ folder as a resource root

Example:

xml
<project>
    ...
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <joinedworkz.version>1.3.74</joinedworkz.version>
    </properties>

    <dependencies>
        <!-- Spring Boot facility (brings in Java + Base) -->
        <dependency>
            <groupId>org.joinedworkz.facilities</groupId>
            <artifactId>spring-boot</artifactId>
            <version>${joinedworkz.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>model</directory>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.joinedworkz.cmn</groupId>
                <artifactId>cmn-maven-plugin</artifactId>
                <version>${joinedworkz.version}</version>
                <executions>
                    <execution>
                        <?m2e ignore?><!-- ignore this execution in Eclipse -->
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The model module itself typically does not contain Java production code. Its job is to:

  • hold the CMN models
  • run the generators
  • route outputs into other modules

4. Layers and CMN package headers

Layers are defined in the header of CMN files and are used both for platform conventions (e.g. SpringBoot) and for outlet routing.

A common pattern in a multi-module Spring Boot project:

  • core – internal domain types
  • api – external API DTOs and resources
  • backend – components, applications, technical backend configuration

Example CMN headers:

cmn
core package com.example.customer

import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.common.base.api
import org.joinedworkz.facilities.profiles.spring.boot

platform SpringBoot
cmn
api package com.example.customer.api

import com.example.customer
import org.joinedworkz.facilities.common.base.api
import org.joinedworkz.facilities.profiles.spring.boot

platform SpringBoot
cmn
backend package com.example.customer.backend

import com.example.customer.api
import org.joinedworkz.facilities.profiles.spring.boot

platform SpringBoot

Typical usage:

  • core: entities, value objects, domain model
  • api: DTOs (CustomerView, OrderSummary, …) and resources
  • backend: components (component CustomerBackend), applications, etc.

5. Outlets: what you can override

Outlets define where generated artefacts are written, e.g.:

  • generatedJavaSource – Java sources (from Java/SpringBoot)
  • generatedOpenApi – OpenAPI YAML
  • generatedOpenApiHtml – HTML OpenAPI viewer
  • generatedDiagram – diagrams
  • generatedSchema – schemas

The Base and SpringBoot profiles define default directories, for example:

  • ./src/generated/resources/openapi
  • ./diagram/api
  • ./diagram
  • ./target/joinedworkz/schema

In a multi-module setup these defaults are often not what you want. Instead, you use joinedworkz.properties to:

  • define a global default, and
  • override per layer.

Pattern:

properties
outlet.<outletName>.directory=...
outlet.<outletName>.<layer>.directory=...

6. joinedworkz.properties – routing examples

Create model/joinedworkz.properties in the model module.

6.1 Basic routing for Java sources

Assume:

  • core types (domain) should go to backend/src/generated/java
  • api types (controllers/DTOs) should go to webapp/src/generated/java
properties
# default for generated Java sources (not used directly here)
outlet.generatedJavaSource.directory=src/generated/java

# domain-related Java code (core layer) to backend module
outlet.generatedJavaSource.core.directory=../backend/src/generated/java

# API controllers and DTOs (api layer) to web module
outlet.generatedJavaSource.api.directory=../webapp/src/generated/java

Any generator that writes to generatedJavaSource will:

  • put artefacts from CMN models in the core layer into the backend module
  • put artefacts from CMN models in the api layer into the webapp module

6.2 Routing OpenAPI and HTML viewers

OpenAPI and the HTML viewer usually live in the web boundary module (webapp):

properties
# OpenAPI YAML only for the API layer
outlet.generatedOpenApi.api.directory=../webapp/src/generated/resources/openapi

# HTML OpenAPI viewer (also only API layer)
outlet.generatedOpenApiHtml.api.directory=../webapp/diagram/api

You can still keep the default for other layers if needed:

properties
outlet.generatedOpenApi.directory=src/generated/resources/openapi
outlet.generatedOpenApiHtml.directory=diagram/api

In this setup:

  • OpenAPI generated from api-layer models goes to webapp
  • OpenAPI generated from other layers (if any) would stay local in model

6.3 Routing diagrams and schemas to docs

If you have a docs module that collects diagrams and schemas:

properties
# diagrams (e.g. component / application diagrams)
outlet.generatedDiagram.directory=../docs/diagram

# schemas (if used)
outlet.generatedSchema.directory=../docs/target/joinedworkz/schema

Depending on your needs you can also route diagrams per layer, for example only application-level diagrams to docs, and more technical diagrams to backend.


7. Configuring target modules

The modules that receive generated artefacts must register the directories in their POMs.

7.1 Backend module: generated Java sources

backend/pom.xml:

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <id>add-generated-source</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>add-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>${basedir}/src/generated/java</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

You now can use generated domain types or DTOs from the backend module as if they were hand-written sources.

7.2 Webapp module: generated Java + resources

webapp/pom.xml should handle:

  • generated Java sources (controllers, API DTOs, handlers)
  • generated resources (OpenAPI YAML, HTML)
xml
<build>
    <resources>
        <!-- your existing resources -->
        <resource>
            <directory>src/main/resources</directory>
        </resource>

        <!-- generated resources (OpenAPI, etc.) -->
        <resource>
            <directory>src/generated/resources</directory>
        </resource>
    </resources>

    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <id>add-generated-source</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>add-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>${basedir}/src/generated/java</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Now:

  • the generated controllers/handlers become part of the webapp’s codebase
  • the generated OpenAPI YAML is packaged as a resource, and can be served or used by other tools

7.3 Docs module: diagrams and schemas

In docs/pom.xml, register the diagram and schema directories:

xml
<build>
    <resources>
        <resource>
            <directory>diagram</directory>
        </resource>
        <resource>
            <directory>target/joinedworkz/schema</directory>
        </resource>
    </resources>
</build>

You can then publish or serve the generated diagrams together with other documentation.


8. Build and verify

From the project root:

bash
mvn clean package

Check the results:

  • model/

    • contains only CMN models and joinedworkz.properties
    • may have temporary build output (e.g. target), but no production code
  • backend/src/generated/java

    • contains domain-related generated Java types (from core layer)
  • webapp/src/generated/java

    • contains API-layer controllers, DTOs, handlers (from api layer)
  • webapp/src/generated/resources/openapi

    • contains OpenAPI YAML
  • webapp/diagram/api

    • contains HTML OpenAPI viewers (if enabled)
  • docs/diagram, docs/target/joinedworkz/schema

    • contain diagrams and schemas (if routed there)

If something is missing:

  • check the outlet names used in joinedworkz.properties
  • check that the layers in your CMN headers match the layer names used in the overrides
  • check that the target modules include the directories as sources/resources

9. Variants and tips

A few common variations:

  • Only OpenAPI, no Java
    If you only want centralised OpenAPI, you can:

    • restrict joinedworkz.properties to generatedOpenApi and generatedOpenApiHtml
    • skip generatedJavaSource and source registration in target modules
  • Multiple bounded contexts
    Use separate packages and layers per bounded context (e.g. core/api per domain area) and route them to different modules or subprojects as needed.

  • Platform combinations
    You can use multiple platforms/facilities in the same model module (e.g. SpringBoot + another Java-based platform), and still route their outputs based on layers and outlets.

  • Gradual migration
    Start with a minimal multi-module setup (model + one target module), then introduce more modules and layer-specific overrides as the system evolves.


10. Summary

In a multi-module setup, outlets and layers give you fine-grained control over where generated artefacts end up:

  1. Keep models in a dedicated module that runs the JoinedWorkz plugin.
  2. Use layers (e.g. core, api, backend) in CMN headers to express whether elements are domain, API or technical composition.
  3. Configure joinedworkz.properties to route:
    • domain artefacts to backend modules,
    • API artefacts and OpenAPI to web/API modules,
    • diagrams/schemas to docs modules.
  4. Register generated directories as sources/resources in the target modules’ POMs.
  5. Let the multi-module build take care of calling the generators first and then compiling all modules against the generated code.

This keeps your project structure clean and lets you scale modelling and generation across multiple modules without losing track of where artefacts come from or where they belong.