Sign up for our $0 plan

Rails apps come in many shapes and sizes. On the one hand, you have large monolithic applications where the entire application (admin, API, front-end, and all the work it needs to perform) is in a single place. On the other end of the spectrum, you have a series of microservices, all communicating with each other with the goal of breaking up the work into more manageable chunks.

This use of microservices is what’s called Service Oriented Architecture (SOA). Rails apps from what I have seen tend to lean toward being monolithic, but there is nothing stopping a developer from having multiple Rails apps which work together and/or connecting to services written in other languages or frameworks to help them accomplish the task at hand.

Monoliths don’t need to be written poorly, but poorly written monoliths broken up into microservices will most likely end up being poorly written as well. There are several ways you can write your app which will not only help you write cleaner (and more easily testable) code, but will also help you if/when the need arises to break up the application.

Our Use Case for a Rails App with Microservices

For this article, we’ll be talking about building a website that is a CMS. Imagine any large newspaper or blog that has multiple writers contributing articles with users that can subscribe to receive alerts about certain topics.

CMS Editor: Used by writers and editors to create, edit, and publish articles.

Public Website: Used by the public to view published articles.

Notifier: Used to notify subscribers of new published articles.

Subscribers: Used to manage user accounts and subscriptions.

Should I Build My Rails App as an SOA?

So how do you decide if it makes more sense to build your Rails app as a monolith or to build it using microservices? There is no right or wrong answer, but asking yourself the following questions may help you decide.

How are my teams organized?

Deciding to go with an SOA often has very little to do with technical reasons but rather how the different development teams are organized.

It may make more sense for four teams to each take one of the major components to work in a silo than to have everyone working on a single system. If you’re working on a team of just a few developers, deciding to start right from the beginning with microservices may actually decrease development speed by adding the increased flexibility of having four different systems communicate with one another (and deploy).

Do different components scale differently?

There’s a good chance that in our use-case system in this article, the Public Website will have a lot more traffic to it than the CMS Editor that the writers and editors will be using.

By building them as separate systems, we can scale them independently and/or apply different caching techniques for the different parts of the system. You can still scale the system as a monolith, but you would be scaling the entire thing at once, rather than the different components individually.

Do different components use different technologies?

You may want to build your CMS Editor as a Single Page Application (SPA) with React or Angular and have the main public facing website be more of a traditional server-rendered Rails app (for SEO purposes). Maybe the Notifier would be better suited as an Elixir app due to the language’s concurrency and parallelism support.

By having them as different systems, you’re free to choose the best language for the job in each of the services.

Defining Boundaries

The most important part in all of this is that there are well-defined boundaries between the different components of the system.

One part of the system should think of itself as the Client communicating to an external Server. It doesn’t matter if the communication happens through method calls or over HTTP, it just knows that it has to talk to another part of the system.

One way we can do this is through defining clear boundaries.

Let’s say that when an article is published two things need to happen:

First that we send the published version of the article to the Public Website, which will return to us a URL that it is published at.

Second that we send the newly created public URL, the topic, and the title to the Notifier which will handle notifying all interested subscribers. This second task can be done asynchronously because it may take a while to notify everyone, and there isn’t really a response that we need back.

For example, take the following code which publishes an article. The article doesn’t really know whether the service being called is simply a method call or if it is an HTTP call.

Service Communication

Services need to be able to communicate with each other. This is something as Ruby developers we’re already familiar with even if we haven’t built microservices before.

When you “call” the method of an Object, what you’re really doing is sending a message to it, as can be seen by changing Time.now to Time.send(:now). Whether the message is sent via a method call or whether the communication happens over HTTP, the idea is the same. We’re wanting to send a message to another part of the system, and often we want a response back.

Communication over HTTP

Communicating over HTTP is a great way to send messages to a service when you need an instant response — when you’re relying on receiving a piece of data back to continue program execution.

Its job is to build the data that will be posted to the service and handle its response. It also verifies that the response is a success and will raise an exception otherwise. More on that later.

I’ve used the constant Cms::PUBLIC_WEBSITE_URL, which gets its value through an initializer. This allows us to configure it using ENV variables for the different environments we end up deploying our app to.

Testing our services

Now it’s time to test that our PublisherService class works correctly.

For this I don’t recommend making an actual HTTP call; that would slow down testing and require that you have the service up and running at all times on your development machine (or continuous integration server). For this we can use the WebMock gem to intercept the HTTP call and get the required response.

Planning for failure

There will inevitably come a time when the service is down or times out, and we need to handle that error.

