Implementing Software Engineering Best Practices and Techniques with Apache Maven

0
129
10 min read

 

Apache Maven 3 Cookbook

Apache Maven 3 Cookbook

Over 50 recipes towards optimal Java Software Engineering with Maven 3

        Read more about this book      

(For more resources on this subject, see here.)

These techniques have been around for more than a decade and are well-known by practitioners of software engineering. The benefits, trade-offs, and pros and cons of these practices are well-known and will only need little mentioning.

These practices are not inter-dependent, but some of them are inter-related in the larger scheme of things. One such example would be the relation between project modularization and dependency management. While nothing stops either from being implemented in isolation, they are more beneficial when implemented together.

These techniques can be further supplemented by the industry’s best practices such as continuous integration, maintaining centralized repositories, source code integration, and so on.

Our focus here will be on steadily understanding these software engineering techniques within the context of Maven projects and we will look at practical ways to implement and integrate them.

Build automation

Build automation is the scripting of tasks that software developers have to do on a day-to-day basis. These tasks include:

  • Compilation of source code to binary code
  • Packaging of binary code
  • Running tests
  • Deployment to remote systems
  • Creation of documentation and release notes

Build automation offers a range of benefits including speeding up of builds, elimination of bad builds, standardization in teams and organizations, increased efficiency, and improvements in product quality. Today, it is considered as an absolute essential for software engineering practitioners.

Getting ready

You need to have a Maven project ready. If you don’t have one, run the following in the command line to create a simple Java project:

$ mvn archetype:generate -DgroupId=net.srirangan.packt.maven
-DartifactId=MySampleApp

How to do it…

The archetype:generate command would have generated a sample Apache Maven project for us. If we choose the maven-archetype-quickstart archetype from the list, our project structure would look similar to the following:

└───src
├───main
│ └───java
│ └───net
│ └───srirangan
│ └───packt
│ └───maven
└───test
└───java
└───net
└───srirangan
└───packt
└───maven

In every Apache Maven project, including the one we just generated, the build is pre-automated following the default build lifecycle. Follow the steps given next to validate the same:

  1. Start the command-line terminal and navigate to the root of the Maven project.
  2. Try running the following commands in serial order:

    $ mvn validate
    ...
    $ mvn compile
    ...
    $ mvn package
    ...
    $ mvn test
    ...

You just triggered some of the phases of the build life cycle by individual commands. Maven lets you automate the running of all the phases in the correct order. Just execute the following command, mvn install, and it will encapsulate much of the default build lifecycle including compiling, testing, packaging, and installing the artifact in the local repository.

How it works…

For every Apache Maven project, regardless of the packaging type, the default build lifecycle is applied and the build is automated. As we just witnessed, the default build lifecycle consists of phases that can be executed from the command-line terminal.

These phases are:

  • Validate: Validates that all project information is available and correct
  • Compile: Compiles the source code
  • Test: Runs unit tests within a suitable framework
  • Package: Packages the compiled code in its distribution format
  • Integration-test: Processes the package in the integration test environment
  • Verify: Runs checks to verify if the package is valid
  • Install: Installs the package in the local repository
  • Deploy: Installs the final package in a remote repository

Each of the build lifecycle phases is a Maven plugin. When you execute them for the first time, Apache Maven will download the plugin from the default online Maven Central Repository that can be found at http://repo1.maven.org/maven2 and will install it in your local Apache Maven repository.

This ensures that build automation is always set up in a consistent manner for everyone in the team, while the specifics and internals of the build are abstracted out.

Maven build automation also pushes for standardization among different projects within an organization, as the commands to execute build phases remain the same.

Project modularization

Considering that you’re building a large enterprise application, it will need to interact with a legacy database, work with existing services, provide a modern web and device capable user interface, and expose APIs for other applications to consume. It does make sense to split this rather large project into subprojects or modules.

Apache Maven provides impeccable support for such a project organization through Apache Maven Multi-modular projects. Multi-modular projects consist of a “Parent Project” which contains “Child Projects” or “Modules”. The parent project’s POM file contains references to all these sub-modules. Each module can be of a different type, with a different packaging value.

Implementing Software Engineering Best Practices and Techniques with Apache Maven

Getting ready

We begin by creating the parent project. Remember to set the value of packaging to pom, as highlighted in the following code:

<?xml version="1.0" encoding="UTF-8"?>
<project

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>

<groupId>net.srirangan.packt.maven</groupId>
<artifactId>TestModularApp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>MyLargeModularApp</name>

</project>

This is the base parent POM file for our project MyLargeModularApp. It doesn’t contain any sub-modules for now.

How to do it…

To create your first sub-module, start the command-line terminal, navigate to the parent POM directory, and run the following command:

$ mvn archetype:generate

This will display a list of archetypes for you to select. You can pick archetype number 101, maven-archetype-quickstart, which generates a basic Java project. The archetype:generate command also requires you to fill in the Apache Maven project co-ordinates including the project groupId, artifactId, package, and version.

