Skip to content

Instantly share code, notes, and snippets.

@npiper
Last active February 14, 2024 13:23
Show Gist options
  • Save npiper/f7547716ab401db5ecf00b908922820e to your computer and use it in GitHub Desktop.
Save npiper/f7547716ab401db5ecf00b908922820e to your computer and use it in GitHub Desktop.
Right way to do versioning in maven for Microservices

A SemVer Microservice CI-CD Pipeline

Preferred:

X.Y.Z_${CI-buildNo}.${gitcommit}

Ensure our POM accepts versions set externally.

for Now Maven: X.Y.Z , Tag X.Y.Z__${CI-buildNo}.${gitcommit} , Docker label ${CI-buildNo}.${gitcommit}

Principles

Thanks to Axel Fontaine & Jez Humble

  • Keep as much knowledge as possible inside the POM
  • Every build is a potential release, so I want to use a maven release version (i.e. not a snapshot version) for each build
  • The CI machine (the one and only machine you should ever build releases on)
  • Ideal traceability:
    • Git commit
    • Build Number from CI server

Multi-Module projects?

A multi-module build should have all modules use the version of the parent POM, which itself is unique for every build

We need a sane default for local development to avoid having to pass a dummy version for every single local build.

    <properties>
        <!-- Sane default when no revision property is passed in from the commandline -->
        <revision>0-SNAPSHOT</revision>
    </properties>

Semantic Versioning

For services and deliverables consumed by other teams and external parties you can also easily combine this technique with semantic versioning by prefixing the version tag in your POM with the correct semantic version.

You can then automatically produce releases internally and manually updated this semantic version before each external delivery.

<version>X.Y.Z+${revision}</version>

<properties>
        <!-- Sane default when no revision property is passed in from the commandline -->
        <revision>0-SNAPSHOT</revision>
</properties>

<plugin>
    <artifactId>maven-scm-plugin</artifactId>
    <version>1.9.4</version>
    <configuration>
       <tag>${project.artifactId}-${project.version}</tag>
    </configuration>
</plugin>

Pre-Requisites

  • Use Git
  • Use a CI Server for build Releases & Deployment
  • Maven 3.2.1+ ( Continuous Delivery friendly versions. )
  • Ideally Docker so you get Versioned containers
  • Wagon S3: Create an S3 Repo and only one Role/User with that access
  • A Release Repository ( I use Wagon S3, could be Artifactory, Nexus etc;)
  • Plugins
    • maven-scm-plugin
    • git-commit-id-plugin

But... then we found a problem with Maven inheritance in Maven 3.2

In Maven 3.2 - it was not allowed to give a Parent pom as a version range, this was thankfully fixed in Maven 3.5.

We would like this as it's much easier to improve the POM's in the hierarchy and automatically roll them in to the next build and reduces changing this through the hierarchy.

MNG-2199 Support version ranges in parent elements

Before 3.5

	<artifactId>npiper-rest-reference</artifactId>
	<version>0.0.3</version>
        <packaging>jar</packaging>
	<name>npiper-rest-reference</name>
	<parent>
		<groupId>neilpiper.me</groupId>
		<artifactId>rest.microservice.base</artifactId>
		<version>>0.1.0_22.2013488</version>
	</parent>

With Maven 3.5.. we can do parent range inheritance

This works.. Semantic version of the POM artifact and a parent version range.

	<artifactId>npiper-rest-reference</artifactId>
	<version>0.0.3</version>
        <packaging>jar</packaging>
	<name>npiper-rest-reference</name>
	<parent>
		<groupId>neilpiper.me</groupId>
		<artifactId>rest.microservice.base</artifactId>
    <!-- We can now do version ranges -->
		<version>(,1.0]</version>
	</parent>

However.. we still can't quite use the version stamp we want yet

But, .. when using this maven is now strict on only using a semantic version. Which is where we are now, not quite exactly what we are after but a workable compromise.

	<artifactId>npiper-rest-reference</artifactId>
	<version>0.0.3+${revision}</version>
        <packaging>jar</packaging>
	<name>npiper-rest-reference</name>
	<parent>
		<groupId>neilpiper.me</groupId>
		<artifactId>rest.microservice.base</artifactId>
		<version>(,1.0]</version>
	</parent>
BUILD FAILURE:
'version' contains an expression but should be a constant. @ line 8, column 11
[FATAL] Version must be a constant

The Current Approach SemVer the Jar, Tag the repo and Docker image with our build revision

So now.. we have good parent inheritance

