Monday 1 July 2019

Micro Anti Patterns



The premise behind micro-services is a tried and tested one, divide and conquer via the strong application of single responsibility. Joining together elements that do one thing and do it will leads to a robust and flexible architecture.

Something however that can complicate the adoption of a micro-services architecture is the slightly abstract definition of when a service is a micro-service. One of the reasons for this is that it can depend on many factors, the context of your problem domain, the nature of your existing codebase and your required performance.

Rather than trying to address this thorny problem this article instead takes the opposite approach by describing some of the pitfalls that you can encounter when implementing micro-services.

Temporal Coupling

The glue most commonly used to bind micro-services is REST. In many circumstances this is the correct choice but not always. Equally common when this pattern is applied is the formation of chains of API calls. Service A calls Service B that then needs to call Service C and D in order to fulfil the original request.

This creates temporal coupling between all the services. Service A must wait for all services to return, a failure from any service results in a failed user operation from Service A. This leaves the system intolerant to even minor interruptions in the availability of downstream services.

The answer to this can be too decouple services via the introduction of async messaging or eventing. This potentially not only allows Service A to move on with the user journey whilst backend processes continue to run but also leaves it unaffected by temporary changes in the availability of any of the downstream services.

Implementing eventing can come with its own pitfalls and this shouldn't be used to replace all REST calls, but equally it should be a weapon in the armoury that is deployed when temporal coupling begins to present itself.

Unbounded Context

Data is the lifeblood of any system and therefore many micro-services will be concerned with the querying and manipulation of data. Given this fact it is important to consider how your domain will map to your micro-services and how you will ensure clear ownership over different aspects of it.

The goal of this is to ensure that services don't become coupled by the domain such that the complexity of the model is kept to a minimum and the implementation of new features doesn't involve widespread change.

Imagine in a retail based system many micro-services will work with the notion of a product but the view of a product model is likely to be very different between the micro-service that supports the searching of a product catalogue and one that is responsible for shipping it once an order has been placed.

By using bounded contexts, a core tenant of domain driven design, it is possible to support these different views whilst allowing the micro-services involved to remain decoupled, it simply requires the domain to be thought through and divided up along these lines.

Distributed Monolith

A common starting point for wishing to implement a micro-services pattern is a monolithic application. There is actually a school of thought that this is a good starting point because you have a good working knowledge of your problems domain that can aid the segregation of your existing codebase.

However when you do this care must be take such that you don't end up with the same monolithic application simply distributed across a network of micro-services. This can happen for many reasons, but can be detected by changes and enhancements continually requiring many micro-services to all be changed and re-deployed.

This may because of attempts at over sharing code, not having clear functional boundaries between micro-services or an ineffective deployment strategy. A strategy to deal with this situation is the Common Closure Principle (CCP), simply put things that tend to change together should be packaged together.

Looking at how a codebase reacts to and deals with change is usually a very good indicator as to its health. A well defined but flexible architecture will smooth the wheels of change by narrowing its impact. This increases the likelihood that automation can be relied upon to both test and deploy change and also makes it much easier to roll back the change should the unexpected arise.

As previously stated defining exactly what constitutes a micro-service is not as easy as one might think. However an easier prospect is to define what it isn't, this can aid iterations of trying to successfully segregate a system into micro-services by providing reference points to indicate when the architecture is heading in the wrong direction. Recognising these signs will allow you to analyse where the false step came and re-evaluate the decision to try and find a better way.

It is likely there are many right answers alongside the equally lengthy list of wrong ones. 


No comments:

Post a Comment