Shoprocket's Journey to Mircoservices: Part 1

A few months into my job at Shoprocket and I was getting frustrated. We had a PHP application being worked on by our small team of bright developers, spread all over the globe. Why was I getting frustrated? To me, it seemed bug fixes spawned more bugs, integrating new features wasn't as simple as it could be, and deployments were infrequent and stressful. The reason for these problems: highly coupled code.

We had a monolithic application, and almost all of our codebase sat in one Github repository. Updates or fixes to one feature would often occur in several places. For testing, we had a peer review process, where at least two other developers had to sign off on a branch before it would be merged into master and deployed. All of this meant that when bugs managed to slip in under the radar, it was often hard to locate the breaking changes. And once we did, the fixes would undergo the same process and the whole application had to be redeployed.

Being an eager young developer always looking for the new thing, when I came across microservices I thought this architectural style would solve all of our problems. There isn't a formal definition of microservices, but from my research I took away that microservice architecture is a way of designing software applications as distributed systems of independent services, each highly specialised and with its own lifecycle. For readers who want a more detailed introduction to the concept, I recommend Martin Fowler's excellent article.

There were many aspects of microservices that I was interested in, but two in particular: Firstly, the idea of splitting the application into services with very strict boundaries. This lowers the chance of logic specific to one business context from leaking between components because the interfaces between boundaries have to be carefully planned out. It's not as simple as making local function calls; Secondly, each component can have its own independent lifecycle. This allows teams to take ownership of their code and use a technology stack best suited to the individual service. Also, it permits more fine-grained control over the application as changes can be deployed to one service at a time, and rolled back without affecting the rest of the application if something goes wrong.

So, I was sold. Shoprocket would break up the monolith! This is the first in a short series of blog posts following this journey. My hope is that readers planning or going through a similar transition will learn from our successes and failures and feel better prepared to take on this challenge in their own projects.

My first challenge wasn't technical, but human. I had to sell the idea to the rest of the team. They seemed curious, but didn't share the same enthusiasm and were reluctant to give up dev hours at this early stage of our product's life. Luckily, the CTO wanted me to look into our session management and I seized the opportunity by convincing him I could split off a session service.

In retrospect, this was perhaps a poor choice of component for a first service. Our product includes a javascript plugin that injects eCommerce functionality into any site. We treat sessions as a resource, persisting session information to the database for detailed analysis of KPIs like cart abandonment rates. However, this resource isn't as tangible as something like orders or line items. Furthermore, we were using the session resource with almost every change in application state. This meant that the service would be inevitably chatty with the rest of the application, which I didn't foresee, but made my task difficult.

Having convinced my boss, I ramped up my research on microservices. Aside from the aforementioned article, I also found Sam Newman's 'Building Microservices' to be an invaluable resource. I'll refer back to them both often in my posts.

In his book, Newman recommends first restructuring an existing application into modules that reflect the domain's bounded contexts. Then, once the developers are happy, modules can be split off into their own services. The reason for this is that it's much easier to break down and rewrite modules if their boundaries were badly chosen than it is to break down and rewrite entire services, with all their extra plumbing code and persistent resources. However, due to time constraints and my eagerness to build a component of my own, I went ahead with the first service.

I chose a stack only marginally different to the one we were using for our monolith and started building. The build itself proceeded rather quickly, taking a few weeks to produce a standalone application with a REST API. Only then did the real challenges start.

I had to get the service to talk with the monolith. It was at this point that I learned how tightly coupled session management was with the rest of the code. It to me a while to break down existing function calls and translate them into API calls. This was tricky. As the session service and the monolith had their own database schemas, I had to rewrite any logic in the monolith that used session resources from the old database, restructuring the data at the application boundary to maintain compatibility with clients such as our javascript plugin.

Integrating the service with our existing application took longer than it did to write the service itself, and this didn't include a lot of the extra overhead that microservices normally bring, such as service discovery, logging and monitoring. Furthermore, due to tight coupling, changes were made to many areas of the code. As the team had been working on various areas of the code in parallel during this time, it would have required a lot more work to merge the changes and get the session service into production.

The service might soon go into production, however, I now feel that I should have followed Newman's advice and put my efforts into modularising our existing application. The benefit of having one service living on its own is marginal. The real benefit will come from splitting up the whole thing, at least into coarse-grained services, that chat as little as possible. This requires very well thought-out component boundaries, and we're not quite there yet.

I'm still sold on the idea of microservices, and I see my attempt at splitting off a service as a successful experiment. I learned a great deal about application architecture and domain modelling in the process, and improved my understanding of REST principles, among other things. These have had an immediate knock-on effect in other areas of our code.

For readers looking to convert, I would be sure to read up as much as possible on the subject, and put off the work until you are very confident with your existing codebase and your domain model. There are other ways to introduce stability and resilience into a monolithic application. Test driven development and automated unit and functional tests help a great deal. Horizontal scaling helps make deployments a little smoother too.

Shoprocket will continue on its journey to microservices. We look forward to telling you more in part 2!