Appearance
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
- contains CMN models (
- 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.propertiesin 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:
- build
model(runs the JoinedWorkz plugin) - place generated outputs into the configured directories (possibly in sibling modules)
- compile
backend,webapp,docsusing 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 typesapi– external API DTOs and resourcesbackend– 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 SpringBootcmn
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 SpringBootcmn
backend package com.example.customer.backend
import com.example.customer.api
import org.joinedworkz.facilities.profiles.spring.boot
platform SpringBootTypical 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 YAMLgeneratedOpenApiHtml– HTML OpenAPI viewergeneratedDiagram– diagramsgeneratedSchema– 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:
coretypes (domain) should go tobackend/src/generated/javaapitypes (controllers/DTOs) should go towebapp/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/javaAny generator that writes to generatedJavaSource will:
- put artefacts from CMN models in the
corelayer into the backend module - put artefacts from CMN models in the
apilayer 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/apiYou can still keep the default for other layers if needed:
properties
outlet.generatedOpenApi.directory=src/generated/resources/openapi
outlet.generatedOpenApiHtml.directory=diagram/apiIn this setup:
- OpenAPI generated from
api-layer models goes towebapp - 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/schemaDepending 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 packageCheck the results:
model/- contains only CMN models and
joinedworkz.properties - may have temporary build output (e.g.
target), but no production code
- contains only CMN models and
backend/src/generated/java- contains domain-related generated Java types (from
corelayer)
- contains domain-related generated Java types (from
webapp/src/generated/java- contains API-layer controllers, DTOs, handlers (from
apilayer)
- contains API-layer controllers, DTOs, handlers (from
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.propertiestogeneratedOpenApiandgeneratedOpenApiHtml - skip
generatedJavaSourceand source registration in target modules
- restrict
Multiple bounded contexts
Use separate packages and layers per bounded context (e.g.core/apiper 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:
- Keep models in a dedicated module that runs the JoinedWorkz plugin.
- Use layers (e.g.
core,api,backend) in CMN headers to express whether elements are domain, API or technical composition. - Configure
joinedworkz.propertiesto route:- domain artefacts to backend modules,
- API artefacts and OpenAPI to web/API modules,
- diagrams/schemas to docs modules.
- Register generated directories as sources/resources in the target modules’ POMs.
- 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.
