You are working on a monolith application and it’s getting bigger and bigger everyday. As it has been coded up by 50 developers if not 100 from the last 8 years, it has become a beast, difficult to manage and maintain. The tests are taking longer to run, regression takes ages to finish, the cost of changes are increasing exponentially. Now, after researching a while, you conclude that the next logical step is to split them into microservices. You need to pay special attention to the following details while designing an architecture for your services.
Size of the micro services
The next challenge is how you split your monolith into smaller services. You can start off with back end APIs and front end applications. However, if you put all your functionalities into back end and front end, you will end up in a monolith for both applications in no time. So, special consideration has to be given before jumping in without long term thinking. We need to build stand alone services that can be developed, managed and deployed without impacting other parts of the application. Different companies have different choices around how they split their services. Two major choices I have seen in companies are
- Back end services and front end apps
- Data layer responsible for providing data, Orchestration layer responsible for aggregating data from different services and dumb front end apps(mobile, web apps).
From my experience, I have found 5/6 APIs per service is ideal for a back end service. You need to make your own choice based on your circumstances and requirements. One important thing to note is that you don’t want to fall into a trap of circular dependencies of services while designing your distributed system.
Big bang rewrite vs incremental isolation
The next question that comes to your mind is whether you should do a complete rewrite in a new architecture or start shipping smaller services by decoupling certain functionalities from your main application. In an ideal world, you want to work on a greenfield project and a big bang rewrite makes sense. But you need to step back and think about the bigger picture and have a long term vision. It has been proven that a big bang rewrite is always a mistake as it creates problems. These include migration issues around the maturity of the processes, technology, and overall understanding of architecture. Plus, you can not apply feedback from the lessons learned from building your new services into another one as non of them would have been tested in the market. The other thing is it’s hard to find problems in non production environments that would happen in a production environment such as issues caused by web traffic, networking, different processes etc. What you should do rather is start off with decoupling a small functionality or journey into a service. Then apply the learnings from that one while building other services. Once a new journey is built as a new system, you need to make a transition plan. For example, by having a feature toggle or routing a fraction of traffic to your new service to provide a good customer experience.
Choice of the technology stack
Another major advantage of moving into microservice architecture is that you don’t have to stick with the same programming language that was chosen 10 years ago. The technology keeps on changing everyday and you will see major enhancements in speed and functionalities in new programming languages and frameworks. However, you don’t want to choose different programming languages for each new service. You need to think about the long term effects of your decision, the maturity of the technology, support of the developers community for your chosen technology stack. It’s a good idea to set disciplines and standards while choosing a technology stack and you want your developers to be accountable for their choices not only for them but for new developers who would join the team in the future. You also want to make sure you follow certain design principles and standards while designing your services.
Service management (versions, stages)
In the beginning, when you are just starting out, everything works perfectly and you would know every service and their dependencies. However, as a number of services starts growing, it will be difficult to remember and manage these dependencies. You want to set up certain standards and conventions around naming, service discoverability, their versions, and stages. You can use third party services like Apigee or some sort of API gateways to manage your APIs.
It’s important to think about how you ship your product all the way from development to production. While you are in a monolith application, you just trigger one deployment job or switch from blue to green or vice versa, then you are done. But in microservice architecture, the deployment strategy is much more complicated. It’s a good idea to automate all of your deployments to have a dashboard as a monitoring tool for your deployments. While progressing further in the deployment process, it’s good to use a containerized technology such as docker. This will ensure your service would behave similarly across different environments. In addition, it will make your journey easier towards continuous deployment.
Testing and Continuous Integration
Testing becomes more vital as there would be more dependencies around your services and you don’t want to break functionalities in one service while making changes in another service. You want to ensure your team is writing tests for components as well as integrations. You shouldn’t solely rely on the test coverage as the test suites may not be adequate. It’s very important to educate developers around good automated testing. While you update your services frequently, developers tend to change them just to pass as opposed to writing tests for modified functionalities. Changing the thinking paradigm around testing becomes more important to build fault-tolerant distributed systems. You should use tools like Jenkins, TravisCI to automate your test suites and to automate deployment.
SLA and Data Synchronisation
Another challenge is around communication between services. In the service level agreement, we need to make sure that the services are backward compatible if we decide to bump up the version of certain service. We need to pay more attention around asynchronous calls that require background processing. Normally, you want to use a separate database for each service. But that might also add up additional challenges such as consistency as you wouldn’t be able to use DB transaction as in monolith application. You might have to choose between availability and strong consistency in a microservice architecture. Another question to ask is how one system responds when another one is not available or takes too long to respond. In the fault tolerant system, you want your users to see graceful error messages as opposed to generic errors for better user experience.
Documentation is one of the overlooked and less disciplined areas when it comes to technical people. Writing and managing proper guidelines for a service as well as SLA is helpful when transferring knowledge to new developers. Another problem I have seen is these documentations are not organized properly. You would normally have a couple of different versions of the same thing in different system done by different people. Documentation is also similar to your code, you should write, maintain and keep it up to date to make it useful, otherwise, it would be hard to find relevant information.