It’s been a few years since I first wrote The Seven Deadly Sins of Microservices after working on a few early microservices projects and noticing a number of common pitfalls. These were mostly technical; since then, the technologies that enable microservices have evolved significantly but design approaches and working practices have not necessarily caught up.
Indeed, quite a few of the anti-patterns we observe today on microservices projects are strongly related to how people approach the problem. Given their nature, these anti-patterns tend to be deeply ingrained and self-sustaining. Addressing them starts with increased awareness and by changing ways of approaching the problem, rather than by the introduction of yet another technical tool or framework.
To put these antipatterns in context, let’s highlight some common motivations behind microservices adoption:
- To enable the use of modern technologies (such as cloud non-relational databases)
- Managing and adapting to fast-moving business requirements
- Building systems that can handle change dynamically
- To allow for more distributed ways of working (for both efficiency and convenience)
- Designing flexible and evolutionary architectures
Let’s look into the anti-patterns now.
The SOA Heritage
Consciously or not, many organisations approach microservices as a better SOA. These two architectural styles do share a common pedigree but in my experience, it is neither practical nor helpful to rely on these similarities. Firstly, the technology landscape has moved on since the early SOA days with the emergence of new tools and techniques such as automation, containers and streaming. The way people organise work has also evolved, with broader adoption of agile and distributed teams.
Nevertheless, the influence of old school SOA can still be discerned in many modern microservices projects. Organisationally, one of its most pervasive features is ivory tower architecture, where the architecture function is divorced from delivery and runtime concerns.
Technically, there might be an excessive focus on point-to-point integration such that the microservices architecture becomes indistinguishable from an ESB. At the implementation level, this approach manifests itself by developers optimising for, what I call, monolithic values such as DRY at the cost of more decoupled architecture.
These are not good tradeoffs in a microservices architecture where flexibility, composability and diversity typically provide better value.
The Homegrown Framework
A few microservices projects we’ve seen start with an attempt to create a homegrown “microservices framework”. While this is understably tempting, it can give a false impression of understanding and progress. Primarily, there are better microservices frameworks out there that you might be missing out on. One good example is Spring Boot and its plethora of companion Spring Cloud projects.
There are two misconceptions that motivate building in-house frameworks in my experience. The first one is the belief that every aspect of a (micro)service should be controlled through its internal code. The second is the automatic application of monolithic values to microservices as mentioned earlier, such as DRY and extreme configurability.
Putting these together results in organisations diverting effort disproportionately from creating business value and into developing complex code that is not necessarily fit for purpose. Unwittingly, this might also make it harder for new developers to work on the system as well. It also means that applications implemented in different languages might behave very differently or might not share the same “good behaviour” across the platform.
Logging is a typical example of this antipattern. In a monolith, most aspects of logging are controlled through application logic. In a microservices architecture, on the other hand, logging concerns – including configuration, collection and aggregation – are the responsibility of the platform as a whole; not of the individual service. Many custom microservices frameworks I’ve seen start by trying to control every aspect of logging through code.
In some respects, this antipattern is the developer’s equivalent of ivory tower architecture.
Fear of Deployment
Services are only useful if they are deployed and if they provide value and flexibility to handle change. Microservices architecture results in complex systems where the complexity is in the behaviour of the system as a whole, not in the individual service. If anything, individual services are the most trivial part of a microservices system. The complexity comes from how services communicate and scale. It comes from the different pieces of middleware (API gateways, data transport), databases, etc. A service is only useful when deployed. When a service is invoked no one knows what language it was implemented in or whether it adheres to DRY principles or not. However, everyone notices a slow or flaky service.
The point is, you can only say you have a meaningful microservices system if you deploy services to a real environment and test them continuously (functionally and non functionally behaviour) from the very start.
The focus on speculative solutions (homegrown frameworks, powerpoint architecture) leads to a situation where months into a microservices project, there could be no tangible proof of how the system works, or if it works at all. In some cases, a passive resistance against concrete proof of system behaviour develops within an organisation.
Lack of Business Alignment
Microservices should be all about delivering clear business value yet many projects start with a purely technical premise. The way microservices can enable more business value is through better handling of change. A change can be seen as any dynamic modification in the system. Scalability is a change in the number of instances. A new version of a service is a change in the software. These are not purely technical concerns because fast delivery and the ability to update small parts of the system makes all the difference between a feature being available to the end users or not. Even non functional behaviours such as resilience and latency determine how usable a product feature is to the end user. It’s never a purely technical endeavour. It’s always about business value.
The mindset and the skillset of a team approaching microservices is probably the main determinant of success. Modern software engineers should have good understanding of the software, but also of the underlying infrastructure. Many organisations try to move into microservices without ensuring that their staff have adequate knowledge in both areas.
It’s no surprise that, in this case, you end up with pain points such as a lack of autonomy in the development process, or design mismatches between different components of the infrastructure, middleware and software. This is another antipattern that reinforces the tendency to seek platform-wide solutions by coding them locally into microservices (development as a golden hammer discussed earlier).
Training engineers up in the different aspects of a microservices architecture, including infrastructure, has been one of the most effective ways of improving the outcome for the people and the system in my experience.
Microservices are better approached as holistic systems that are distributed in nature and that require the right type of approach, infrastructure, technologies and automation to be successful. The most effective way to handle that is to focus first on the people and the mindset, the approach and the skill set within the organisation.