A time comes in the life cycle of every growing startup, when the codebase becomes excessively complex & over engineered. Initially when startups are built, they are built with — ‘Get Shit Done’ / ‘Move Fast Break Things’ attitude. While these attitudes help the company to build itself in the early years till a fair share of market is captured, in the long run it creates a huge technical debt. This is practical because in order to get shit done, people just write shit. Naturally engineering is not the utmost priority for many startups in the initial days even years. So 2/3 years pass & people start to feel the heat. Over engineered code, api becomes very complex to modify. More the code people add, more they enhance the burden, paving the way of frustration for developers. So when the tipping point arises, it becomes very important to think about how to break the code base so that people can happily manage them & business see the overall productivity of the engineering team.
The problem becomes very stinky when the following conditions are met —
- The under lying database is shared by multiple teams. So the models accessing the database is also shared by them. No specific ownership of data & no bounded context on the shared code. This is very very big pain. It makes the problem more difficult to solve as multiple teams might need to get involved in order to solve it.
- People sometimes overwrite someone’s code in the git repo probably due to carelessness. So redoing the change also takes some effort from cross teams.
- As the code base is a single piece, multiple decomposable services reside under it. So one buggy code in the codebase, a full deployment is required by devops team.
- The codebase has to be upgraded to a more scalable technology. So new technologies, new programming language & new frameworks. This demands a heavy learning curve at least for initial few weeks.
So how to solve the problem? What is the most effective way to get away with the problem?
Majorly when this kind of problem appears, the problem has to be solved for long term in the right way. Specially when the company is interested to invest in this refactoring, it has to be done in proper way and the onus lies on both management & the developers. Management should be flexible with timeline and give proper time to the developers for R&D. And developer also need to take an active mindset considering the learning curve, work satisfaction & long term sustainability of the product.
For solving the problem, I think the following approach in generally will work out though it depends on the individual organisation & their structure.
- Identification of decomposability into services: When I say services, I don’t necessarily mean microservice or SOA. A decomposition means to extract some portion out of the monolith which can function independently. You might need to expose api end points to access the service. But I believe, in monolith, there are multiple portions which can function independently. Like the very basic authentication system. Trivially, authentication & session management is done inside the codebase with the help of the framework’s inbuilt session management & authentication utilities. But the whole thing can be implemented using api gateway. A separate service can be built for user session, authentication, authorization management & api routing logic. Similarly payment can be a different service. Once we identify the decomposable portions, we can check if we can really decompose them. If the portions are regularly modified by multiple teams, then it will be bit hard to do. Otherwise, it’s in control of a single team & the team can decide how to proceed.
- Parting away with the shared database schema / code / internal technologies: Can we take the bottom up approach to clean up the mess from the lower level? Mostly in monolithic code base, there is lack of ownership of data, boundary of codebase. Codebases belonging to different team modify the same database table using the same db model. The problem it poses is that you never know you modify something, other’s code can break. They modify something yours can break. Worse, you both have your code bases modified by each other. This is very tightly coupled code, a big pain to all developers. So in order to start solving it from the lower level (bottom up), you need to identify the ownership of data. The idea is to logically separate the code sharing by defining boundaries, simulating inter-process communication within the same monolithic codebase first. Because if you can’t logically separate code & data sharing in the monolith, it would be extremely tough to model it as a separate service. For some reason, in a monolith, different teams end up manipulating some common columns in the same database table. So in order to start refactoring, you might need to create exact replicated tables at all teams side. There should be strict agreement that teams will be reading & writing data at their own side, and they will update corresponding column of other teams though api call / firing query if necessary. So some redundant api calls / cross table query operations will be there till the monolith is broken. Similarly, teams can create their own models & code to manipulate their own data. In this way, a logical boundary can be created between teams where teams are completely aware of their data & code ownership. So once your codebase logically separate from other teams, you can start migrating your codebase, database schema & models etc as per your wish keeping in mind that you will expose api to other teams so that if necessary they can update data at your side. It will be better if you design the system in such a way that slowly all systems become independent & there is a very loose coupling between them.
- Upgrading the current system: Once logical data & code boundaries are defined for multiple teams, it becomes very easy for you to proceed forward. Now you have full liberty to design the system considering what all data need to be shared through api or interprocess calls. You can upgrade framework, technology, redefine api request / response. Other teams won’t care as far as they can communicate with you through api calls when required.
- There might be some other things like defining proper interfaces for all your functionalities so that others can communicate to you through interface — but that mostly depends on underlying technology framework & the choice of developers how they want to do it. If you model your code as independent service, exposing api is good to start with.
It’s very easy to pollute code. But refactoring takes efforts from all directions involving cross teams. Breaking monolith is a long term, time consuming work. It can’t be done at one go. Bottom up approach might give you more independence & clarity on how to proceed forward & relook at the system design from an improved perspective.
Above is my thought on what can be done as per my experience of what I have seen. If you have more improved & simple approach, please don’t hesitate to let me know.