When migrating an application from a monolithic approach to a microservices architecture, it’s not only a technical challenge but also requires a change in mindset for the development teams.
Ignoring the many mistakes one could do when designing a monolithic application, a benefit is, that usually the build and deployment is straight forward and the whole application needs not too many resources. Often a database and an application server are all you need to run the business logic and deliver for example a web user interface. Developers very often run the whole setup on their developer machine and use it for daily development.
On the fly compilation, hot code replacement etc. enable a developer experience where you see your changes immediately within the application. How good and efficient it might be (or not) but developers test and verify their changes, bugfixes or new features directly in the application UI.
The development flow is more or less like
- writing code for a while (short or longer)
- checking your tests (maybe even doing TDD)
- verify the changes in the running application by executing the feature e.g. in the UI
of course followed by code review, QA and a final merge request.
The transition to microservices
The decision was made that microservices add value to your companies product strategy although they come with a lot of complexity and challenges. A common approach is, that you won’t replace the whole monolithic application at once but slowly refactor out modules as microservices. There’re many different strategies described in the wonderful book Monolith to Microservices by Sam Newman.
Good, you identified a few services and your application consists of the still existing monolith (maybe reduced version including the database) and some new microservices. Everyone has to learn a lot of new stuff like containers, Docker, Kubernetes, different logging, tracing and monitoring, resilient services, health checks and that running the application is not just a single build script executed.
In this transition phase with a lot of learning and experimenting, you likely still want to have everything locally running on your machine. So, you install tools like minikube to run the 3–5 microservices you already got there and they connect to your also local running monolith.
That’s fine for a while but you soon realize that your team needs more and more time keeping all services in sync and fixing issues with their local deployment. And, it will not take long the first requests will pop up for larger developer machines because 16GB of RAM are just not enough any longer.
Adding more or better hardware is often an easy but sometimes expensive solutions and, it often hides the real problems. At least for a while.
Of course, getting a better machine is always nice and for developers this might reduce costs in a long term. But that’s a different story.
In the meantime, having a little more experience with the new technologies and infrastructure, your company got “real clusters” somewhere in the cloud and the development teams got access to some of those. There might be a staging environment and every team has their own development cluster.
But how is development looking like now?
Has everyone made the transition in their mind to do microservices, not only technically?
Are some developers still trying to run everything locally?
Are developers still verifying their changes in the whole, running application?
You need to stop thinking end-to-end. This is time consuming, costly and prevents people from getting into a microservice mindset.
And, it’s not a good idea and soon impossible to run the whole system on your developer machine. Stop trying to do it.
Thinking in contracts
One of the intentions for choosing a microservice architecture is, that teams can work independently on an isolated functionality — the microservice.
Now of course one microservice is only a more or less tiny piece of the system and provides specific functionality. Other services will interact with your microservice and your service might require data from another one. Those are the inputs and outputs of the service which should be well defined. We can speak about a contract with other services. This could be for example a schema definition or protobuf. You service implements an API that fulfills the contract.
Let’s assume we have a well defined and designed API that satisfies all required use cases for our microservice domain. Or better, all needed at the moment with the option to easily extend and evolve. Getting to this is not easy going, depending on your domain but we imagine we spent some time and effort got a good result.
Ideally the development of that microservice should now focus on the isolated functionality of that single service. We can mock or stub out dependencies if possible and testing that our service works as expected is validated by the success of automated contract tests.
No need for the developer to build and deploy the service and click around the UI or call APIs to check our code changes.
No need for running all dependencies on your machine to run the microservice you develop.
Don’t try to run a whole application based on microservices on your machine.
As a developer, don’t try to verify code changes for your microservice on a running system. Focus on your service contract.
The peoples mindset, the monolithic and end-to-end thinking is often difficult to change and takes time you need to plan for a migration.
My thoughts above often have an ideal case in mind. It’s not always that simple in reality or even possible. I’m happy to get your feedback about your experience when migrating from a monolith to a microservice architecture and how your teams act with the changes.