With a large number of microservices in a project, we have to face the fact that in some of them, we have to duplicate the same code (utility classes, configurations, etc.), and have found a bug in one place, we have to search and fix it everywhere. At the same time, if microservices are supported by different developers, each of them will fix the bug in their own way, and it will be more difficult to bring everything to uniformity in the future. Let's consider several approaches to code sharing in microservices, by example.
Let's imagine that we have a pet clinic website where you can register, choose a doctor, and make an appointment. The project may contain many microservices, but we will take the following:
In our example, microservices can be divided into several categories:
What if we want all these microservices to follow some internal standards? For example, those that provide API methods for communication should have auto-documentation describing all methods via OpenAPI, and logging in all microservices should be configured so that all logs are sent to Splunk. In this case, we will have to duplicate configurations and some utility classes to handle dates, prices, and other things in each microservice. In order to avoid this, we will need our own library where we will store all the shared code. Let's consider several possible implementations of this library.
We can create a new maven project in which we add all dependencies that can be used in microservices. In this case, each microservice will have a large number of unused dependencies, which will increase the size of the final jar file. In addition, we will have to be strict about not creating new beans unnecessarily (why do we need a database bean if we don't need it in a microservice?).
We can split our library into many projects. In this case, we will get rid of the problems from the previous example, but we will encounter new ones. If we change several libraries at once, we will have to make many pull requests and publish many versions of these libraries.
In this case, all changes are in one pull request; there are no unnecessary dependencies and beans in microservices, and you can publish separate versions of subprojects as well as release a single version. Let's take a closer look at how to implement such a library using Maven.
To give you an example, let's define the submodules we might need and what will be in them:
Let's start with the base pom file. We will use the pom file from the previous article as the parent to ensure that dependencies in submodules have the same versions as in all microservices.
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>base-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.example.common</groupId>
<artifactId>common-library-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>common-library-example-parent</name>
<description>Demo project</description>
<modules>
<module>core</module>
<module>utils</module>
<module>database</module>
<module>logging</module>
<module>client</module>
<module>api</module>
</modules>
</project>
Each submodule needs to have its own pom file added. In it, you can add all dependencies required in a particular submodule, including dependencies on other submodules. For example, let's take a pom file from the client submodule, which requires a dependency on feign (I wrote more about how to use it in my article) and exceptions from the core submodule. So the pom file will look like this:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>common-library-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>client</artifactId>
<version>${project.parent.version}</version>
<name>client</name>
<description>Demo project</description>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-spring4</artifactId>
</dependency>
</dependencies>
</project>
In a submodule, we can add classes to extend the feign client capabilities. For example, its implementation of logging requests/responses from API and much more.
The final structure of the project will be as follows:
In order to use this library, as with parent pom, it must first be published to a local repository (or another repository such as Nexus) with the mvn install
command. You will see the following:
Next, you can add the necessary submodules to the microservices. Let's add the client submodule to our gateway microservice in order to add user-service integration to it easily:
<properties>
<common-lib.version>0.0.1-SNAPSHOT</common-lib.version>
</properties>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>client</artifactId>
<version>${common-lib.version}</version>
</dependency>
</dependencies>
Of course, the number of submodules can be any number. It all depends on what is required in your project.