Imagine that you have a huge 7 year old monolith, and you have just recently started to break it down into microservices. You are in charge of the design and implementation of a significant part of your company’s product, and you have been asked to develop a feature that is crucial to the success of your company for the coming year.
This is what the Gett for Business R&D team faced, when their product manager asked for the additional option of self-registration for businesses. Now, you have a dilemma – develop a quick solution over the existing messy but familiar code in the monolith, or take half a year to create a new set of properly defined microservices?
So what was the solution that enabled us to create a new set of microservices, while deploying them to production even before our deadline? Let’s find out.
Self-Registration – What is it all about?
Registering an enterprise company requires a dedicated account manager and a lot of time for manually customizing the system based on the company’s specific needs. The self-registration solution, targeted at SMBs, allows companies to order a ride or package delivery in just a few minutes.
How is it done? By making the process totally automatic, using a basic set of features and default options.
Understanding where we wanted to go
The beginning of our long journey to breaking up our monolith was described in a previous blog post – Microservices@Gett – When starting this project, we had about 90 microservices, but only 2 of them were owned by the Gett for Business R&D team. The rest of the business logic was buried deep inside the monolith.
The website for our business customers was served by rendering the pages on the monolith. The libraries we used were outdated, and each HTML or CSS change required a new deployment.
We wanted something else – something that would take advantage of our strengths and allow us to work around our weak points. We wanted our solution to be able to deliver significant business value, while providing a high level of quality and still keeping to a strict time frame.
We had to construct a solution that could work side by side with our existing system, which served thousands of companies and millions of rides per month.
Designing the solution for minimum friction
As we set out to design the solution, we started with defining the different boundaries or areas of responsibilities that took part of the flow. We made a strict rule that we should not add or change any functionality in our existing monolith, which would result in a significantly more complex testing and deployment plan. We wanted to use the functionality of existing microservices to save us time and effort.
Here is a diagram of all new and existing services that take part of the self-registration flow.
The business logic for managing employees was part of the monolith. Extracting it at this stage was going to take a significant amount of time and effort.
Following our rule regarding changes in the monolith, we decided that the new Employees Service would be a proxy and pass all requests to the monolith. Once the current project was complete, we could complete the service with lower risk.
We had a dependency on a new integration for communication with external systems. When a new company registers, its details should be propagated to these systems.
To reduce the integration risk, we created a dedicated service for it and made sure that all communications with this service were asynchronous and the service would not be a mandatory part of the registration process.
Taking care of internal integrations
We had 3 teams working on the project. We wanted to make sure integrations between our services were sorted out, for our test engineers to start writing integration tests and start building the required automation code as soon as possible.
We shared a document describing all the endpoints of the relevant services, including inputs and outputs in one place. This made it easy to notice any discrepancies or misunderstandings between the teams.
Creating the stub services and deploying to dev and staging environments, enabled our DevOps to prepare everything that was needed for our CI and deployment to production, saving us precious time later on.
Writing tests on these stubs made sure that all further changes from static data to the real business logic were covered.
Adapting unique work process for greater efficiency
At Gett, we are working in sprints of 3 weeks. We have a proven process of when to do our grooming, planning, regression tests and how to deploy to production. This process is complicated, as it includes both our monolith and microservices.
Instead of continuously delivering small features of the project, we decided that we would only deliver all the new functionality when everything was ready, ignoring the the rest of the company’s sprints.
Making business compromises where possible
Everywhere we could, we chose default settings and removed the need for manual or complex decision making. This is also at the heart of a self-service solution – to make it as easy and straightforward as possible.
The core flow of self-registration involves many moving parts, and with numerous microservices it means many failure points. Taking the optimistic approach, we planned our services to be as permissive as possible to failures. We defined most parts of the process as optional, and set-up monitoring processes to alert us to such failures. For each failure, we’d manually fix it if needed.
What we achieved
After a lot of hard work, the teams managed to complete the project ahead of time. During that time, many issues were found around the functionality, design and data, but we quickly solved them with short and focused discussions.
Thanks to the approach we took and with the new services in production, the velocity of the team was greatly improved. Now It will be much easier to continue with our roadmap in quick, agile sprints.