Best practices of microservices systems modules design

Approaches for working with microservices

Microservices implement important modularity principles, leading to tangible benefits:

Yet, they come with their own set of drawbacks. It’s not very clear how to design modules in microservices architecture in a way that can enforce these benefits.

Modularity in software development can be boiled down into three guiding principles:

Design modules

Creating good modules requires the same design rigor as creating good microservices. A module should model (part of) a single bounded context of the domain. Choosing microservice boundaries is an architecturally significant decision with costly ramifications when done wrong. Module boundaries in a modular application are easier to change.

In many ways, modules in statically typed languages offer better constructs for well-defined interfaces. Calling a method through a typed interface exposed by another module is much more robust against changes than calling a REST endpoint on another microservice. REST+JSON is ubiquitous, but it is not the hallmark of well-typed interoperability in the absence of (compiler-checked) schemas. For example you can use Apache Avro to serialize/deserialize data and check data using the given schema from Kafka Schema Registry.

Many module systems allow you to express your dependencies on other modules. When these dependencies are violated, the module system will not allow it. Dependencies between microservices only materialize at run-time, leading to hard to debug systems.

Modules are natural units for code-ownership as well. Teams can be responsible for one or more modules in the system. The only thing shared with other teams is the public API of their modules.

Sharing within the modular application then happens through well-defined interfaces or messages between modules, not through a shared datastore. The big difference with microservices is that everything happens in-process. For modules you can choose eventual consistency or transaction use. For microservices, there is no choice: eventual consistency is a given and you need to adapt.

Microservice Chassis pattern

When you start the development of an application you often spend a significant amount of time putting in place the mechanisms to handle cross-cutting concerns. Examples of cross-cutting concern include:

You need to have such kind of features for mitigating this problems:

The major benefit of a microservice chassis is that you can quickly and easy get started with developing a microservice.

You need a microservice chassis for each programming language/framework that you want to use. This can be an obstacle to adopting a new programming language or framework.

Externalized configuration pattern

An application typically uses one or more infrastructure and 3rd party services. Examples of infrastructure services include: a Service registry, a message broker and a database server. Examples of 3rd party services include: payment processing, email and messaging, etc.

You need to have such kind of features for mitigating this problems:

Solution is pretty straightforward. Externalize all application configuration including the database credentials and network location. On startup, a service reads the configuration from an external source, e.g. OS environment variables, etc.

Using Gradle for creating modules

Let me describe Gradle in a few words:

Gradle multi-module project

Multi-project builds help with modularization. This allows a person to concentrate on one area of work in a larger project, while Gradle takes care of dependencies from other parts of the project.

Create a root project

The first step is to create a folder for the new project and add a Gradle Wrapper to the project. If you use the Build Init plugin, then the necessary settings and build scripts will also be added.

$ mkdir creating-multi-project-builds
$ cd creating-multi-project-builds
$ gradle init

Open the settings script. There will be a number of auto-generated comments which you can remove, leaving only:

settings.gradle

rootProject.name = 'creating-multi-project-builds'

In a multi-project you can use the top-level build script (also known as the root project) to configure as much commonality as possible, leaving sub-projects to customize only what is necessary for that subproject.

build.gradle

allprojects {
    repositories {
        jcenter() 1️⃣
    }
}

1️⃣ Add a Maven Repo for JCenter

Add a library sub-project

$ mkdir greeting-library

greeting-library/build.gradle

plugins {
    id 'java'
}

dependencies {
    compile 'dependency:dependency:1.0'
}

Add a liblary to the root project:

settings.gradle

include 'greeting-library'

Add a Java application sub-project

$ mkdir greeting-library

greeting-library/build.gradle

plugins {
    id 'java'        
    id 'application'
}

dependencies {
    compile project(':greeting-library') 1️⃣ 
}

1️⃣ Add greeting-library as a dependency for greeter

Add a library to the root project:

settings.gradle

include 'greeter'

See my Code example in (GitHub)[https://github.com/srcmaxim/microservices-modular-design].

Also checkout useful links for working with Gradle and modules:

How to use Gradle + BOM

Some projects require that all dependencies

pom.xml

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>io.srcmaxim</groupId>
  <artifactId>dependencies</artifactId>
  <version>1</version>
  
  <dependencies>
    <dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy</artifactId>
      <version>3.0.6</version>
    </dependency>
  </dependencies>
</project>

gradle.build

repositories {
    maven { url 'https://maven.pkg.github.com/<repo>/<org>' } 1️⃣
}

dependencies {
    compile platform('io.srcmaxim:dependencies:1') 2️⃣
    compile 'org.codehaus.groovy:groovy' 3️⃣ 
}

1️⃣ Add a Maven repository 2️⃣ Add .bom as a platform dependency 3️⃣ Use dependency without version. It will be provided from .bom

Alternatively, you can use Spring Dependency Management Plugin for Gradle dependency management.

Credits

1 Oreilly. Modules and microservices
2 Pattern: Microservice chassis
3 Pattern: Externalized configuration
4 Gradle. Creating Multi-project Builds
5 Code examples

Comments

comments powered by Disqus