What is good service object?

  • Single responsibility
  • Checks it’s input
  • Command/query separation
  • Same level of abstraction
  • Transparent
  • Tested

Example of good service object

Let’s start by giving a simple example:

class UserCreator
  attr_reader :email, :first_name, :last_name
  attr_reader :created_user

  def initialize(email, first_name, last_name)
    @email      = email
    @first_name = first_name
    @last_name  = last_name

    raise ArgumentError, "Missing email" if email.nil?

  def create_user

  def user_created?


  def check_user_existance
    raise "User already exists" if User.exists?(...)

  def save_user
    @created_user = User.create(...)

Single responsibility

As we see above, UserCreator is single responsible for creating user. No email sending or whatever. If you need, for example, to send welcome email it is probably should be done from another service, say WelcomeEmailSender. The smaller construction blocks in your program the less pain it would be to change them.

Checks it’s input

It’s simple, don’t check email address for being nil outside of the service. This will spread such checks all across the codebase. Object constructor is the one who’s responsible for setting object invariant - state in which object can perform without braking. If your service needs email address to work correctly and it is not given - brake early, throw exception from constructor.

Command/query separation

Usually objects have two types of methods: commands and queries. Commands change something and return nothing, queries in opposite return current state without changing anything. This separation brings clarity in how and when you use particular method, so you don’t endup in situation when asking object for it’s state also changes it or sends email.

Same level of abstraction

Term abstraction is about hiding details. In #create_user we hide checking user existence and saving user details inside #check_user_existance and #save_user methods. We’d brake this rule if some details are still left inside #create_user method.


In other words you should be able to inspect input parameters, which usually got supplied through constructor, hense you should have attribute readers for every input parameter. This especially handy upon debugging.


According to Sandi Metz, you should test object query methods by asserting returned result, command methods - by asserting direct public side effect and outgoing command methods, by expecting to send them. Usually, I would test such service as so:

describe UserCreator do
  describe "#create_user" do
    it "creates user" do
      user_creator = UserCreator.new("ian@brown.com", "Ian", "Brown")

      expect(user_creator.created_user.email).to      eq("ian@brown.com")
      expect(user_creator.created_user.first_name).to eq("Ian")
      expect(user_creator.created_user.last_name).to  eq("Brown")

    context "no email given" do
      it "raises exception" do
        expect {
          UserCreator.new(nil, "Ian", "Brown")
        }.to raise_error(/Missing email/)

    context "user already exists" do

The anatomy

Trying to visualize service object I came out with this picture:

Service anatomy picture

Further reading