Microservices and Developer Experience - Lessons Learned

In this blog, I want to discuss how adoption of microservices as the mainstream development practice has impacted our Developer Experience and share some of the observations and lessons learned. We have come a long way in terms of evolving our tech stack from being a C#/.net heavy Monolithic Enterprise shop to now lean micro services. Today we have hundreds of microservices written in different stacks with most popular languages being Golang, Python, Nodejs and C# (dot-net core) and everything gets deployed as a container!

Looking back, during Enterprise/Monolithic days let’s say I want to work on a story, my developer experience would look something like this:


I would come to office in the morning and drink my coffee (most important part of my day!). Download monolithic code + build it + restore DB register + services and all…. basically, trying to get to a point where I have a working system. In monolithic you could spend somewhere between few hours to an entire day from the point where you download latest code to a point when you have a working system. Personally, I have spent hours and hours of my day on this messy and time-consuming process. I would take it even further where I will not even download the latest code for as long as I don’t have to and that could for weeks. I would stay on the old version of the code and work from there because I don’t want to kickoff this time-consuming process again.

Since the adoption of microservices things are different! Today when I want to work on a story, my developer experience looks something like this:



I come to office->drink my coffee --> download the stack --> run docker-compose up --> work on the story! And at this time, I am still drinking my morning coffee!
That’s it! I have a working system in just few mins. This is hugely different and super awesome! Earlier I would not even download the latest code for as long as I don’t have to and if I did, it would take me hours to bring up a working system. But today the first thing I do is get the latest code and getting a working system is as simple as docker-compose up (few teams prefer bash, so they provide a script like service-up.sh or similar). Here the definition of working system is such that the stack I want to work on is deployed locally on my laptop. Its connected to a share environment for all its dependencies. Also, you will need to figure out routing from app meaning if you are logged in yourself then all requests are routed in such a way that it will be sent to local instance of service (for the APIs its serving) and use the shared environment from rest of the dependencies.

Lessons Learned:
Getting to a simple local setup is difficult but consider it MISSION-CRITICAL!
Your local setup has to be as simple as docker-compose up or service-up otherwise it’s too much of productivity lost. Consider it mission critical! What about debugging? - When local setup is also containers it’s hard to debug and you cannot attach any IDEs to the process. One obvious answer is to not run the local service inside container and continue to use your standard way of debugging. Other better option is to learn to debug from logs. It helps you in two ways:
  • If teams are paying attention to logs at development time that means they are clean and meaningful. 
  • During time of crises - you don’t have to go back and open service code to see what is it that my service actually logs. You probably have a very good idea already. 

No big up-front designs:

This is an interesting one. More and more teams are moving away from doing big upfront design and instead taking evolutionary approach. They will quickly spin up a service that solves their current need and evolve it as the need arises. What’s the problem with big upfront design anyway? The way I think about it is that with investing too much into big upfront design - you are emotionally attaching yourself to take care of this service for years and years to come because you are spending so much time doing future proof designs. Evolutionary design is a much better approach. We have teams who are on 5th version of their API in a short amount of time and each new version is completely refactored or rewritten in a different stack compared to previous one. And this is OK because they are able to move faster! Btw this practice of massive refactoring or rewriting a service is a very common thing in microservice world. I personally have done it so many times! Good luck getting permission for this in enterprise but a very common practice in microservice architecture.

Onboarding a new developer is much easier on ยต-service stack.
This is sort of a no-brainer. If you have a microservice with a well bounded context then it’s easy for a new developer to wrap his head around what is it that this service actually does.

Massive increase in cross team PRs!
In a way it is sort of a variation of the earlier one. We are noticing massive increase in cross team PRs. Specially for small things like let’s say I have a dependency on an API and I consume 2 fields from them. Now if I need one more field, I will get their stack locally and see how they are doing it and if it is simple enough I will just create a PR for them. Earlier this could have been a small project in itself but today it’s just a PR away! Massive increase in cross team PRs.



The single most important thing that could boost developer’s productivity is providing a simple Developer Experience that one has to deal with on a daily basis. Adoption of microservices has helped us take this experience to the next level where teams being more productive and able to take quick local decisions and move faster. As managers/leader investing into Developer Experience is probably the best thing we can do for our teams!
Happy Coding!

Popular posts from this blog

Break functional and orchestration responsibilities for better testability

Microservices and Tech Stack - Lessons Learned

Prefer Consumer over Integration Tests