Blog

A nifty service layer for your Rails app

If you have been in the Rails world for a while, you have undoubtedly heard about the discussion around whether, as a Rails app grows in size, it needs an additional "service layer" that contains all the business logic. There have been many blog posts about this topic already so I won't repeat the pros and cons here.

Not sure if I need a service layer or just put it all in /lib

I for one started using services as an experiment in a couple of Rails apps I am working on/maintaining in early 2013 and must say, it has made working on these apps enormously more pleasant! Everything from reasoning about the way the apps work to finding bugs or implementing new features has become much more enjoyable!

Boil all the code!

I noticed that over time a certain amount of boilerplate code appeared that was pretty much the same in every app. So, naturally, I pulled this code out of the app and into a central place (a gem) so I could test it, fix bugs, and improve the stability and performance for all apps using it.

After more than a year, I now feel comfortable sharing this code with the world. It is still very much customized for my own apps (e.g., Sidekiq is used for background processing and Postgres as the db) but it should be fairly easy for contributors to jump in and make different parts more agnostic.

Here is the Github repo!

A minimal framework for writing services

So how does it work? Let's look at an example:

module Services
  module Users
    class Delete < Services::Base
      def call(ids_or_objects)
        users = find_objects(ids_or_objects)
        users.each do |user|
          user.destroy
          Mailer.user_deleted(user).deliver
        end
        users
      end
    end
  end
end

This is a service that deletes one or more users. It's flexible: you can pass in a single user object, or a array of multiple user objects, or a single user ID, or - you guessed it - a array of user IDs. The only "magic" going on here is the find_objects method but I think you can guess what it does. This method and a couple more are defined in Services::Base from which your services should inherit.

Basic principles

There are a couple of basic principles of what a service should be and do in your Rails app:

  • a service does one thing well (Unix philosophy)
  • a service can be run synchronously (in the foreground) or asynchronously (in the background)
  • a service can be unique, meaning only one instance of it should be run at a time
  • a service logs all the things (start time, end time, caller, exceptions etc.)
  • a service has its own exception class and all exceptions that it may raise must be of that class or a subclass
  • a service can be called with one or multiple objects or one or multiple object IDs

Apart from these basic principles, you can implement the actual logic in a service any way you want.

Get involved!

Head over to the Github repo and have a look at the README, it describes all parts of the gem in much more detail.

Please try out the gem and let me know what you think!

Discuss this post on Hacker News

Ideas? Constructive criticism? Think I'm stupid? Let me know in the comments!