Skip to content

Latest commit

 

History

History
359 lines (279 loc) · 15.3 KB

MavenIntroduction.md

File metadata and controls

359 lines (279 loc) · 15.3 KB

Apache Maven

Introduction

Apache Maven is a build automation tool. It is used to manage the dependencies and build configurations for software projects and to automatically build them. If you are familiar with the C family of languages, Maven can be thought of as an alternative to Make for Java projects (as well as for other languages like Scala or C#, for example) that is better adapted to the specificities of the language.

An excellent introduction to Maven can be found on the project's official website. It is advised that you read this introduction to familiarise yourselves with the technology, as you will use it throughout the entire semester for your own projects.

An autopsy of the microservices Project Object Model

A central aspect of Maven is the notion of Project Object Model (POM). The POM is an xml file (named pom.xml) that contains all of the information necessary for Maven to produce the artifacts for a project: it's a configuration file that indicates to Maven what plugins and packages it needs to download to build the project, as well as the commands it must execute to build its targets.

The Super POM

If a project is subdivided into separate modules (like this is the case in the microservices example, where each microservice is defined as a module of the project), it can contain multiple pom.xml files. The POM at the root of the project is then called the Super POM, and each module contains its own pom.xml file. The Super POM lists all the modules that compose the application. All of the POMs in a project's modules inherit from the configuration in the Super POM, meaning that any dependency listed in it becomes a shared dependency for all of the project's modules.

Let us now have a look at the elements in the microservices Super POM.

The <project> and <modelVersion> tags

The first elements in a pom.xml are the project and modelVersion tags:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

The project tag indicates to Maven that the file is a POM. It must always be at the top-level of any pom.xml. The modelVersion tag indicates to Maven what version of the object model the POM is using. It should be set to 4.0.0 for the current version of Maven.

Minimal requirement for POMs

After the project and modelVersion tags, we have:

<groupId>ch.unige</groupId>
<artifactId>pinfo-micro-services</artifactId>
<version>0.4.0-SNAPSHOT</version>
<name>Parent Pom of the Pinfo Micro Services</name>

These tags are the minimum requirement for a pom.xml to be valid. Their roles are the following:

  • groupId indicates the name of the organisation or company that created the project represented by the POM;
  • artifactId is the unique identifier of the artifact generated by Maven for the project (here, it is the name of the microservices project);
  • version is the version number of the artifact produced by Maven with the POM;
  • name is the name used by Maven for the project, for example in documentation files (it is also known as its display name).

Packaging

The tag:

<packaging>pom</packaging>

indicates to Maven how the project's artifact must be packaged (for example in a JAR, a WAR, etc.). Here, the pom option means that the current POM is a Super POM, and that the project's artifact is actually a set of artifacts, one for each of the project's modules (i.e. each microservice). We will see that the pom.xml files in each of the microservices indicate in turn to Maven how the modules they describe must be packaged specifically.

SCM plugin

The scm tag that follows is simply a plugin for Source Control Management. We won't look further into it here.

Properties

The properties tag is used to define POM properties:

<properties>
	<version.thorntail>2.6.0.Final</version.thorntail>
	<java-ee-api.version>8.0.1</java-ee-api.version>
	<postgresql.jdbc.driver.version>42.2.5</postgresql.jdbc.driver.version>
	<hibernate-entitymanager.version>5.0.10.Final</hibernate-entitymanager.version>
	<version.h2>1.4.200</version.h2>
	<version.junit>5.6.0</version.junit>
	<mockito.version>3.3.0</mockito.version>
	<failOnMissingWebXml>false</failOnMissingWebXml>
	<maven.compiler.source>1.8</maven.compiler.source>
	<maven.compiler.target>1.8</maven.compiler.target>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<skip-docker-build>true</skip-docker-build>
	<skip-docker-compose>false</skip-docker-compose>
	<dockerHost>tcp://localhost:2375</dockerHost>
</properties>

Properties in Maven can be thought of as special variables that can be accessed from anywhere in the POM with the syntax ${X}, where X is the name of the property to access. When a a reference to a property is made in the POM, it is replaced with the actual value defined for it in the properties element.
For example, in the microservices Super POM, the ${skip-docker-build} property is used in one of the build phases. When Maven sees it, it replaces it with the value true, as defined in the properties listed above.

In addition to the properties explicitly defined in the POM, special properties can also be used. For example, environment variables can be accessed from inside the POM by prefixing them with env. (e.g. ${env.JAVA_HOME} will return the path to your Java Home, as defined in your system's environment variables). See this page for more information.

Modules

The modules element indicates to Maven the list of modules that compose a project.

In the microservices example, we have:

<modules>
	<module>counterparty-service</module>
	<module>instrument-service</module>
	<module>valuation-service</module>
	<module>regulatory-service</module>
	<module>api-gateway</module>
</modules>

Each module element in the list refers to one of the modules (microservices) of the project. Modules are identified by the relative path to their directories or their POM. By convention, the artifact id of a module is used as the name of the directory that contains it.

When the pom packaging option is used, like in the microservices, Maven searches for modules in the modules element, it follows the relative paths it finds, and it builds the modules with their POMs.

Dependencies

The dependencies needed by a project to compile can be listed in the dependencies element of a POM with the dependency tag.

For example, in the microservices Super POM, the following dependencies are listed:

<dependencies>
	<dependency>
		<groupId>org.junit.jupiter</groupId>
		<artifactId>junit-jupiter-api</artifactId>
	</dependency>
	<dependency>
		<groupId>org.junit.jupiter</groupId>
		<artifactId>junit-jupiter-engine</artifactId>
	</dependency>
	<dependency>
		<groupId>org.junit.platform</groupId>
		<artifactId>junit-platform-launcher</artifactId>
	</dependency>
	<dependency>
		<groupId>org.mockito</groupId>
		<artifactId>mockito-core</artifactId>
	</dependency>
	<dependency>
		<groupId>org.mockito</groupId>
		<artifactId>mockito-junit-jupiter</artifactId>
	</dependency>
	<dependency>
		<groupId>javax.xml.bind</groupId>
		<artifactId>jaxb-api</artifactId>
	</dependency>
</dependencies>

Because they are defined in the Super POM, these dependencies are inherited by each module of the project. When the project is built, the dependencies are resolved (i.e. downloaded) before the modules are compiled.

Dependency Management

When a dependency is added to a project, it is possible that it requires itself some other dependencies that weren't listed in the project's POM. These are called transitive dependencies. Maven is able to automatically detect and resolve such dependencies.

Sometimes, a project's author might want to explicitly tell Maven which version of an artifact to use for a transitive dependency, or for an explicit dependency where no version was specified. This sort of information can be defined in the dependencyManagement element of a POM.

In the microservices example, we have:

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>io.thorntail</groupId>
			<artifactId>bom</artifactId>
			<version>${version.thorntail}</version>
			<scope>import</scope>
			<type>pom</type>
		</dependency>
		<dependency>
			<groupId>com.github.dadrus.jpa-unit</groupId>
			<artifactId>jpa-unit-bom</artifactId>
			<version>0.5.0</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		...
</dependencyManagement>

This tells Maven to use specific versions of the dependencies needed by the project, and it provides it with additional information, such as the scope or type of the dependencies. See this page for more information about dependency management.

Build lifecycle

In Maven, the process to build and distribute the artifacts of a project is clearly defined throught the concept of a build lifecycle. A build lifecycle defines the different phases that must be executed by Maven to compile a project and to deploy its artifacts.

Build phases represent stages in a lifecyle, such as the validation of a project (check that it is correct and that all of the necessary information is available), its compilation, the execution of its tests, etc.

Each phase in a lifecycle is divided into goals. Goals define how a phase is executed, hence they are the mechanism to use to customize a build phase. This is done by declaring the plugin goals for some specific phase.

For example, in the build element of the microservices Super POM, we have:

<plugins>
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-compiler-plugin</artifactId>
	</plugin>
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-surefire-plugin</artifactId>
		<configuration>
			<skipTests>false</skipTests>
		</configuration>
	</plugin>
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-failsafe-plugin</artifactId>
		<executions>
			<execution>
				<goals>
					<goal>integration-test</goal>
					<goal>verify</goal>
				</goals>
			</execution>
		</executions>
	</plugin>
	<plugin>
		<groupId>io.thorntail</groupId>
		<artifactId>thorntail-maven-plugin</artifactId>
		<executions>
			<execution>
				<goals>
					<goal>package</goal>
				</goals>
			</execution>
		</executions>
	</plugin>
</plugins>

The plugin goals defined above indicate to Maven how to complete different build phases of the lifecycle. For example:

<plugin>
	<groupId>io.thorntail</groupId>
	<artifactId>thorntail-maven-plugin</artifactId>
	<executions>
		<execution>
			<goals>
				<goal>package</goal>
			</goals>
		</execution>
	</executions>
</plugin>

tells Maven to comlete the package goal with the thorntail-maven-plugin plugin.

See this page for more information on Maven's build lifecycle.

Build Profiles

Maven build profiles are special customised build configurations that can be used to target different deployment environments, amongst other things.

In the microservices example, profiles are used to define a special build target that creates Docker images for the microservices of the project with the fabric8-maven-plugin:

<profiles>
    ...
    <profile>
		<id>package-docker-image</id>
		<activation>
			<property>
				<name>docker-build</name>
			</property>
		</activation>
		<build>
			<plugins>
				<plugin>
					<groupId>io.fabric8</groupId>
					<artifactId>fabric8-maven-plugin</artifactId>
					<configuration>
						<skip>${skip-docker-build}</skip>
						<useColor>true</useColor>
						<dockerHost>${dockerHost}</dockerHost>
						<images>
							<image>
								<name>%g/%a:%l</name>
								<run>
									<ports>
										<port>8080:8080</port>
									</ports>
								</run>
								<build>
									<from>java:openjdk-8-jdk</from>
									<ports>
										<port>8080</port>
									</ports>
									...
								</build>
							</image>
						</images>
					</configuration>
					<executions>
						<execution>
							<phase>package</phase>
							<goals>
								<goal>build</goal>
							</goals>
						</execution>
					</executions>
				</plugin>
			</plugins>
		</build>
	</profile>
</profiles>

Fabric8 is a maven plugin that can be used to bundle Java applications into Docker images. Here, the plugin is used for the build goal of the package build phase in the Maven build lifecycle. The plugin creates Docker images for the project's modules after building them.

More information about build profiles can be found here.
To learn more about the Fabric8 Maven plugin, go here.

Module POMs

In addition to the Super POM, each microservice in the project defines its own pom.xml.

Let us have a look at the counterparty-service's POM.

The parent element

The parent element in a POM defines the parent POM of a module in a project with a Super POM.

In the counterparty-service example, we have:

<parent>
	<groupId>ch.unige</groupId>
	<artifactId>pinfo-micro-services</artifactId>
	<version>0.4.0-SNAPSHOT</version>
</parent>

This element indicates to Maven that the module's parent is the pinfo-micro-services project, which is configured in the Super POM we analysed earlier. It also specifies which version of the parent must be used in the module.

When a module defines a parent, it inherits from its dependencies and properties.

Custom elements

In addition to the elements inherited from the parent POM, a module can define its own custom elements. For example, in the counterparty-service we have:

<properties>
	<skip-docker-build>false</skip-docker-build>
</properties>

which overrides the value defined in the parent POM.

Packaging

Note how the packaging in the counterparty-service is set to war. This means that the module will be packaged as a WAR archive (a special kind file to distribute a collection of JARs that compose a web application) by Maven.

Calling Maven on a Project

In order to build, test and/or deploy a project containing a POM, Maven must be called on it. This can be done in a shell by calling the mvn command at the root of the project, where the POM resides. The mvn command takes a build lifecycle, phase or goal as parameter to tell it what to do with the project. For example, mvn clean cleans the files and directories generated by Maven during a build, while mvn install resolves the dependencies needed by a project and compiles it.

When you define profiles in a POM, you can also pass them as argument to Maven with the -P option. In the microservices example, the command mvn install -Ppackage-docker-image runs the install lifecycle of Maven on the package-docker-image profile.