In 10 minutes, you can publish a model jar that as well as versioning Spring Boot Microservices, gives clients a simple way to call them.

Overview

If you want to make life easy for your clients, it’s important to give them an easy way to access your Microservices. This is especially important if you have lots of clients – otherwise, they all have to build their own.

One simple way to do that is to provide them with a model jar that contains all of the model classes needed to access those services.

This model jar can be versioned whenever your services change and all your clients have to do is update the version in their build to get the latest interface.

Versioning Spring Boot Microservices Code

You can download the code from here and follow the readme instructions to run it.

git clone https://github.com/johndobie/spring-boot-versioning

Part 1 – Creating The Model.

Whenever you build Microservices in Spring Boot you will end up with some request and response model classes that are passed into your controllers.

It is these classes that a client will use to access your services.

Versioning Spring Boot Microservices Model

In the Java world, your code will look something like the above with the model classes in one package. These are the classes that need to give to your clients.

These classes make it easy for them to create the objects needed by your API.

We can tell Maven to package only those classes in a special model jar with a simple change to the pom.xml

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <executions>
        <execution>
            <id>package-model</id>
            <phase>package</phase>
            <goals>
                <goal>jar</goal>
            </goals>
            <configuration>
                <classifier>model</classifier>
                <includes>
                    <include>**/com/johndobie/springboot/web/model/**</include>
                </includes>
            </configuration>
        </execution>
    </executions>
</plugin>

You can now run a build

mvn clean install

This will produce the standard jar plus a spring-boot-versioning-1.0.0-model jar.

This new jar contains only the model classes required by your clients.
It is also versioned at the same level as your main jar

jar -xvf spring-boot-versioning-1.0.0-model.jar
 inflated: com/johndobie/springboot/web/model/Address.class
 inflated: com/johndobie/springboot/web/model/LoginDetails.class
 inflated: com/johndobie/springboot/web/model/User.class

Using The Model Jar as a Client.

Clients can import your model classes in the standard Maven way and use them with any client technology they want.

The key is the classifier which specifies the model

pom.xml
<dependencies>
    ..
    <dependency>
        <groupId>com.johndobie</groupId>
        <artifactId>spring-boot-versioning</artifactId>
        <classifier>model</classifier>
        <version>1.0.0</version>
    </
    dependency>
    ..

Adding Some Clients.

Sometimes in a project, you might also want to provide the clients that make the REST calls. You can do this and publish them in the same way as the model.

Add as many classes or packages as you like.

XML
<classifier>model</classifier>
<includes>
    <include>**/com/johndobie/springboot/web/model/**</include>
    <include>**/com/johndobie/springboot/web/client/**</include>
</includes>

Part 2 – Versioning Spring Boot Microservices

Versioning.

You need to version your API every time you make a change and you need to decide how that version number will change.

There are 2 main types of changes.

  • non-breaking changes
  • breaking changes.

We will go into the differences below, but for now, all you need to know is that you will change the version number depending on the type of change.

Versioning Strategy – just pick something.

Conversations about versioning strategy can get pretty heated, probably at the same level as tabs vs spaces. They require the same treatment which is to “just pick something” and make sure it’s documented.

Versioning Spring Boot Microservices With Semantic Versioning

It’s common to use the major and minor versions of semantic versioning.

Versioning Spring Boot Microservices With Semantic Versioning
  • Use Major for breaking changes [b]
  • Use Minor for non-breaking changes [nb]

For a run of 4 breaking [b] and 2 non-breaking [nb] changes you get to version 5.0.0 as below.

1.0.0 -> 2.0.0 [b] -> 3.0.0 [b] -> 3.1.0 [nb] -> 4.0.0 [b] -> 4.1.0 [nb] -> 5.0.0 [b]

This is easy to do in your pipeline using standard Maven versioning.

Because most changes are breaking changes, your major versions may increase rapidly.

Using Patch To Reduce Major Increments

If you look at the common maven repos, the major version number does not go up all that often. With the standard semantic versioning model above, you can see that is not the case.

If you are not happy with lots of major version changes, then just change your strategy.

  • Use Minor for breaking changes [b]
  • Use Patch for non-breaking changes [nb]
  • Only use Major when making a major shift in your API.

For a run of 4 breaking [b] and 2 non-breaking [nb] changes, you will instead get to version 1.4.0

1.0.0 -> 1.1.0 [b] -> 1.2.0 [b] -> 1.2.1 [nb] -> 1.3.0 [b] -> 1.3.1 [nb] -> 1.4.0 [b]

Which Strategy to Use

Just choose one. The important thing is that you make it clear when a breaking change occurs.

The versioning alone will not be enough for this anyway so it’s just an indicator.
It should be accompanied by examples and documentation.

Hopefully, no one is relying on versioning alone!

What is a non-breaking change?

A non-breaking change will not cause an issue for a client when passing the current data.

versioning spring boot microservices non-breaking change

They include

  • adding a new endpoint
  • changing the content of data returned
  • adding a new optional field.

Adding a New Optional Field

This is the most common non-breaking change and it is when you decide to add a new field that a client can optionally pass in the API.

It is non-breaking because the clients can choose to add this field or leave it off.
It doesn’t matter, and it will cause them no issues with what they are passing now.

private String newField;  

What is a breaking change?

A breaking change will cause an issue for a client when passing the current data.

versioning spring boot microservices breaking change

They include

  • adding a new mandatory field
  • changing the type of an existing field
  • removing an existing field
  • tightening constraints on an existing field
  • changing status codes

Adding a new Mandatory field

Adding a mandatory field is a breaking change.

@NotNull
private String newField;

It relies on your client passing that field in and because your existing clients do not pass this field in, they will get an error.

{"errors":[{"code":"NotNull","detail":"newField may not be null"}]}

Change the type of an existing field.

In most cases changing type will result in your client breaking because their request will be rejected when they pass in the old format.

When using the new model, you should also get a compilation error.

Java
private Integer age;  // age : 15
private String age;   // age : "15"

Tightening a constraint on an existing field.

Reducing the max length of a field from 15 to 10 could break your clients.

Java
@Size(min = 1, max = 15)
private String name; // John Andrew - fine 

@Size(min = 1, max = 10)
private String name; // John Andrew - too long

When passing in a value that was fine before, you end up with this error.

{"errors":[{"code":"Size","detail":"name size must be between 1 and 10"}]}

Removal of an existing field.

The removal of a field may break any system that relies on that field coming back.

Java
  // private Integer age;

Note: This doesn’t have to be a mandatory field
There may be downstream systems that rely on the presence of this field.
If you remove them, you may break them.

The Ignore Unknown Field Debate.

There is sometimes a debate on whether you should ignore unknown fields.
The basis of the objection is generally that a client can misspell a field by accident and not realise it. TBH I believe that should be pretty obvious in testing.

By defining your mapper to ignore unknown fields it means you can add optional fields as non-breaking changes. If you don’t, you can’t. The choice is that simple.

If you decide to ignore unknown fields you can define your mapper as so.

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

Versioning Spring Boot Microservices – Wrap-Up

If you change your services, you should change the version of your model.

Now you can easily update the version in your pom depending on the type of change, and make the new interface available in a model jar.

If you use the above steps in your application, you will be able to deal with most versioning situations with a minimum of fuss.

References

https://semver.org/

https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm

You Might Like

https://johndobie.com/blog/spring-boot-swagger/

https://johndobie.com/blog/feature-flags-in-java-spring/