paint-brush
Code Sharing in Microservice Architecture: Creating Your Own Common Libraryby@gromspys
3,403 reads
3,403 reads

Code Sharing in Microservice Architecture: Creating Your Own Common Library

by Sergei KorneevNovember 1st, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Explore effective methods for code sharing in microservices using comprehensive examples and multi-module libraries for streamlined development.
featured image - Code Sharing in Microservice Architecture: Creating Your Own Common Library
Sergei Korneev HackerNoon profile picture

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:

  • user-service - responsible for authorization, registration, and work with users (both doctors and pet owners).
  • notification-service - responsible for email distribution (it can be news, appointment reminders, etc.).
  • gateway - a microservice that will aggregate responses from internal microservices (such as user-service) and prepare a response for the frontend.


Categories of microservices

In our example, microservices can be divided into several categories:

  • Microservices that use a database and do not provide any API methods. In the list above, this is a notification service that does scheduled mailings or uses some message broker (like Kafka or rabbitMQ).
  • Microservices that do not use the database but communicate only with third-party APIs and provide their API methods for interaction. In our example, this is a gateway that only processes responses from other microservices.
  • Microservices that use both the database and provide their own API methods for operation (like user-service).


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.


One library for all microservices

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?).


Many different shared libraries

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.


One common multi-module library

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:

  • core - in it, we will store something basic for all microservices. For example, some of our basic exceptions are PlatformException or ApplicationException. We can also store there some basic data structures like Pair, Timer, and all similar things that can be used in any of our microservices regardless of their purpose.
  • utils - in this module, we will store general-purpose utility classes. For example, classes for working with dates, prices, JSON, etc.
  • database submodule will contain some useful things for working with the database. It can be custom serializers / deserializers of database fields and many other things. In our example, this submodule can be used by user-service and notification-service.
  • logging - in this, we will be able to describe the configuration to send all our logs to Splunk.
  • client - in this submodule, we can store everything that may be needed in microservices that interact with other APIs. It can be used as a gateway.
  • api - here, we will store everything for services that will provide API methods. For example, configuration for autogeneration of documentation and the like. It can be used by user-service and gateway.
  • Any other submodules you may need.


Creating a multi-module project

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:

The final structure of the project


Adding a multi-module library to microservices

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.


Useful links