Notes on writing Service Objects
Tags: ruby, serviceobjects, codestyle
Published: 2018-07-04 06:45:41

---
title: Notes on writing Service Objects
published: true
description: 
tags: ruby, service objects, code style
canonical_url: http://katafrakt.me/2018/07/04/writing-service-objects/
---

Service Objects are probably a single most popular technique for refactoring Ruby applications. However, there is one little with them: there are (too) many ways to write them. And because different people use service objects different way, they tend to get messy too.

I wrote dozens of SOs since I joined Boostcom 1.5 year ago. This is mostly because we don't really use Rails, so they are even more natural. We never established strict rules to follow while writing services, however, they all tend to look similar. It would seem that the way we write them works for us, so I'd like to share **a few tips about what I think service objects should look like**.

Here is a list as bullet points, with longer explanation coming later in the post:

* Only one public method
* Use `.new` explicitly
* Only use instance variables in a constructor
* Don't overuse private methods (stay flat)
* Don't be afraid of local variables
* Reuse service instances in heavy loops
* Bonus: Return value objects
* Bonus level hard: Use monads
* Break the rules

## Only one public method

Since classes generally should follow Single Responsible Principle, we should apply it to service objects too. One responsible means only one public method – and one way to use the service.

I usually call it simply `call`. This has a few benefits:

* You can use Ruby magical shortcut syntax `service.(arg)`. This might not be a killer feature, but sometimes is nice to have
* If method's name is `call`, you need to give a proper name to a service, which is good
* "Calling" services seems natural

I know other people who have completely different opinion on this subject and say you should **never** call your method `call`. This is, however, not really important, as long as there is only one method.

## Use `.new` explicitly

I know many people like writing a shortcut like this:

```ruby
class MyService
  def self.call(*args)
    new(*args)
  end

  def call(args)
    # do something
  end
end
```

I admit: there is a certain appeal to writing `MyService.call(1,2,3)` instead of longer and seemingly redundant `MyService.new.call(1,2,3)`. It's certainly shorter and you usually don't reuse service objects instances anyway. I actually don't think it's the best way.

On the other side lies this counterproposal: `MyService.new(my_secret_data).call`. I must say I use it sometimes, but I also think this is not perfect. Why? By passing the data to initializer and assigning to an instance variable, you are later tempted to reuse the service with a different method (and same data). This breaks the previous rule about only one public method. For example:

```ruby
service = MyService.new(data)
service.remove_dulicates!
filtered = service.filter_invalid
service.send_valid_to_api(filtered)
```

I wouldn't dare say that this is bad _per se_ but it allows your service objects to grow uncontrollably. If you have self-discipline to avoid that, this might be your way. But I think there are other possibility worth exploring.

At some point in your service-objects-writing quest, you will probably discover dependency injection. And you will want to rewrite the services like that:

```ruby
# before
class MyService
  def call(data)
    filtered = FilteringService.new.call(data)
    ApiSender.new.call(filtered) # where are my Elixir pipes? :(
  end
end

# after
class MyService
  def initialize(filterer: FilteringService.new, api_sender: ApiSender.new)
    @filterer = filtering
    @api_sender = api_sender
  end

  def call(data)
    filtered = filterer.call(data)
    api_sender.call(filtered) # where are my Elixir pipes? :(
  end

  private
  attr_reader :filterer, :api_sender
end
```

Why? For example for testing `MyService` without needing to stub both services it depends on. But that's a whole different story... Anyway, by leaving constructor unused at first, it is really easy to put dependency injecting code there later. That's why I think that `MyService.new.call` is a way to go if you don't have any other strong opinion.

## Only use instance variables in a constructor

As seen in the example above, instance variables are only used in the constructor. This is a way it should be, in my opinion. If you use them in any other place, user `attr_reader` and **avoid mutating those variables at any cost**.

There might be a question whether those getters should be private or not. I think they should, but it's not really a big deal.

