Migration to Microservices Lessons Learned, Part I

Microservices architecture is overhyped nowadays. There are a lot of decent blog posts, articles, and presentations that explain what it is, the pros and cons, or just focus on implementation details. Some engineers advocate for a transition to microservices, while others advocate for a transition from a monolith…to a majestic monolith.

We’ll publish a 5 part series on microservice and each installment in the series I will share real practical decisions that if I could go back and redo, I would.
There will be a lot of Captain Obvious, but I hope that you will find something valuable to help you with your microservices journey and learn from our experiences.

Start With Just One Service, Not a Few in Parallel

Imagine that you have decided to start a project from scratch, and somehow, a decision was made that it should follow microservices architecture using one primary ecosystem, for instance, Java. So, what will the engineers do? They will create at least a few repositories, organize several different backlogs, and at the same time, they will start coding. That might lead to the following:

  • These services will have a distinct module structure.
  • These services will depend on various JAR versions, use various utility libraries for the same purpose, etc.
  • These services will follow different coding standards.
  • These services will have different approaches for testing.
  • And more.

At the end of the first or maybe second sprint, those teams will realize that they don’t have a collective agreement in place and will start arguing about all the items listed above. First PRs might block the whole process for a while.
Instead of starting with multiple services at a time, start the first couple of sprints developing functionality for multiple domains in the same service, in the same repository, but in different packages. All team members will be involved in code review and will align their common vision on package structures, coding standards, testing, libraries used, and approaches for standard functionality (data mapping, validation, CRUD operations, etc.). Then, a split into two services will look like a natural step to divide two bounded contexts into separate services. But at this stage, separate teams will have the same vision of what they are building and how.

Another important point at this stage is packaging. There are a few approaches to it:

  1. Separate packages for each kind of component
  2. Common packages for DTOs and other reusable stuff. Separate service packages for components of that service.
  3. Separate service packages for components of that service. Common stuff is partially duplicated on demand.

Since we already know that a split to microservices in inevitable, it makes sense to follow a packaging approach suitable for distributable deployables, i.e. option 3 in the list above. In this case, the best practices for packaging inside monolithic application can be violated deliberately.

And last but not least, a differentiation between service and library. The first thing we are trying to avoid is that a change in a library will require a redeployment of several services. From another point of view, not every piece of functionality should become an independent deployable due to additional overhead related to network, latency, infrastructure, monitoring, etc. A rule of thumb here is to try reusing services not code and follow common sense.

Our next blog post, we’ll discuss starting with services that bring business value, orchestration and data consistency.