Service Objects in Rails allows to neatly separate the business logic of your application in reausable components. This post describes our implementation of this pattern.
We went through the usual story, we started by putting some business logic in controllers and some in models, then decided that all this logic should go in the models and ended up with very fat models tightly coupled to each other.
So when looking for alternatives patterns for organising the business logic I came across the idea of having separated objects to handle this business logic. Somewhere I saw this pattern labeled as ‘Service objects’ (SO). This was way before this very interesting post 7 Patterns to Refactor Fat ActiveRecord Models
The discussions often involved the Single Responsibility Principle (SLP), so most of the examples shown a class with only one public method on it. At first I totally dismissed this as a kind of functional pattern that didn’t fit into the OOP world of Ruby.
But my pain with the fat models made me look into this again. So I decided to give it a try. Since then I have grown very fond of this approach because of the following:
- As this objects have little code they are easy to reason about
- They are very easy to compose (use one inside the other)
- They encapsulate the business logic neatly, so you never has to repeat the same logic in different places
- They use dependency injection (DI) heavily so they are loosely couple with the rest of the application
- Using DI makes it very easy to swap the dependencies in testing
- But still they have sensible dependency defaults, I don’t see the point in injecting dependencies all the time when in 90% of the cases you just need the defaults
Let me show the pattern we are using:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
The key points in our version are:
- the class has only one public method (call)
- dependencies are only passed if needed, the class has some sensible defaults that will be used 90% of the time
- each dependency injector has its own method instead of a attr_accessor, this is so you can prepare the dependencies if needed
This has been a great pattern for us, we have hundreds of these objects that can be easily composed as needed. This pattern has made our code seriously easier to work with.