At LocoNav, we have been maintaining a decently large (yet manageable) monolith Rails application. Initially, all of our backend stack was in Ruby. With time, our ingestion layer was rewritten in Golang, and the data layer was moved to Java, but the web part was still a large monolith. It had almost every functionality we provided and the developers kept adding more functionalities to it. We knew that we were building a strongly coupled system that would need untangling very soon.
Around mid-2021, we started our discussions to break this application. Fast forward to today, the large application still exists. A few parts of the system were extracted, and many are still in progress, while some got deprioritized, but the knowledge gained from the process was immense. I’m sharing a few points from that knowledge:
Keep the front end out as soon as possible - Our monolith is a full-stack application. That means we had the entire frontend code inside this repo. Our oldest stack was Embedded Ruby and jQuery with Bootstrap. But soon we started moving part of the system to React. For the longest time (including now), our deployment process included the management of both ruby and react-based frontends. If you have seen the deployment of a frontend / full-stack application, then you might be aware of the processes that happen to make frontend assets production-ready (transpilation, minification, compression and much more). Our frontend part of deployment (known as asset precompilation in rails) takes 50-60% of the total deployment time. We’re in the process of taking out the React frontend completely from this repo, but this is the debt we pay every time we need to deploy our app.
Read a lot before starting - Monolith to microservices is a complete subject on its own. You might be a very experienced senior developer, but I’d not recommend doing anything without reading enough on the topic. I found this book quite helpful in understanding the challenges and the journey. By reading enough, you’ll make sure that you don’t get trapped in issues like distributed monolith or data inconsistency. Also, unlike before when the entire system went down or came back as a whole, now parts of the system can fail. So we need to make sure that the dependent services handle these situations (eg. adding required timeouts).
Align enough resources - Often companies with small team of developers assume that they can align some bandwidth for microservices migration but even after months they don’t see any significant progress. Please understand that microservice migration needs a dedicated effort. That doesn’t mean 2x of your developer strength, but at least more than what you currently have (or deprioritize some tasks for existing developers for a long time).
Train people for now and future - The developers (like me) who have been working on monolith over the years will need a mindset shift to start productively working on microservices. Some people might not agree with it, but the idea of microservices is modularity. If you bring the same mindset of a monolith system while writing services, you might end up with a few small monolith applications. You’ll need to develop a lot of libraries and tooling to avoid the repetition of work across services. Some use cases of such libraries are - consistent logging, request-response handling, inter-service authentication, deployment tooling, etc. Accordingly, developers need to learn how to use these to avoid duplicate efforts. Anything that sounds outside the scope of your business logic should be taken out in a library so that someone else can use it in future if needed. This habit also allows you to write functionalities that are decoupled from business logic (Click here to read more about the Benefits of Decoupling).
Leverage OpenAPI autogeneration - If you’re aware of OpenAPI documentation (more popularly known as Swagger documentation) and you don’t know about the autogeneration that comes free with it, then you’re missing something great. Although this could be a detailed post on its own, in brief - if you are defining a nice API documentation for your service, and have documented all the request and response schemas along with it, then you can generate a client in any popular programming language without much effort and start using it without any hassle. I’m leaving two links here to help you get started with this (don’t miss this): The tool and The documentation
Think twice whenever you’re building something new - Whenever you’re building a feature that sounds somewhat large, just think if you can draw clear boundaries on the needs of this feature and take it out right away. This might be easily possible for some features while overkill for others. I’ll leave this to the understanding of the Engineering manager who is building this. In case you still plan to build this inside the monolith app, use the required service layer (facade layer) to access this feature so that your controller layer doesn’t heavily depend on the model layer (ORM) to CRUD on the data.
Refactor, Refactor, Refactor - Read a book around design patterns if you can. When you plan to take out a module from your large app, you will introduce some layers of abstractions that might be temporary or permanent to decouple it from the rest of the app. This is true for any part that you’re taking out from a monolith (you are blessed if you don’t need to do this). This effort might be underestimated if you’re doing this activity for the first time.
Costs will increase in two ways - A true microservices system is not for small teams. You need people to manage production-grade services. Although the ratio between developers and services varies from one organisation to the other, generally the need for developers increases as you move to services. You might want to rethink your decision to move to microservices if you don’t want to invest in a larger team (Read this for an alternative architecture). Another direction in which cost increases is the cloud cost. More services will lead to more costs - costs for servers (or containers, or pods), databases, infrastructural maintenance of these systems, tests, deployment and so on.
Above are the best insights I can share from my experience. See you guys later!