On a build

SemVer: 1.0.0 Build; 37 Short git commit: 7806fad

Maven Jar: 1.0.0
Git Tag:  1.0.0_37.7806fad
Docker label: 37.7806fad
github.pages - 'Description' gives the metadata of what the master build is currently at.

Deploying via the CI Server

# If you need to update the semantic version
# The build server must provide the Revision attribute

mvn deploy scm:tag -Drevision=$BUILD_NUMBER_$GIT_SHORT_COMMIT

Travis-ci

Travis-CI is an awesome free server, lots of features - however it won't allow you to import SSH keys in the free version.

This means an element of Jiggery pokery to do the setup:

Need to encrypt and set as environment variables:

  • AWS Access (For our S3 bucket)
  • Git User
  • Git Auth token

I also found the scm:tag maven command not respecting a server.xml values so as a workaround put in -D settings for environment variables.

The deploy command line then looks like this.

mvn clean install deploy scm:tag -Drevision=$(git rev-parse --short HEAD) -Dusername=${GIT_USER_NAME} -Dpassword=${
GITPW}

Repositories

  • RELEASE

We reduce the need for the SNAPSHOT... because every succesful deploy that has been screened through our pipeline is a prod candidate.

Thoughts.. STAGE vs. MASTER?

Semantic versioning rules

https://semver.org/

MAJOR version when you make incompatible API changes, MINOR version when you add functionality in a backwards-compatible manner, and PATCH version when you make backwards-compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

Software using Semantic Versioning MUST declare a public API

Once a versioned package has been released, the contents of that version MUST NOT be modified. Any modifications MUST be released as a new version.

Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.

A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version.

Build metadata MAY be denoted by appending a plus sign and a series of dot separated identifiers immediately following the patch or pre-release version

Workflow?


----o-------- MASTER ------o------------o
     \                   /             /
      \-o-o-o-o-o--DEVELOPMENT---o-o--o
            \                   /
             o---FEATURE-------o

Ideal Branching approach

Branches

  • master (Prod)
  • develop (Trunk)
    • Feature (Short lived)

Short lived Feature branches for expirements.

CI hooked up to develop and merge activities to master, hotfixes off master only if required.

Major versions could create separate branches, however it is almost worth starting a new Repo as your earlier versions should become maintenance only.

I've got a development candidate - Reflecting production got the release

Merge the development commit id into master - it should be very straightforward.

git merge <commit-id>
git push

Copy the candidate from STAGING into 'RELEASES' repo.

Feature Branches (Should be Short lived)

Feature branch off 'develop'

https://docs.gitlab.com/ee/workflow/workflow.html

OPTIONS?

Maven Release Plugin

Errrm... No

Maven gitflow plugin

Useful, maybe in a very large project and multiple teams.. but perhaps not for microservices

Normal trunk builds

Append the git commit to a SNAPSHOT build Should only do SNAPSHOT's on Green Unit test builds

X.Y.Z+${short gitcommitid}.{CI buildNo}

Useful Info

Git commit hash as build meta-data for the built artifact.

Docker

ARG GIT_COMMIT=unspecified
LABEL git_commit=$GIT_COMMIT

How to tag Docker images with Git Commit info

https://blog.scottlowe.org/2017/11/08/how-tag-docker-images-git-commit-information/

docker build -t flask-local-build --build-arg GIT_COMMIT=$(git log -1 --format=%h) .

References

https://opencredo.com/versioning-a-microservice-system-with-git/

https://github.com/sidohaakma/semver-maven-plugin

https://blog.philipphauer.de/version-numbers-continuous-delivery-maven-docker/

https://semver.org/

https://github.com/fabric8io/docker-maven-plugin

https://www.e-gineering.com/2016/02/02/gitflow-maven-and-ci-done-right-part-1-teaching-maven-new-tricks/

https://trunkbaseddevelopment.com/

https://github.com/ktoso/maven-git-commit-id-plugin

https://axelfontaine.com/blog/dead-burried.html

https://axelfontaine.com/blog/maven-releases-steroids.html

https://axelfontaine.com/blog/final-nail.html

http://www.openpersuasion.org/continuous-delivery-of-microservices/

https://asardana.com/2017/11/04/microservices-database-management-using-liquibase/

(Jenkins and Branching - pipeline) https://stackoverflow.com/questions/39161285/maven-release-plugin-use-in-jenkins-pipeline

https://medium.com/production-ready/implementing-semantic-monitoring-534f16e18247

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment