Skip to content

Generate your first Spring Boot service from a ZDL model

You will write one small ZDL model and generate a Spring Boot service from it. The service exposes a REST API defined with OpenAPI, publishes domain events documented with AsyncAPI, and comes with unit and integration tests.

We recommend SDKMAN! to install all of them.

Temurin (Eclipse) is a good open-source option for the Java JDK.

Use JBang to install an evergreen self-updating CLI for the ZenWave SDK.

jbang alias add --force --fresh --name=zw release@zenwave360/zenwave-sdk

NOTE: You can install ZenWave versions from different channels: release for the latest stable version and snapshots for development versions. They can be installed side by side using different --name=<alias> values, or independently.

ZenWave SDK generates business code and tests with no exotic dependencies. It works with any Spring Boot project.

You can start from a fresh project on start.spring.io or from a project you already have.

The simplest setup is a single-module Maven or Gradle project. This is a recommendation, not a requirement. Multi-module Maven projects also work, but you need to point each generator to the right module folder.

For the fastest start, use the ZenWave archetypes. They are kept up to date and come with the OpenAPI Generator and ZenWave SDK Maven plugins already configured. You choose JPA or MongoDB, Kafka and Spring Modulith when you generate the project.

Java:

mvn archetype:generate \
  -DarchetypeCatalog=internal \
  -DarchetypeGroupId=io.zenwave360.archetypes \
  -DarchetypeArtifactId=zenwave-java-archetype \
  -DarchetypeVersion=RELEASE \
  -DinteractiveMode=true

Kotlin (see Kotlin Backend Application):

mvn archetype:generate \
  -DarchetypeCatalog=internal \
  -DarchetypeGroupId=io.zenwave360.archetypes \
  -DarchetypeArtifactId=zenwave-kotlin-archetype \
  -DarchetypeVersion=RELEASE \
  -DinteractiveMode=true

Maven asks for the project name, description, artifactId, groupId and package. Then it asks which features to include:

[FEATURE] Support for JPA? y
[FEATURE] Support for MONGODB? n
[FEATURE] Support for KAFKA? n
[FEATURE] Support for MODULITH? y

Choose exactly one of JPA or MongoDB. Modulith implies Kafka. See the Java archetype and Kotlin archetype READMEs for all options.

The generated project already contains a sample zenwave-model.zdl and a zenwave-scripts.zw with the plugin configuration.

Create a zenwave-model.zdl file in the project root. This model has one aggregate, one service exposed as a REST API and one domain event. The plugins block in config lists the SDK plugins you will run in the next section.

config {
    title "ZenWave Customer JPA Example"
    basePackage "io.zenwave360.example"
    persistence jpa
    databaseType postgresql

    layout CleanHexagonalProjectLayout

    // these should match the values of openapi-generator-maven-plugin
    // used by the OpenAPIControllersPlugin and SpringWebTestClientPlugin
    openApiApiPackage "{{basePackage}}.adapters.web"
    openApiModelPackage "{{basePackage}}.adapters.web.model"
    openApiModelNameSuffix DTO

    plugins {
        ZDLToOpenAPIPlugin {
            idType integer
            idTypeFormat int64
            targetFile "src/main/resources/public/apis/openapi.yml"
        }

        ZDLToAsyncAPIPlugin {
            asyncapiVersion v3
            idType integer
            idTypeFormat int64
            targetFile "src/main/resources/public/apis/asyncapi.yml"
        }

        BackendApplicationDefaultPlugin {
            includeEmitEventsImplementation true
        }

        OpenAPIControllersPlugin {
            openapiFile "src/main/resources/public/apis/openapi.yml"
        }
    }
}

/**
* Customer entity
*/
@aggregate
@auditing // adds auditing fields to the entity
entity Customer {
    name String required maxlength(254) /** Customer name */
    email String required maxlength(254)
}

@rest("/customers")
service CustomerService for (Customer) {
    @post
    createCustomer(Customer) Customer withEvents CustomerEvent
    @get("/{id}")
    getCustomer(id) Customer?
}

@copy(Customer)
@asyncapi({ channel: "CustomersChannel", topic: "customers" })
event CustomerEvent {
    id Long required
    version Integer
}
  • persistence selects jpa or mongodb.
  • layout sets the package structure of the generated code.
  • @rest, @post and @get describe how the service is exposed as a REST API.
  • withEvents makes createCustomer publish CustomerEvent. @asyncapi documents it in AsyncAPI.

The full syntax is in ZDL Domain Language. A larger version of this model is the Customer Address JPA example.

Run every command from the project root.

1. Generate OpenAPI and AsyncAPI from the ZDL

Section titled “1. Generate OpenAPI and AsyncAPI from the ZDL”
jbang zw -p io.zenwave360.sdk.plugins.ZDLToOpenAPIPlugin \
    specFile=zenwave-model.zdl \
    idType=integer \
    idTypeFormat=int64 \
    targetFile=src/main/resources/public/apis/openapi.yml
jbang zw -p io.zenwave360.sdk.plugins.ZDLToAsyncAPIPlugin \
    specFile=zenwave-model.zdl \
    idType=integer \
    idTypeFormat=int64 \
    targetFile=src/main/resources/public/apis/asyncapi.yml

These API definitions are the source of truth for your inbound and outbound communications. You can edit them before the next step.

2. Configure the API-first generators in pom.xml

Section titled “2. Configure the API-first generators in pom.xml”

If you started from a ZenWave archetype, skip this step. Both generators are already configured.

Otherwise, add openapi-generator-maven-plugin to generate Spring MVC interfaces and DTOs from openapi.yml. Its packages must match openApiApiPackage and openApiModelPackage in your ZDL:

openapi-generator-maven-plugin in pom.xml

Navigate to source

Then add zenwave-sdk-maven-plugin with the AsyncAPIGenerator to generate event producers and DTOs from asyncapi.yml. Follow AsyncAPI Generator: Maven Usage. For Gradle, see Maven & Gradle Support.

Generate the domain entities, services, repositories and their tests:

jbang zw -p BackendApplicationDefaultPlugin \
    zdlFile=zenwave-model.zdl \
    includeEmitEventsImplementation=true \
    targetFolder=.

Generate the Spring MVC controllers that implement the OpenAPI interfaces:

jbang zw -p io.zenwave360.sdk.plugins.OpenAPIControllersPlugin \
    openapiFile=src/main/resources/public/apis/openapi.yml \
    zdlFile=zenwave-model.zdl \
    targetFolder=.

To see all options of a plugin, run it with --help:

jbang zw -p io.zenwave360.sdk.plugins.BackendApplicationDefaultPlugin --help

Run the tests:

mvn test

Start the infrastructure and the application. Projects created from the archetypes include a docker-compose.yml:

docker-compose up -d
mvn spring-boot:run

Open SwaggerUI at http://localhost:8080/swagger-ui/index.html.

With CleanHexagonalProjectLayout, the generated code is organized under your basePackage:

  • core.domain: the Customer entity.
  • core.inbound: the CustomerService interface and its DTOs.
  • core.outbound: the CustomerRepository Spring Data interface.
  • core.implementation: CustomerServiceImpl and its mappers. It saves the entity and publishes CustomerEvent.
  • adapters.web: CustomerApiController, implementing the interface generated from openapi.yml.
  • src/test: CustomerServiceTest with an in-memory repository, repository integration tests with Testcontainers, and controller tests.

OpenAPI interfaces, AsyncAPI producers and their DTOs are generated by the Maven build into target/generated-sources.

Generated service implementations, mappers and tests are a starting point. Add your business logic and test data there. To change layouts or templates, see Customizing Generated Code.

Learn how to evolve your models, generated code and AI-implemented business logic together: Iterative, AI-Assisted Model-Driven Development with ZenWave SDK.

The IntelliJ plugin is optional. It adds editing support for .zdl and .zw files and runs each plugin from a play button.

For rendering class diagrams in markdown preview, install the PlantUML Integration plugin.

NOTE: ZenWave Editor uses the jbang zw command to execute plugins, so you need JBang available in the PATH of the console configured by default in IntelliJ.

DDD In Practice: From Ubiquitous Language DSL to Complete Spring Boot Kotlin Project

Video: Generating a Complete Spring Boot Application with ZenWave SDK

Section titled “Video: Generating a Complete Spring Boot Application with ZenWave SDK”