It’s really up to the developer to decide what needs to be done when the service is down. Can the website continue to work when it’s down? In the case of our CMS application, it can work fine for creating and editing articles, but we’ll have to show an error to the user when they attempt to publish an article.

As part of the test above, there is a case that looks for a 500 response from the server and checks to ensure it raises the PublisherService::ServiceResponseError exception. This error comes from its parent class, HttpService, which for now doesn’t contain much other than the error classes.

In another article by Martin Fowler, he talks about a pattern called the CircuitBreaker, whose job it is to detect when a service is misbehaving and to avoid making a call that would most likely end up in an error anyway.

Another technique that can be used to make our application more resilient is to make it aware of the status of the different services that it needs to function.

This could be controlled manually by developers, giving them the ability to turn off certain website functionality when a service is known to be down and avoiding errors altogether. However, it could also be controlled in conjunction with some sort of CircuitBreaker pattern programmatically.

Before showing the Publish button to your users, a quick check to PublisherService.available? will help avoid errors.

Communication with queues

HTTP isn’t the only way to communicate to other services. Queues are a great way to pass asynchronous messages back and forth between the various services. The perfect situation for this is when you have some work that you want done but you don’t need a direct response back from the receiver of this message (sending emails for example).

In our CMS app, after an article is published, any subscribers to that article’s topic are notified (either through email, a website alert, or push notification) that there is a new article that might be of interest to them. Our app doesn’t need a response back from the Notifier service, it just needs to send a message to it and let it do its work.

Using Rails queues

ActiveJob expects the receiving code to be running inside of a Rails environment, but it is a great option that is very easy to get up and running.

Using RabbitMQ

RabbitMQ on the other hand is Rails (and Ruby) agnostic and can act as a generic messaging system between your various services. It is possible to use RabbitMQ as a way to handle Remote Procedure Calls (RPC), but it is much more common to use RabbitMQ as a means to send asynchronous messages to other services. They have a great tutorial for getting started in Ruby.

Below is a class built for sending a message to the Notifier service about a newly published article.

Conclusion

Microservices aren’t something to be afraid of, but they should also be approached with caution. It is in fact possible to have too much of a good thing. My advice is to start with a simple system with well defined boundaries, allowing you to easily extract services as required.

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles. Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.

We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

Николай

Thanks for the article!

Some questions:

– How to manage a team and how do you allocate developers responsibility? – How many deployers should respond for each service? – Do you write API documentation for each microservice? – What a git hosting do you use? – How you understand should the next task be a new microservice or a part of exsisting one? – Which tools do you use for microservice deployment?

Interesting. I would like to use this design in a new project of mine and could use a little advice/consultation. If you (or anyone reading this) might be interested in exploring the possibility of advising or consulting, please get in touch. Thks ;)

Here’s a little tool to help you formalise the messaging in your microservices infrastructure a bit. It lets you write specifications of what each service publishes and consumes in human readable markdown. The cool thing about is that you can express that service X only needs a couple of properties of objects Y published by service Z and that you can test each service against the whole infrastructure without having to check out the other services repositories. This also works in CI.

> The most important part in all of this is that there are well-defined boundaries between the different components of the system.

That is about the most important thing for any of this. SOA principles should be interpreted abstractly and should be decoupled from the actual tech. Most of your important “services” just match your business departments. This allows you to build even your monolithic app with SOA principles. For example, if you are in the early stages of scaling a product app, it’s probably less risky to have one monolithic app than having microservices, but you can still use SOA principles. You can understand that a collection of classes in your app represent your Shipping Service (shipping department) and another collection of classes represent your Pricing/Inventory service (pricing department). Even if you can’t just copy and paste code into microservice once you move into a more mature scaling phase of your product that demands more modularization (sometimes legacy code makes this difficult), it’s still easier to think about the concepts and where changes need to happen if you have been thinking “SOA”.

This is an approach we have taken that has allowed us to start with on monolithic Rails web app and move to two Rails apps used as web apps and one Rails app used as a service endpoint all with a 4-dev team. All the apps communicate with each other through carefully defined business events or “messages”. For the tech, we use RabbitMQ (with https://github.com/jondot/sneakers) for publishing the messages between the apps and Redis (with https://github.com/mperham/sidekiq) for processing the messages within each app. We have created our own gem to glue all these pieces together and normalize the publishing semantics: https://github.com/giftcardzen/la_gear.

Funny because I am ripping apart the https://devschool.rocks web app right now and splitting it up. It is amazing how much just the deployment gets sped up with there isn’t 50 native gems to build with every small update to the site!