tags:

From Monolithic to Microservice in practice

From Monolithic to Microservice in practice

This blog will help you to migrate your monolithic application to microservices (see Martin Fowler – Microservices) in small steps.

The end goal is to have each service totally decoupled from any other service. This means that the ideal is for each service to have it’s own code base, and it’s own database.

Obviously those of us that are coming from monolithic apps, do not see how this is feasible. So this post will help you create a road map to slowly redesign your current app going forward towards a microservice architecture.

Technology

The assumption for this blog is that your application is written in java, and that you are using the spring framework - preferably spring boot.

The Problem

Since we obviously cannot stop all work and split the application up (it would take too much time with no immediate visible value), we need to do this in a lot of small steps. We would like to feel comfortable with each step before moving on to the next. So how do we do this?

Of course the issues with common code and microservices are great:

  • You should not share the database

  • You should not share the code.

  • All interactions between services should be via well defined API’s.

Solution with Spring Profiles

Spring profiles gives us the leverage to assign each service in our application with a profile. Then we can run the same codebase, but under different profiles. This in essence gives us the option to run the same codebase as if it were different applications.

Spring has simple profiles annotation that can be added to any bean (http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html), but we need something more customizable since some of the beans will be run in more than one application. For this spring gives us conditions.

First you should create Conditions for each application, for example:

  public class WebProfileCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String[] activeProfiles = context.getEnvironment().getActiveProfiles();
        ArrayList<String> list = Lists.newArrayList(activeProfiles);
        return list.contains("web") || list.contains("test");
    }
  }

If you need the bean to be for more than one application, you can just change the condition. Since you write the code, you can activate a profile by using the system parameters or any other code for example system variables or anything else.

Then for each class you need to add the conditional annotation, for example:

  @Controller
  @RequestMapping("/admin/api/device")
  @Conditional(WebProfileCondition.class)
  public class AdminDeviceController

Once you have done this step, you can now test your new microservice application. You can now run the same code base on different servers and run each one with the appropriate profile. You now have different servers but with the same code base.

To run your spring boot application use the following flag to activate specific profiles:

    -Dspring.profiles.active="staging,analytics"

(notice that profiles can be used not only for different applications, but for different scenarios of the same application [dev,staging,production]).

Application.properties

Since we now will run the same code base as different applications, we need a way to have different property files per runtime application.

We might need a different property file per application. The way spring implements this is by a naming convention with the property file. For example I want to have the following property files:

  • application.properties

  • application-staging.properties

  • application-production.properties

  • application-web.properties

Take note that you can only specify one profile and not more for each file. For more information see: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Why is this not enough?

The main reason that we would like microservices is so that we can react quickly to changing requirements. Any change in our code, we will need to run all tests for all profiles.

Sometimes we will need code that will affect only one service and we don’t want it to effect another service.

We would like to have different release cycles for each server and not have any dependencies between them.

We even might want to rewrite one service. For any of this to happen we need to have 2 separate code bases. On the other hand we have so much code that needs to be in more than one service, and we don’t want to duplicate everything. So what we want to do is create in the same application code area’s for each application, and an area for all the common code that will be used in more than one application.

Package names

Step one to code separation is to have a separation on the package level. For example after a simple refactor I will have the following packages:

  • com.mycomp.web

  • com.mycomp.analytics

  • com.mycomp.batch

  • com.mycomp.common

This step is fairly simple since any modern IDE knows how to rename a class’s package and update any calling code. If you have a class that needs to be in two packages, then obviously you need to put it in the common package.

Common code

As you can guess already from the previous paragraph what will happen is that you have a lot of code in the common section. You need to try to minimize this, since in the next step we will move each package into a separate application. So before moving code to the common think should it really be there, or can you refactor the class so that it will be separated to each appropriate package.

Code split

We now come to the main step towards real microservices. I assume that you have some CI tool for building your application’s, like jenkins. First step will be to remove all the common code to a separate code base with it’s own life cycle.

You need to create a new job in your jenkins to compile the common code into a new jar. This jar then needs to be added to your main project (a dependency if you are using maven or gradle).

Now you need to do the same step but with each package. In the end you should have three separate application code bases. One for web, analytics and batch.

Once you have separated the code, you can of course remove all the conditions that we have added earlier to all the beans, since we don’t need the spring conditions anymore (unless you are using them for setup separation - dev,staging, production).

Common Code

So what is so bad with common code? As you might have seen while doing the previous steps that you tend to move most of your code to the common area (since we have been taught to reuse code). Once you go down this path, you don’t have the ability deploy each application separately since any change in the common mandates that you recompile all applications.

The only code that should be left in the common is code that has nothing to do with the application business logic. Generic code that could have come from the internet but you coded yourself should be put in the common. All other code needs to be moved into each application.

Database

The most difficult issue that we will not fully address here is the database. When starting from an monolithic application, we usually have one database. Even after the separation that we did here we still have a tendency to keep the single database. The reason is that the code is already written, or that we will need to rethink our architecture.

If we do not decouple the database then we cannot fully decouple the application life cycle. Any change in the scheme will force us to update all applications.

I will just give a hint as to the solution. Once we have separated our monolithic application into new services we start to understand that each service has a specific domain and that it should have it’s own database by design. There are of course scenarios that one service needs information from another service. The proper solution is for one server to call another and not to go directly to the other services database.

Summary

Moving from a monolithic app to a microservices architecture can take some time to do. But as we have outlined this process is not all or nothing. You can do this process in the background as part of your sprints with other features.

So when you boss tells you that the change is too big and takes too long, send him to my blog.