Automating Service Provisioning Series

Subscribe to my newsletter to receive updates when content like this is published.

  1. Implementing APIs using Spring Boot, CXF and Swagger
  2. Implementing a custom Spring Boot starter for CXF and Swagger
  3. Implementing a custom Maven Archetype to generate Spring Boot-based services (you are here)
  4. Automating service provisioning and CI/CD using AWS Pipeline, Bitbucket and Terraform (work in progress)

1. OVERVIEW

Let’s say your organization is splitting the big monolithic application in multiple, smaller and more specialized microservices. Some of them will interact with a relational database and a message broker while others will communicate with a NoSQL database for instance.

This is a great opportunity to standardize the new deliverables in your company. You might want to develop some custom Spring Boot starters responsable for providing common Java application components, JSON-formatted logging, tracing, metrics, monitoring, resilience, RDBMS and APIs implementation support, helping with the application bootstrapping process and reducing common boilerplate code in each Spring Boot-based service.

Once you are using these Spring Boot starters in an application, how would you use them in a new service? Copy and paste and change the artifact ID? Removing unused dependencies? Clean up properties files and Java classes? Although this process works, it involves manual labor which is always tedious and error-prone.

Custom Maven archetype

In this guide I describe how to implement and use a custom Maven archetype, which uses a custom Spring Boot starter, integrated in a multi-module Maven setup to automate new projects generation.

2. REQUIREMENTS

  • Java 7+.
  • Maven 3.2+.

3. CREATE asimio-api-archetype MAVEN MODULE

Let’s first add new module to the Maven parent pom found at custom Spring Boot starter’s parent module:

Parent pom.xml:

<modules>
  <module>../asimio-cxf-swagger-springboot-autoconfigure</module>
  <module>../asimio-cxf-swagger-springboot-starter</module>
+  <module>../asimio-api-archetype</module>
</modules>

so that this Maven archetype gets built as part of any change to any module of the custom Spring Boot starter.

Let’s now create the new Maven module asimio-api-archetype:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-archetype -DgroupId=groupId -DartifactId=asimio-api-archetype -DinteractiveMode=false

I would suggest to implement a proof of concept or demo application like asimio-api-starter-demo, discussed while creating an application using a custom Spring Boot starter, and use some of its Java classes and other files as the prototype for this archetype. The resulting folder structure should be similar to:

Custom Maven archetype folder structure
Creating a custom Maven archetype to generate Spring Boot applications with a common set of components

It’s pom file looks very simple.

pom.xml:

...
<parent>
  <groupId>com.asimio.api.starter</groupId>
  <artifactId>asimio-api-springboot-starter-parent</artifactId>
  <version>0-SNAPSHOT</version>
  <relativePath>../asimio-api-springboot-starter-parent</relativePath>
</parent>

<artifactId>asimio-api-archetype</artifactId>
<packaging>jar</packaging>
<name>Asimio API Maven Archetype</name>
<description>Asimio API Maven Archetype</description>
...

This archetype gets built as part of a Maven multi module setup, where the modules version, dependencies, etc. are handled in the parent pom.

Next comes the archetype descriptor file:

src/main/resources/META-INF/maven/archetype.xml:

<archetype
  xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0 http://maven.apache.org/xsd/archetype-1.0.0.xsd">

  <id>asimio-api-archetype</id>

</archetype>

which according to the documentation, the id should match the artifactId as found in pom.xml.

In the archetype metadata file you describe the properties and file sets this Maven archetype uses and processes:

src/main/resources/META-INF/maven/archetype-metadata.xml:

