Most successful projects incur their own level of technical debt. This is a normal part of building software: requirements change, features are being built, customers are happy and using the software, more features get built, and so on. These projects tupically reach a certain point where they begin to have a large enough surface area and a similarly large number of developers working on them that development begins to slow down. Ownership of the various portions of the system becomes fuzzy, developers begin to spend a lot more time communicating about how to implement new features.
The typical software lifecycle
Much has been written about monolithic vs. service-oriented patterns, and a fair amount has been written about the benefits of building new software with those patterns, and the trade-offs. I would like to talk about the lifecycle of software in most organizations, which doesn’t always fit into such a nice bucket.
Splitting apart the monolith
Monlithic software applications, being as large as they are (I have worked with code bases with over a million lines of Ruby spanning hundreds of models and controllers), pose a challenging problem for most engineering teams. Much like the age old rhetorical question of “How do you eat an elephant?”, the answer to the question of “How do you break apart a monolith?” is very similar. The answer? “One bite at a time.”
Here, however, there is no food to be eaten, instead your stack likely has tunnels of logic winding their way through the code. Templates and sub-templates that are dependent on variables being defined elsewhere, code that is tightly coupled to your storage mechanism, and lots of other gremlins hiding in the woodwork. So how, then, do we begin this monumental task of breaking things apart? One way to approach this problem is to begin to form boundaries around the various logical portions of your system. In this way you can cordon off related pieces of code into isolated, maintainable, and easy to understand sub-systems that can then interact with the rest of the system in a natural way.
Defining “Service Boundaries”
Defining service boundaries is a critical part of being successful in a system where your data is managed by a number of services. By defining boundaries you can take a monolithic code base and begin the process of moving to a service-oriented architecture. Whether you choose to use microservices, where each services' surface area is very small, or slightly larger services is up to you. Either way, this method does not lock you in, you can change course as needed at a later date.