Splitting out Microservices

I've always valued simplicity in all the code I write, and in every bit of infrastructure I maintain. As such I've always favoured monolithic software architecture. Another factor in this, is that I'm mostly coding in very small teams (very often as the only developer), so splitting out Microservices brings no organisational advantage either.

However, recently I decided to split out two microservices from our monolith, and I thought I'd write a quick note on why and how. What factors influenced me to split these out, when as a single developer at my company this would definitely lead to increased maintenance burden and reduced simplicity (if only slightly)?

This post was originally a Mastodon post, but I wanted to give it some more context.

Logical separation

The first factor was that these two services are already logically quite separate from the rest of the app. To use one metric: Our MySQL database currently has about 300 tables. Between them, these two services only need data from 3 of these. And for the most part, they simply need to read that data, further simplifying the data side of things.

Different usage patterns

While our app overall scales quite predictably as we onboard more clients, these two aspects scale independently of client onboarding: If one of our client makes a change - which they'll often do without warning -, we can see usage increase 10-fold or more in a matter of minutes (and occasionally it can drop just as quickly).

Now, we could provision spare servers on standby to jump in when needed (and in fact that's effectively what we used to do until now), but that would obviously be wasteful. Or we could use automation to automatically spin up copies of our app servers as usage increases. But that's not trivial, and based on our calculation that would be significantly more expensive than the solution we ultimately arrived at.

Different uptime requirements

This is an interesting one and probably quite unique to our situation: Whilst I had planned to do this split for some time (to better deal with usage spikes), the ultimate trigger of the change was that our hosting provider gave us notice of a maintenance window lasting a few hours. For the most part, this wouldn't be a huge problem for us, as our app is largely 'hands-off' and we don't even necessarily expect our clients to interact with it on a daily basis. But these two parts of the app needed to stay up and running, as otherwise both us and our clients would loose significant confidence and revenue. So, if nothing else, we needed these two services to remain up and running even when our main app servers are down.

Our solution

To set the scene, here is a brief overview over our current infrastructure: Our app is written in Laravel, and running on a small number of servers that sit behind CloudFlare, both for DDOS protection, and some of the other stuff we can do on their edge servers.

In order to split out our microservices, we wrote a couple of CloudFlare Workers. We use Laravel's Events to sync the data we need from the main app into CloudFlare's Key/Value store, so that the Worker functions can access it.

And for any data that we need to write into our app's datastore, we push the info needed into Amazon's Simple Queue Service (SQS) so that we can then process it through Laravel's Queue system.

As such we have almost unlimited and immediate scalability, as well as continued uptime even when our own servers are down on these two services. Any writes that would occur during downtime get pushed into a queue, and simply get picked up when the servers are back up and running. Additionally the system is overall actually still quite simple, which is a huge plus.

Incidentally it was also my first foray into writing TypeScript, which - without wanting to cause too much controversy - I actually quite like: Strict typing is great because it hugely reduces the chance for mistakes and provides great auto completion in my IDE.

Closing thoughts

So this is how and why we've recently split out two parts of our app into their own microservices. Clearly these things work for us, given our specific requirements and history. They may or may not be applicable to you.

Do I regret not writing the system with a microservices infrastructure from the outset? Absolutely not! Having a monolithic app simplified and sped up our development hugely, which enabled us to be where we are today. And ultimately, due to good practices and separation of concerns from the outset, spinning out these two microservices was actually much less work than I first thought it would be.

So, my advice to myself for the future will continue to be: Start your app as a monolith. Follow good practices with regard to separation of concern. And don't be afraid of microservices, and transitioning to them if and when it makes sense.