Introduction

Splitting your application into multiple microservices is the norm in modern day software architecture and development. We do this to work around some issues related to monoliths: big complex deployments, unfinished features blocking other features etc. Practices and technologies like Containerization, Cloud and Service Discovery help to implement this model more easily these days.

However, if you don’t create these services correctly, you’ll end up with a bunch of development and operational troubles and not reaping the benefits from having multiple services. In reality, you’ll slow down development and make things harder to change and release instead of the other way around.

In this are three anti-patterns that I’ve observed multiple times in the wild, the symptoms they bring and some tips on how to avoid them.

Anti-pattern 1: Hard coupling to a shared service or a service outside your control

You base your data model and actions on the data model and actions of the service that you use. You use these throughout your application instead of creating your own business entities and corresponding actions. This data model and actions are often generated from the service’s WDSL or JSON API specification (like Swagger), saving time on creating and updating this manually.

One reason to do this is to avoid creating seemingly duplicate code and wasting time on writing code to map the service’s data model to your own business entities.

Symptoms

  • As soon as a service or component that you’re dependent on changes, you also have to make a change in your component and release it at the same time. In other words: you have the same problem as with monoliths.
  • Changes made to the service that are undesired changes for your component are forced onto you because you have a hard coupling.
  • You need the other service to first develop new functionality before you can use it in your component, leading to longer lead times and/or more integration issues due to late testing.
  • It’s difficult to switch to a different service that provide the same functionality but has a different interface.

Underlying problem

You have a hard coupling with the service that you’re dependent on. You got into this situation because you wanted to avoid writing you own business entities and mapping to save time, but the consequence is that you’re no longer in control of changes to these entities that you’re dependent on. When the service is developed by another team and/or is used by other services as well, this span of control is even more limited.

You don’t make your own abstractions and data model which are the best fit for your component and are high-level. Instead, you lean on the abstractions of the service, which are low-level. This is a violation of the Dependency Inversion Principle, which states that high level modules should not depend on low-level modules.

Figure 1. Hard coupling to a service.

How to avoid

Create your own business entities with the abstractions that make sense in your component instead of using the ones from the service you depend on. In fact, this problem can also occur in a monolithic culture. In Domain Driven Design (DDD) this is solved by defining Bounded Contexts, where instead of trying to create one unified model of the business, we divide it into smaller pieces (Bounded Contexts). Each piece can have its own models and abstractions that make sense for it. Each service or component then consists of at least one Bounded Context.

You can use the Adapter pattern to make the translation between your model and that of the service. This way, you keep this code in one place of your application and isolate changes to the service interface. If you want to avoid spending a lot of time writing code to map your data model to the services’ one, use an auto mapper that takes care of most of this work.

Figure 2. – Using Dependency Inversion and Adapter to remove hard coupling to the service.

Anti-pattern 2: Premature service creation

In this case, you create a service which is specific for one Consumer, without a real need for it yet. Often, this is done to satisfy the architecture (e.g. “From now on, we always use Micro Services”).

Symptoms

  • You have a complex and error-prone architecture due to extra components that can fail.
  • It takes more time to develop new functionality, time is spent on service creation instead of building the actual functionality.
  • It’s more difficult to locate and debug End-to-End problems.
  • Reuse of the service by other consumers is hard due to the implementation being specific for the first consumer.

Underlying problem

There’s a cost attached to creating separate services. There’s the cost of the initial setup of the service, the cost of maintenance and the increased cost of building and testing the new functionality due to the added complexity. If the benefits outweigh these costs it’s fine to do so, but if you directly start with it you probably only get the costs without the benefits.

From an operational point of view, adding services introduce you to a whole category of new challenges. These challenges include network related issues, partial outages of services and a more complex deployment.

How to avoid

Postpone creating separate services as long as possible, because You Ain’t Gonna Need It (YAGNI) yet. This way, you keep your system as simple as possible and only add complexity when it’s really needed. This is the time when you know most about the problem you’re actually solving and can actually test your assumptions on how this service is solving these problems.

If you already build your software in a proper Object-Oriented way, keeping the SOLID principles in mind, separating a part and putting it into an independent service is not too difficult. Instead of finding out the perfect plan, we want to change as cheap as possible.

Anti-pattern 3: Proxy services

You have a proxy service when your service doesn’t really do anything besides being a middle man between two other components. Like premature service creation, this is sometimes done to adhere to an architecture. The lots of implementations of services on an ESB (Enterprise Service Bus) also follow this anti-pattern.

Symptoms

  • You have a complex and error-prone architecture due to extra components that can fail.
  • You need the other service to first develop new functionality before you can use it in your component, leading to longer lead times and/or more integration issues due to late testing.
  • It’s more difficult to locate and debug End-to-End problems.

Underlying problem

Like the previous anti-pattern, the costs of creating and maintaining a proxy service are too high in comparison to the added value and with that the benefits of having this service. Also, adding a service also means adding a component that can fail and can make problem finding more difficult.

One reason that people choose to create a proxy service is to add some minor functionality, like authorization, logging or some message translation. These features often are not enough reason to justify the creation of a separate service and can be implemented in other ways (see How to avoid).

How to avoid

If you can’t come up with strong reasons why this service should be used instead of components directly talking to each other, then don’t build it. If it’s just a small piece of functionality, try to make it part of either the consumer or the provider instead of creating a separate service for it.

If you only need this service for authorization or logging, consider using a API gateway for this. API gateways act like a Facade and provide this functionality out of the box.

When you have an Enterprise Service Bus, use it as such instead of using it as a very expensive proxy service. That means using it for service discovery, routing, logging and to create an extra abstraction layer.

Conclusion

Separating your application into multiple services can be a very good thing, but make sure you do it right and at the right time, otherwise the costs outweigh the benefits. Some general advice is to use the same Object-Oriented design principles you always use but on service level: aim for low coupling, high cohesion by using SOLID principles. In the end, it’s all about being conscious about the balance between the benefits and the costs that services bring you.

Recommended reading

“Clean Architecture” by Robert C. Martin

“Monolith first” by Martin Fowler

Recommended training

Microservices Architecture

Quality Code using SOLID design principles

iSQI Certfied Agile Test Driven Development

 

This blog was written by Harm Pauw.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

*