After project generation, inspect the POM file of the original parent project. You will find the following block added:

<modules>
<module>moduleJar</module>
</modules>

The sub-module we created has been automatically added in the parent POM. It simply works—no intervention required!

We now create another sub-module, this time a Maven web application by running the following in the command line:

$ mvn archetype:generate -DarchetypeArtifactId=maven-archetype-
webapp

Let’s have another look at the parent POM file; we should see both the sub-modules included:

<modules>
<module>moduleJar</module>
<module>moduleWar</module>
</modules>

Our overall project structure should look like this:

MyLargeModularApp
├───MyModuleJar
│ └───src
│ ├───main
│ │ └───java
│ │ └───net
│ │ └───srirangan
│ │ └───packt
│ │ └───maven
│ └───test
│ └───java
│ └───net
│ └───srirangan
│ └───packt
│ └───maven
└───MyModuleWar
└───src
└───main
├───resources
└───webapp
└───WEB-INF

How it works…

Compiling and installing both sub-modules (in the correct order in case sub-modules are interdependent) is essential. It can be done in the command line by navigating to the parent POM folder and running the following command:

$ mvn clean install

Thus, executing build phase on the parent project automatically gets executed for all its child projects in the correct order.

You should get an output similar to:

------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] MyLargeModularApp ........................ SUCCESS [0.439s][INFO] MyModuleJar .............................. SUCCESS [3.047s][INFO] MyModuleWar Maven Webapp ................. SUCCESS [0.947s]------------------------------------------------------------------
[INFO] BUILD SUCCESS
------------------------------------------------------------------

Dependency management

Dependency management can be universally acknowledged as one of the best features of Apache Maven. In Multi-modular projects, where dependencies can run into tens or even hundreds, Apache Maven excels in allowing you to retain a high degree of control and stability.

Apache Maven dependencies are transient, which means Maven will automatically discover artifacts that your dependencies require. This feature has been available since Maven 2, and it especially comes in handy for many of the open source project dependencies we have in today’s enterprise projects.

Getting ready

Maven dependencies have six possible scopes:

  • Compile: This is the default scope. Compile dependencies are available in the classpaths.
  • Provided: This scope assumes that the JDK or the environment provides dependencies at runtime.
  • Runtime: Dependencies that are required at runtime and are specified in the runtime classpaths.
  • Test: Dependencies required for test compilation and execution.
  • System: Dependency is always available, but the JAR is provided nonetheless.
  • Import: Imports dependencies specified in POM included via the <dependencyManagement/> element.

How to do it…

Dependencies for Apache Maven projects are described in project POM files. While we take a closer look at these in the How it works… section of this recipe, here we will explore the Apache Maven dependency plugin.

According to http://maven.apache.org/plugins/maven-dependency-plugin/:

“The dependency plugin provides the capability to manipulate artifacts. It can copy and/or unpack artifacts from local or remote repositories to a specified location.”

It’s a decent little plugin and provides us with a number of very useful goals. They are as follows:

$ mvn dependency:analyze
Analyzes dependencies (used, unused, declared, undeclared)

$ mvn dependency:analyze-duplicate
Determines duplicate dependencies

$ mvn dependency:resolve
Resolves all dependencies

$ mvn dependency:resolve-plugin
Resolves all plugins

$ mvn dependency:tree
Displays dependency trees

How it works…

Most Apache Maven projects have dependencies to other artifacts (that is, other projects, libraries, and tools). Management of dependencies and their seamless integration is one of Apache Maven’s strongest features. These dependencies for a Maven project are specified in the project’s POM file.

<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<scope>...</scope>
</dependency>
</dependencies>

In Multi-modular projects, dependencies can be defined in the parent POM files and can be subsequently inherited by child POM files as and when required. Having a single source for all dependency definitions makes dependency versioning simpler, thus keeping large projects’ dependencies organized and manageable over time.

The following is an example to show a Multi-modular project having a MySQL dependency. The parent POM would contain the complete definition of the dependency:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.2</version>
</dependency>
<dependencies>
</dependencyManagement>

All child modules that require MySQL would only include a stub dependency definition:

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

There will be no version conflicts between multiple child modules having the same dependencies.

The dependencies scope and type are defaulted to compile and JAR. However, they can be overridden as required:

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>war</type>
</dependency>

There’s more…

System dependencies are not looked for in the repository. For them, we need to specify the path to the JAR:

<dependencies>
<dependency>
<groupId>sun.jdk</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>

However, avoiding the use of system dependencies is strongly recommended because it kills the whole purpose of Apache Maven dependency management in the first place.

Ideally, a developer should be able to clone code out of the SCM and run Apache Maven commands. After that, it should be the responsibility of Apache Maven to take care of including all dependencies.

System dependencies would force the developer to take extra steps and that dilutes the effectiveness of Apache Maven in your team environment.

LEAVE A REPLY

Please enter your comment!
Please enter your name here