Part 1: What can go wrong, will go wrong!
Over the years we have been part of a couple of migration efforts to a more modern infrastructure and architecture. We have seen the good, the bad and the ugly side such projects bring with them. All of them could be described as from monolith to microservice, using different strategies. Most of them took ages, went nowhere till today or yielded not the hoped for result. This Post will describe the most underestimated factors.
So, what was usually the starting point and what are the hoped results?
The starting point:
Most projects start from a similar point. Systems have a classic design consisting of one or more monoliths on classic servers together with a database. The systems often evolved over the years and refactoring was mostly neglected for a broad variety of reasons. Thus maintenance became hard and expensive and developers willing to take up with even more scarce. I guess you know what kind of projects/products are described here.
Disregarding some individual variations, the goals are largely the same:
- A Switch to modern technologies.
- Use docker for virtualisation.
- Have compartmentalized components that are easier to maintain, test and that can evolve individually.
- Reduce the complexity of the system.
- Allow for continuous integration and deployment.
- Be more flexible and refactoring friendly
Note that this is just a rough composition and does not claim to be complete. The Lists are just an example to establish a rough frame for what’s to come.
Modularity and Microservices?
The current definition of Microservices people have in their head is largely thanks to Martin Fowler. As far as I see it, this definition is an architecture approach to achieve modularity which came up in the 60s and 70s. In my understanding a Modular System is based on more or less independent components that define services as an API to communicate with them. Modularity can be achieved nearly everywhere, but is very hard, especially in environments, where everybody has access to everything. They need a strong architectural governance or a Framework that sets boundaries and helps to keep them in place. Every Component defines a contract everybody needs to adhere to. I remember a lot of projects from the past, where we e.g. used a Framework A. When the API of Framework A didn’t allow for something needed and crafty developers (myself included) pulled up their sleeves and reached arms deep in the inner workings of A. Then some inner class got pulled out of it's inner workings that did the trick. Well, from time to time developers have good reasons to keep things inner API. The next version update of A can screw you big time, because the hack might be impossible now.
From our perspective, modularity is more a Mindset a Developer needs than an Architecture. True modularity adds complexity at first and requires years of practice. It requires keeping your monoliths as small as possible, which then can be called components. You really need to think about your API and what needs exposing. True modularity needs Versioning as well because I need to define what API Versions of other Components I need in order to work. At the same time I need to consider what impact changes of my API have and how my Version should reflect this.
Why microservice Projects fail or disappoint
The human factor
Consider the starting point I have described. Old architectures like this evolved over the years. So when you start migrating to something new, you need to have a look, what team you have at your disposal. We have mostly seen two scenarios:
- You start with the team that created the Monolith in the first place.
- Developers came and went over the years and there might not be that many original developers left. Here the situation is more complex. The Project/Product most likely became painful enough to trigger a migration effort. Thus consider the developers that stuck with the project up to this point. Open minded and eager developers often leave such projects after a short period because they look more for the “cool” stuff. You most likely have a team comprised of people that feel at home in the “old” world.
Long standing or even initial Team members usually need a lot of convincing. They have developed monoliths for most of their lives. They can become a tough nut to crack in architectural discussions. You will hear things like: “Why should we do this?”, “This can’t be applied here!”, “The way we do it now works.” and so on. I guess you know the type.
Then you will have the eager ones, who most likely bring a lot of wind in your team. They can be dangerous as well, because what they have in enthusiasm, they lack most often in experience in this topic.
So you may have an experienced team with senior developers, but they are most likely noobs in regard to modular thinking.
Fallacies all around
Due to the fact that, every project and situation is individual, I can not paint a complete picture here. Thus I will present a list of Fallacies we stumbled upon over time.
Boundaries of your services
The question about the boundaries of your Microservices will most likely be the first that is discussed. There are a lot of approaches that could or could not fit to any part. Do you divide your service along technical or business lines? How big can a service be? What can be part of a Service in order to consider it a Microservice? A lot of Questions with a lot of possible answers.
In our experience the following quote from an unknown source has the most merit here:
"You will never get the architecture and domain boundaries right from the start with microservices"
But, as said before, you will have a team that started fresh with this topic. A lot of people will read about it and want to do it right. Thus they will pick most likely one approach and present it as the optimal one. There will be long discussions and the realisation that the truth is somewhere in the middle, often comes late and after a lot of time and money went into beating one approach over every use case.
Stay flexible here. One of the goals of a Microservice environment is to be more flexible and refactoring friendly. If you fixate on one concept you will lose this ability.
The team can concentrate on the business logic, Kubernetes and Docker will do the rest.
Most developers think that they need some logic with a REST API in a docker container for every component and let something like kubernetes do the rest. They mistake infrastructure management with application management. Kubernetes is a great tool for managing your containers and even allow a certain amount of additional features. But it does not care about the inner workings of any application. That a Container is started and running, does not mean that the component this container represents is available. Consider an index that runs as a service (Solr, Elastic Search or similar). On first start the index needs to be built before the service is actually available for serious queries. This can take some time and the only thing kubernetes knows is that the container is up and running. Your application needs to handle the lifecycle of your services and this is not always easy.
Components are trivial and easy
A Component itself should be simple by definition, in order to be easy to develop, maintain and test. The whole system on the other hand is another topic. Modularity adds complexity at first in comparison to other systems. You need concepts for describing your dependencies, services and Version, as initially described. Services need to be announced in the System. Modulare systems have a certain dynamic and this needs to be addressed. You can not assume that a service is always available in the version you want. You need to have strategies to handle such cases. When you go move to a distributed system, where components run in their own exclusive process, you will find new previously unknown issues. Maybe you had caches before, where do you put them now? Services now communicate remotely and have added latency due to the network,serialisation and deserialisation for every call. I would say, that a modular System makes it easier to implement business logic and maintain it, but much harder to make it run smooth in the first place.
The Framework we want to use does not fit to our case, we can build something better!
Just don’t! As mentioned, a modular and especially a distributed system will need a lot of glue for its components. There a couple of Frameworks around that can help you here. We nonetheless have seen it from time to time that most or at least a lot of the “glue” was hand- written. This usually brings more problems than it solves. Don’t reinvent the wheel! The reasons have always been, that the available Frameworks appeared aether as too complicated or the desired process wouldn’t fit in any of them. When you reach such a point, you might be in dangerous waters. First of all, most of the Frameworks have been created by people that have been through the same issues you are currently working through. They often have much more insides then you might think. They often appear overly complicated, because they address issues you don’t even know you have or will have. If you can’t apply the framework to a process you have, rethink your process.
Set the goals and let the team(s) run with it
The biggest mistake we have seen, was that the initiators of the project left before it was ready. You need a strong architect that has an eye on the progress of the team. Next to this you need at least somebody that has the experience required here, that can work inside the team to show how things should work.
Ignoring the 90-90 rule or Pareto principle
This is best described on an example we had. A customer was setting up a microservice based environment and wanted to recreate step by step parts of their old System there. They chose a reasonable MVP to start with and planned for enough time. So far so good. At first they spend a lot of time on their initial setup. Kubernetes took a lot of time to get up and running. Time went also in the setup of the Database cluster in the new environment and for the messaging cluster. Additional and quite significant effort went into discussions and evaluation of Frameworks. Then the implementation of the different components started and everybody went off with his individual Component. We early on warned that the actual assembly will need time and should be started with dummy services as quickly as possible.
Well, nobody listened and close to the presentation in front of the board nothing was working and could barely be coupled together. Instead of working on their “glue” components afterwards, everybody jumped on the next bigger thing and the result before the next presentation was the same.
When you do Modularity and Microservices right, your can shift this principles in your favour, but they will hit hard at the beginning.
Many of the fallacies mentioned fit to any Software Project and are not specific to Modular Systems. In our experience Migration or even green field projects with teams new to the concept are much more prone to tapping into these holes. Modulare projects have a higher complexity to start with, which could explain it. Modularity and Microservices play their strong hand later in the game.
The Fallacies don’t necessarily lead to a Fail of your Project, but they can lead to:
- Loss of time and money
- Trust of your other Teams, that should use the platform for their Projects in the future too
- Trust of your Customers
to name just a few Things. If you find signs of the listed Fallacies I can only recommend to get help. Hire somebody who has the experience and give them the power to enforce Things if necessary. If not make sure that your teams at least listen to them!
Stay tuned for the next Post on how the ideal migration should look like and how to achieve it.