<?xml version="1.0" encoding="UTF-8"?>
<archetype-descriptor name="basic">

  <requiredProperties>
    <requiredProperty key="archetypeVersion">
      <defaultValue>RELEASE</defaultValue>
    </requiredProperty>
    <requiredProperty key="groupId">
      <defaultValue>com.asimio.api</defaultValue>
    </requiredProperty>
    <requiredProperty key="artifactId" />
    <requiredProperty key="version">
      <defaultValue>0-SNAPSHOT</defaultValue>
    </requiredProperty>
  </requiredProperties>

  <fileSets>
    <fileSet filtered="true" packaged="true">
      <directory>src/main/java</directory>
      <includes>
        <include>**/*.java</include>
      </includes>
    </fileSet>
    <fileSet filtered="true" packaged="false">
      <directory>src/main/resources</directory>
      <includes>
        <include>**/*</include>
      </includes>
    </fileSet>
...
<!-- Similarly for src/test/java and src/test/resources -->
  </fileSets>

</archetype-descriptor>

In this specific case, default values will be used if not specified in the command to generating a project:

Archetype property Description
archetypeVersion The version of the archetype to use as prototype to generate a Maven project, defaulting to RELEASE
groupId The group ID of the Maven project to be generated, defaulting to com.asimio.api
artifactId The artifact ID of the Maven project to be generated, without a default value
version The version of the Maven project to be generated, defaulting to 0-SNAPSHOT

A packaged fileSet means the included files will be generated in a folder structure prepended by the package property (defaulting to the groupId). For instance, if you use -DgroupId=com.acme to create a project from this archetype, the Java files will be generated in src/main/java/com/acme/.

A filtered fileSet means the included files will be processed as Velocity templates, expresions are will be evaluated, variables will be replaced, etc.. If filtered is false, files will be copied as is.

3.1. THE ARCHETYPE RESOURCES FILES

src/main/resources/archetype-resources is where you put the prototype files used as the source to generate a new application. They include pom.xml, Java classes, properties files, test classes, etc..

Let’s take a look at:

src/main/resources/archetype-resources/src/main/java/rest/v1/impl/HelloResourceImpl.java:

package ${groupId}.rest.v1.impl;
...
import ${groupId}.rest.v1.HelloResource;
import ${groupId}.rest.v1.model.Hello;

@Component("helloResourceV1")
public class HelloResourceImpl implements HelloResource {
...
}

Notice ${groupId} Velocity variable? That’s the why filtered was set to true for the fileSet including src/main/java in the archetype metadata file. Once this folder is processed during application generation, assuming the groupId property was to to com.acme, the generated Java class should look like:

package com.acme.rest.v1.impl;
...
import com.acme.rest.v1.HelloResource;
import com.acme.rest.v1.model.Hello;

@Component("helloResourceV1")
public class HelloResourceImpl implements HelloResource {
...
}

3.2. BUILDING THE ARCHETYPE

Lets build and install this Maven archetype locally so that it could be found in (~/.m2/repository) when generating a new Spring Boot-based service:

mvn versions:set -DnewVersion=1.0.4
...
[INFO] Processing com.asimio.api.starter:asimio-api-springboot-starter-parent
[INFO]     Updating project com.asimio.api.starter:asimio-api-springboot-starter-parent
[INFO]         from version 0-SNAPSHOT to 1.0.4
[INFO]
[INFO] Processing com.asimio.api.starter:asimio-api-archetype
[INFO]     Updating parent com.asimio.api.starter:asimio-api-springboot-starter-parent
[INFO]         from version 0-SNAPSHOT to 1.0.4
[INFO]
[INFO] Processing com.asimio.api.starter:asimio-cxf-swagger-springboot-autoconfigure
[INFO]     Updating parent com.asimio.api.starter:asimio-api-springboot-starter-parent
[INFO]         from version 0-SNAPSHOT to 1.0.4
[INFO]
[INFO] Processing com.asimio.api.starter:asimio-cxf-swagger-springboot-starter
[INFO]     Updating parent com.asimio.api.starter:asimio-api-springboot-starter-parent
[INFO]         from version 0-SNAPSHOT to 1.0.4
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] asimio-api-springboot-starter-parent ............... SUCCESS [  1.033 s]
[INFO] asimio-cxf-swagger-springboot-autoconfigure ........ SKIPPED
[INFO] asimio-cxf-swagger-springboot-starter .............. SKIPPED
[INFO] Asimio API Maven Archetype ......................... SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...

