This is a post about service-oriented architecture. It is not a post about how leveraging Docker and migrating to a microservice architecture will suddenly build you a Google-scale product. It is about how such technology can make life better for developers, for teams and for their productivity as a whole. A service-oriented architecture will not take a poorly designed piece of software and make it better, rather it will enable you to take ownership of your software in ways that may not be possible otherwise. Astute readers will realize that this approach is not really a new concept - modular software development has been around for a long time but now we have networks and utility computing that is easily accessible and permits us to leverage isolation of resources so services are the new-era version of plugins.
The problem with too many cooks
Large, monolithic, code bases are notoriously challenging to manage; as your codebase grows there are typically two significant pain-points:
- Nobody can keep a mental model of your entire application
- Communicating changes to the system becomes increasingly difficult
As a result development slows over time until adding even trivial features becomes a nightmare. I’ve encountered this problem with a number of projects that I have worked on both at Goodreads and at UpCity, and no doubt developers all over the world have hit this wall.
Both of these are problematic in their own right: it’s hard to build new features and estimate their impact when you can’t fully comprehend all of the pieces of your system that will be touched, and keeping up with changes to your shared codebase becomes somewhat like drinking from a firehose with hundreds of commits per day.
Imagine, for example, a Rails web-application with nearly a million lines of Ruby code, two hundred models and one hundred controllers. Changes to code at that scale are incredibly challenging to coordinate between individual developers let alone entire teams of developers.
At this scale, test suites take tens of minutes if not hours to execute on a developer’s machine. This, in turn, leads to developers not running the entire test suite before check-in, which reduces their confidence in the software’s correctness. Quality begins to degrade, slowly at first, and then at a rapid pace as developers stop writing comprehensive tests and eventually begin to ignore the results of the test suite as it becomes more and more onerous to execute.
Enter services (or modules)
Services are a network-based application’s analog to modules; they permit delegation of work to components that are spread out across computing resources. By building services you can isolate domain-specific problems into a codebase that is much more compact and simpler to grasp. This is nothing new; the Linux kernel has been doing this for years, as have innumerable software projects across all types of business domains. The networking stack is independent of the audio stack in the Linux kernel, and rightly so – these two subsystems share very little in terms of overall concerns. Certainly they need some way of managing system resources and the ability to talk to hardware, but the similarities end there.
In a similar fashion building modular software is about separating concerns within your higher-level application. Code written to solve the problem of user-authentication and management is orthogonal to, say, logic pertaining to storing a shopping cart of items and do not need to be in the same place. Why then, should someone interrupt the operation of purchases to make an update to the storage mechanism for authentication data?
The problem with services
Services, on the other hand, bring about the problem of code in many different places. It’s hard for a small development team to keep track of which service does what. This is why monolithic application frameworks have gained so much traction over the years. Monolithic applications empower small development teams to build features and work together in a way that a service-oriented architecture does not.
As a small team, building services can therefore be an impediment to development and are possibly not a good solution for these types of teams. That does not mean that a small team cannot build a monolithic application that has been divided into modular concerns that can eventually be abstracted away. It is entirely possible to build a monolithic system that is forward-looking enough to permit teams to break out portions of the application into independently-deployable and managed components.