One more hint: **use [dry-initializer](http://dry-rb.org/gems/dry-initializer/)** which does the job for you.

```ruby
require 'dry-initializer'

class MyService
  extend Dry::Initializer

  param :filterer, default: proc { FilteringService.new }
  param :api_sender, default: proc { ApiSender.new }

  def call(data)
    filtered = filterer.call(data)
    api_sender.call(filtered)
  end
end
```

## Don't overuse private methods

If your service is getting complicated (and sometimes it has to), you will add more and more private methods to keep things short and Rubocop happy. In time, the code will become hard to read. Look at this example:

```ruby
class BadService
  def call(data)
    send_filtered_data_to_api(data)
    notify_subscribers(data)
  end

  private

  def notify_subscribers(data)
    SubscribersNotifier.new.call(data)
  end

  def send_filtered_data_to_api(data)
    api_sender.call(filtered(data))
  end

  def filtered(data)
    filterer.call(data)
  end

  def api_sender
    @api_sender ||= ApiSender.new
  end

  def filterer
    @filtering_service ||= FilteringService.new
  end
```

How many jumps you had to do in order to read what this code does? The answer is: a lot `call` -> `send_filtered_data_to_api` -> `api_sender` -> `send_filtered_data_to_api` -> `filtered` -> `filterer` -> `filtered` -> `send_filtered_data_to_api` -> `call` -> `notify_subscribers` -> `call`. Or using a different notation:

```
call
  send_filtered_data_to_api
    api_sender
    filtered
      filterer
  notify_subscribers
```

As you see, we have four levels of calls here. I think it only should be two of them. That's why I also sometimes call this rule "**stay flat**".

In my opinion, a perfect way is to **only use private methods in `call`**. It means both: nothing else than private methods in `call` and only `call` can use private methods. This way you keep the entry method as a kind of high-level description of what service does. Of course, private methods should be defined in the same order that they are called, so you don't need to refer to what call does when you read them.

This way even services that do a lot can stay simple to read:

```ruby
class RequestHandler
  def call(params)
    valid_params = filter_valid_params(params)
    save_to_database(valid_params)
    notify_subscribers(valid_params)
    log_params(params)
    send_measures(params)
  end

  private

  # [...]
end
```

Sidenote: if your private methods start to do too much and be too long, you probably need to introduce another service.

## Don't be afraid of local variables

Earlier this year I've read about a thing called _[The Local Variable Aversion Antipattern](http://www.soulcutter.com/articles/local-variable-aversion-antipattern.html)_ and I started to notice it a lot in Ruby code.

Basically, **you don't need to initialize everything in a separate method**. Also, there's no need to memoize everything – usually you use it only once anyway. So there is nothing better in this:

```ruby
def send_to_api(data)
  api_sender.call(data)
end

def api_sender
  @api_sender ||= ApiSender.new
end
```

Than this:

```ruby
def send_to_api(data)
  sender = ApiSender.new
  sender.call(data)
end
```

The latter is actually easier to follow and does not break the rule about setting instance variable only in initializers.

## Reuse service instances in heavy loops

This is probably pretty obvious, but we talk very little about performance when it comes to high-level Ruby code, so I dare to say it aloud.

```ruby

# DON'T

large_array.each do |item|
  ItemProcessor.new.call(item)
end

# DO

processor = ItemProcessor.new
large_array.each do |item|
  processor.call(item)
end
```

Ruby objects are relatively cheap, but they are not free. If you can avoid creating unnecessary objects, you'll benefit from it at some point.

## Bonus: Return value objects

It is a good habit to return service objects when you do something more complicated than just streamlining data. At some point you will like to know more about what happened in high-level service objects and you will start to create little monsters like this:

```ruby
class MyHighLevelService
  def call(data)
    pg_result = save_to_postgresql(data)
    kafka_result = send_to_kafka(data)
    worker_ids = schedule_workers(data)
    [pg_result, kafka_result, worker_ids]
  end
end

# ...

pg_result, kafka_result, worker_ids = MyHighLevelService.new.call(data)
```

Value objects are much better for it.

## Bonus level hard: Use monads

I'm not going to say much more, as this is more of a point on my checklist to start doing. But sometimes what [dry-monads](https://dry-rb.org/gems/dry-monads/) offer will very easily fit your flow of data. But don't try to squeeze them everywhere just because "monads are cool". As Piotr Solnica once said:

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">One more thing re monads - I totally recommend using dry-monads in places where it’s useful, like composing operations with nice result handling etc. Something that would be “too much”, is using monads consistently everywhere, for everything.</p>&mdash; Piotr Solnica (@_solnic_) <a href="https://twitter.com/_solnic_/status/1011539445806456832?ref_src=twsrc%5Etfw">June 26, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## Last but not least: Break the rules if necessary

This is just a set of suggestions. I think they are good and cohesive, but they are suggestions anyway. Even I don't always follow them. Sometimes there are good reasons not to. For example, when all other things (for example coming from external gems you have no control over) are using `Blah.call(1)`, maybe your services should use that notation too? Otherwise you will find yourself wondering everytime what to write.

Don't start changing everything in your code just because some dude on the Internet says he does it otherwise. Make conscious decisions, try to adhere to them, but give yourself permission to sometimes stray away. Context is everything, after all, and yours can be very different than mine.

<hr />
This has been also posted [on my blog](http://katafrakt.me/2018/07/04/writing-service-objects/).
<hr />