And now lets install version 1.0.4:

mvn clean install
...
[INFO] ------------------------------------------------------------------------
[INFO] Building asimio-api-springboot-starter-parent 1.0.4
[INFO] ------------------------------------------------------------------------
...
[INFO] Installing /Users/ootero/Downloads/asimio-api-springboot-starter-4/asimio-api-springboot-starter-parent/pom.xml to /Users/ootero/.m2/repository/com/asimio/api/starter/asimio-api-springboot-starter-parent/1.0.4/asimio-api-springboot-starter-parent-1.0.4.pom
...
[INFO] ------------------------------------------------------------------------
[INFO] Building asimio-cxf-swagger-springboot-autoconfigure 1.0.4
[INFO] ------------------------------------------------------------------------
...
[INFO] Installing /Users/ootero/Downloads/asimio-api-springboot-starter-4/asimio-cxf-swagger-springboot-autoconfigure/target/asimio-cxf-swagger-springboot-autoconfigure-1.0.4.jar to /Users/ootero/.m2/repository/com/asimio/api/starter/asimio-cxf-swagger-springboot-autoconfigure/1.0.4/asimio-cxf-swagger-springboot-autoconfigure-1.0.4.jar
[INFO] Installing /Users/ootero/Downloads/asimio-api-springboot-starter-4/asimio-cxf-swagger-springboot-autoconfigure/pom.xml to /Users/ootero/.m2/repository/com/asimio/api/starter/asimio-cxf-swagger-springboot-autoconfigure/1.0.4/asimio-cxf-swagger-springboot-autoconfigure-1.0.4.pom
...
[INFO] ------------------------------------------------------------------------
[INFO] Building asimio-cxf-swagger-springboot-starter 1.0.4
[INFO] ------------------------------------------------------------------------
...
...
[INFO] Installing /Users/ootero/Downloads/asimio-api-springboot-starter-4/asimio-cxf-swagger-springboot-starter/target/asimio-cxf-swagger-springboot-starter-1.0.4.jar to /Users/ootero/.m2/repository/com/asimio/api/starter/asimio-cxf-swagger-springboot-starter/1.0.4/asimio-cxf-swagger-springboot-starter-1.0.4.jar
[INFO] Installing /Users/ootero/Downloads/asimio-api-springboot-starter-4/asimio-cxf-swagger-springboot-starter/pom.xml to /Users/ootero/.m2/repository/com/asimio/api/starter/asimio-cxf-swagger-springboot-starter/1.0.4/asimio-cxf-swagger-springboot-starter-1.0.4.pom
...
[INFO] ------------------------------------------------------------------------
[INFO] Building Asimio API Maven Archetype 1.0.4
[INFO] ------------------------------------------------------------------------
...
...
[INFO] Installing /Users/ootero/Downloads/asimio-api-springboot-starter-4/asimio-api-archetype/target/asimio-api-archetype-1.0.4.jar to /Users/ootero/.m2/repository/com/asimio/api/starter/asimio-api-archetype/1.0.4/asimio-api-archetype-1.0.4.jar
[INFO] Installing /Users/ootero/Downloads/asimio-api-springboot-starter-4/asimio-api-archetype/pom.xml to /Users/ootero/.m2/repository/com/asimio/api/starter/asimio-api-archetype/1.0.4/asimio-api-archetype-1.0.4.pom
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] asimio-api-springboot-starter-parent ............... SUCCESS [  0.217 s]
[INFO] asimio-cxf-swagger-springboot-autoconfigure ........ SUCCESS [  1.698 s]
[INFO] asimio-cxf-swagger-springboot-starter .............. SUCCESS [  0.090 s]
[INFO] Asimio API Maven Archetype ......................... SUCCESS [  0.072 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...

A better choice would be to deploy the archetype artifact to a Maven repository manager like Nexus or Artifactory or to an AWS S3 bucket for the archetype to be reused outside of your development station.

4. GENERATING A SERVICE FROM A MAVEN ARCHETYPE

Let’s generate a project named asimio-api-demo:

mvn archetype:generate -DarchetypeGroupId=com.asimio.api.starter -DarchetypeArtifactId=asimio-api-archetype -DarchetypeVersion=RELEASE -DgroupId=com.asimio.api -DartifactId=asimio-api-demo -DinteractiveMode=false
...
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: asimio-api-archetype:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.asimio.api
[INFO] Parameter: artifactId, Value: asimio-api-demo
[INFO] Parameter: version, Value: 0-SNAPSHOT
[INFO] Parameter: package, Value: com.asimio.api
[INFO] Parameter: packageInPathFormat, Value: com/asimio/api
[INFO] Parameter: package, Value: com.asimio.api
[INFO] Parameter: version, Value: 0-SNAPSHOT
[INFO] Parameter: groupId, Value: com.asimio.api
[INFO] Parameter: archetypeVersion, Value: RELEASE
[INFO] Parameter: artifactId, Value: asimio-api-demo
[INFO] Project created from Archetype in dir: /Users/ootero/Downloads/temp/asimio-api-demo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.635 s
[INFO] Finished at: 2018-07-05T22:18:26-04:00
[INFO] Final Memory: 18M/312M
[INFO] ------------------------------------------------------------------------

Some VM arguments are explained below:

archetypeGroupId The group ID as defined in the archetype’s pom.xml.
archetypeArtifactId The artifact ID as defined in the archetype’s pom.xml.
archetypeVersion The version of the archetype to use, optional in this case because it was set to RELEASE in archetype-metadata.xml.
groupId The group ID of the new project, optional in this case because it was set to com.asimio.api in archetype-metadata.xml.
artifactId The artifact ID of the new project.
interactiveMode Setting it to false prevents Maven from prompting to user to enter values to the previously described archetype properties, reading them instead from the command line or properties file.

Lets now build it:

cd <path to>/asimio-api-demo
mvn clean package
...
Results :

Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ asimio-api-demo ---
[INFO] Building jar: /Users/ootero/Downloads/temp/asimio-api-demo/target/asimio-api-demo.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.3.RELEASE:repackage (default) @ asimio-api-demo ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.227 s
[INFO] Finished at: 2018-07-05T22:18:42-04:00
[INFO] Final Memory: 39M/406M
[INFO] ------------------------------------------------------------------------

And now you have a new Spring Boot-based application, generated from a custom Maven archetype, integrating Spring Boot, Apache CXF and Swagger via a custom Spring Boot starter with a handful of passing unit and integration tests.

But it could have also included a common set of components such us servlets, filters, listeners, support for fault tolerance, JSON-structured logs, tracing, exception handling, etc..

5. USE ‘RELEASE’ WITH CAUTION

If you look at newly created application asimio-api-demo’s pom.xml:

...
<dependency>
  <groupId>com.asimio.api.starter</groupId>
  <artifactId>asimio-cxf-swagger-springboot-starter</artifactId>
  <version>RELEASE</version>
</dependency>
...

It includes one RELEASE dependency to keep this guide and series short but it’s expected to include many in-house RELEASE artifacts. A change to any of these dependencies will impact the applications that use them.

If asimio-cxf-swagger-springboot-starter artifact is updated, the next time asimio-api-demo gets built, even without any modification, it will bundle a new version. There is risk in doing so and it’s a trade-off.

With a RELEASE version, applications get new functionality and bug fixes without any change to its pom file. But a change to the signature of a public method in the dependency will also break the applications.

On the other hand, if an application use a specific version, such as 1.0.4, there is no risk in it failing as the library keeps getting updated, but the application won’t get new functionality from this library either and there might be reluctance to uplift a dependency to 5.0.16, two years after 1.0.4 was released.

Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.

6. SOURCE CODE

Accompanying source code for this blog post can be found at:

7. REFERENCES