/ architecture

How Microservices Saved My Small SaaS Business

I've been reading a lot about microservices lately and how they're hard to implement. For a long time I was also very much against the architectural complexity they bring. Until I realized that for a single developer maintaining a monolith, microservices gave me something no other architecture could.

Problems Maintaining a Monolith

My framework of choice is Rails with React and Redux for the client. This is a great choice when you consider how fast you can implement new features and how easy it is to write and run system tests (capybara is now integrated with rails). It makes my code stable and my clients happy.

But as I tried to scale up and add more developers to help with maintaining and improving the code I hit a wall.

Turns out it's really hard for most developers to get into someone else's code. It's even harder when that code is written in a technology that not many people use (Rails market share here in Israel is very low). But the final nail in this coffin was developer's salaries. I'm not able to compete with all the cool startups or large companies in this area, and EVERYONE seems to be hiring React developers these days.

In the budget limits I worked with it was just not possible to recruit top talents and give them 2-3 months to learn the code before they can start being productive.

So I Tried Working With Freelancers

And failed. Miserably. Because as hard as it gets for employees to learn your code, it'll be ten times as hard for freelances. They're working on dozens of other projects and they know they only get paid when the job's done. So when they realize your job is a lot harder than the next guy's, they just disappear.

And now you're stuck with all your code on a freelancer's machine, after you spent hours explaining how the code works and what modification they need to make. And the freelancer's not responding.

Over time I would try to minimise those risks: I'd bring a freelancer to work in my office on my machines, or only give them parts of the code. Eventually I gave up. Costs were rising and results were poor.

Microservices to the rescue

Then one day a friend suggested to help me with a feature, but he wanted to implement it in PHP. I decided to give it a chance and set up nginx so "his" page would be served with PHP. I also gave him a separate DB schema and a simple API by building a dedicated Rails controller.

The new nginx location block looked something like this:

    location ~ ^/php/.*[^/]\.php(/|$) {
        alias /home/service1/www/;
        include fastcgi.conf;
        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
        fastcgi_param HTTP_PROXY "";
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
    }

Now every user that navigates to a php file from /php folder would end up on his service. As both the service and the monolith run on the same box AND the same domain, browsers would send the same session cookies.

This allowed a very simple authentication scheme: The PHP sends the cookies it got to a dedicated Rails controller, and the rails would tell it if the action is allowed. Here's a simplified version of the relevant Rails code:

  def can
    action = params[:act].to_sym
    resource = find_resource(params)
    authorize! action, resource
    render json: { userid: current_user.id }
    
  rescue CanCan::AccessDenied
    head :unauthorized
  end

The implementation of find_resource is a long case block and not very interesting here.

One Year Later

A year after that first attempt and I now have a system for working with freelancers that is risk-free and easy for both sides.

I can write detailed spec for tasks using a well defined API. I usually have the freelancer implement both JavaScript API and backend in whatever languages they prefer.

Integration between the services and the monolith is still a thing, but it's not that bad. Whenever possible we'll use client side code to manage transaction logic. For async tasks we'll use RabbitMQ, and for the rare cases that a true transaction is needed we'll build a dedicated backend API.

The main issue I still have with this method is maintenance. As time passes and freelancers finishes their work, I'm left with maintaining their code (which is not always easy). However by requiring a complete test suite that risk can also be reduced.

Microservices, with all their complexities and difficulties, were the simplest method for me to work with other people without the extra costs of hiring and without the risks of disappearing freelancers. I highly recommend trying this architecture, especially for sole developers.