The microservices fallacy - Part 4
This post discusses two widespread fallacies – that microservices improve reusability (and thus pay for themselves after a short while) and that microservices improve team autonomy.
In the previous post of this series we discussed the fallacy that microservices lead to simpler solutions.
In this post we will first look at the reusability fallacy. As I already wrote a whole blog series about that topic, I keep the discussion short in this post. After that we will have a bit longer look at the fallacy that microservices improve team autonomy. But let us begin with the reusability topic.
I discussed the reusability fallacy in detail in a prior blog post series. One of the posts discussed the reusability fallacy in the context of distributed application architectures like, e.g., microservices.
The short version is (for the long version please read the blog post I just referenced):
- Reusability is often used as an argument to sell different kinds of modularization paradigms to decision makers: The high degree of reuse that were to be expected from the new paradigm would guarantee that the initial investments needed to introduce the new paradigm would amortize very soon. You also find this reasoning with microservices which basically are another modularization paradigm – just a distributed one (like, e.g., DCE, CORBA, EJB, DCOM, SOA before).
- Reusability means tight coupling. Actually, it is the tightest form of coupling that exists. If the reused part fails, the reusing part also inevitably fails, i.e., is not able to complete its work.
- To ensure availability of distributed systems like microservices-based applications, you need to avoid cascading failures. “Cascading failure” means that a failure in one part of the distributed system leads to failures of other parts.
- “Avoiding cascading failures” translated to microservices means: If one microservice fails because it gets slow, crashes, runs into consistency problems or alike, it must not affect other services. The other services must remain available, i.e., keep running and delivering their service offering.
- Reusable microservices mean: If service A reuses functionality provided by service B and service B fails, service A also inevitably fails because it cannot access the reused functionality it needs to fulfill its own service offering.
- Technical measures like timeout detection or alike do not help because the coupling takes place on the functional level. Even if service A detects in time that service B is down, it does not help because service A needs the unavailable reused functionality of service B to complete its request. 1
Aiming for reusable microservices means deliberately crippling availability.
If you additionally take into account that reusability rarely pays in a service context (see this post for more details), it becomes clear that reusability is probably one of the last things to strive for in the context of microservices. 2
Another widespread justification of microservices is that they improve autonomy.
My first question then is: “Autonomy with respect to what?”
A typical answer is: “Microservices improve developer autonomy.”
My response to this statement then is: “Really? Are you sure?”
My point is that the whole idea of microservices improving developer autonomy starts at the wrong end, trying to make the tail wag the dog.
Autonomy starts at the organizational level. First of all you need to be organized in a way that teams can make decisions without having to coordinate with people outside the team. To get there, you need to implement a lot of prerequisites. The two most important ones are:
- You need to establish decentralized decision authority. Traditional hierarchical organizations with centralized decision making processes prevent team autonomy by definition.
- You need to organize your workforce in a way that decentralized decision making is possible and provides a clear advantage over centralized decision making.
Most companies fail at the first prerequisite as the existing decision makers are not willing to pass on their decision authority. They usually still want to control every decision. In such a setting, autonomy is just an illusion. If you cannot decide on your own, you are not autonomous.
But even if you get this prerequisite implemented, you still need to organize in a way that decision making in autonomous teams is possible and provides an advantage over centralized decision making.
In a first approximation, you need to isolate different, mostly independent streams of ideas and requirements, having only very few dependencies (ideally these streams would have no dependencies at all, but in practice this is hard to achieve). The different market-facing capabilities of a company create such mostly independent streams.
Still, very often companies have a hard time to organize around market-facing capabilities. Most companies instead solely focus on optimizing their internal processes without considering the actual needs and demands of the market (I discussed this phenomenon, e.g., in this and this post).
In such an inside-facing setting, the streams of ideas and external requirements typically need to be distributed across multiple parts of the organization for implementation. This means that all the time multiple teams need to coordinate to implement new ideas or requirements. In other words: Team autonomy is impeded by design because most decisions require a high amount of cross-team coordination.
Hence, you need to organize your teams around mostly independent streams of ideas and requirements, typically provided by different market-facing capabilities. You also need to have all types of skills and knowledge in that team that are needed to make decisions effectively. This incorporates not only IT skills, but also business, marketing, financial and other types of skills.
If you have established such an organization, your teams can make decisions and act autonomously 3. Yet, as business and IT are not separable anymore these days, you additionally need to make sure that your software architecture does not compromise the arduously established team autonomy.
Assume you have 10 “autonomous” teams working on a typical, internally not too well organized monolith. Due to the tight coupling inside the monolith, the teams always need to coordinate if anyone wants to make a change: Does our planned change affect other teams? Can we check in partial implementations of a bigger feature without hampering other teams? When can we deploy as the work of multiple teams depends on each other? And so on.
This required amount of cross-team coordination kills all autonomy – including its advantages, i.e., the ability to move faster independently.
Therefore, the “loose coupling” (independence) is not only needed at the organizational and functional level, but also on the IT level. IT assets should not cross the boundaries of teams and should be coupled as loosely as possible on a functional and technical level to support team autonomy. 4
So, in the end the causal dependency is reversed. You first need team autonomy on an organizational and functional level that then is supported by microservices on a technical level. This is what I meant when I said that the statement “Microservices improve team autonomy” starts at the wrong end, trying to make the tail wag the dog.
Microservices are a possible means to support team autonomy on an IT level.
Yet, microservices alone do neither enable nor improve autonomy. Autonomy must be established on an organizational, functional and governance level.
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.
In its broader interpretation it states that a software architecture of an IT system and the process organization of the group of people working on it must be aligned. If they are not aligned, problems will occur on both levels, the people and the system. Usually, the communication on the organizational level will explode and the system architecture will deteriorate.
So, if you do everything else right regarding team autonomy, microservices are a possible supporter of team autonomy on the IT level. Microservices alone do not have any positive effect on team autonomy. They just make things more complex.
I will leave it here for this post. In the next post, I will have a closer look at the fallacy that microservices lead to better solution design. Stay tuned …
You could launch several replicas of the same service and then implement some proxy either in the calling service or in front of the replicated services that watch the request processing duration. If it takes too long, the proxy sends the same request to a different replica trying not to exceed the overall time span allowed for the processing of the given request. But be aware that this means you multiply the required resources, code and infrastructure complexity (including the need to address hard problems like state synchronization between replicas) that you would not need if you would design your application differently on a functional level. ↩︎
Of course you will encounter functionality you would like to reuse (and where it economically makes sense to turn that functionality into a reusable asset). But as a rule of thumb you should strive to place reusable assets in libraries. Libraries run in the same process context like their callers. Thus, the problems of non-deterministic communication between reusing and reused functionality do not strike. ↩︎
You still need some kind of cross-team alignment to make sure that all teams work towards an overarching goal. But creating alignment can be separated from the decision making process. ↩︎
The different IT assets do not necessarily need to be distinct runtime units like in the case of microservices. Etsy proved that team autonomy and independent deployments are also possible with a well-structured monolith and a small set of rules that all teams adhere to. Still, this approach requires more discipline of all people involved than separate runtime units. ↩︎