What service objects are
Service objects and Interactors are fashionable among Rails developers today. I can’t speak for everyone’s motives but the impression I get is that service objects are seen as a way to avoid the “fat models” you’re left with if you follow the “skinny controllers, fat models” guideline. If your models are too fat you can move some of their bulk to some service objects to thin them out.
Unfortunately it’s not quite possible to say what service objects are because the term “service object” means different things to different people. To some developers, “service object” is just a synonym for plain old Ruby object (PORO). To other developers, a service object is more or less an implementation of the command pattern. Interactors are similarly close to the command pattern.
For the purposes of this post we’ll go with the “command pattern” conception of a service object: a Ruby class with a single method,
call, which carries out a sequence of steps.
I have some gripes with the way service objects are advocated in the Ruby community. I’ll explain why.
Pattern vs. paradigm
The command pattern has its time and place. Sometimes it makes plenty of sense to bundle up an action into a single object and let that object orchestrate the steps that need to happen for that action.
But the command pattern is a pattern, not a paradigm. A software design pattern is a specific solution meant to meet a specific problem. A programming paradigm (like object-oriented programming or functional programming) is a style that can be applied at the level of entire applications.
What pains me a little bit to see is that many developers seem to have confused the idea of a pattern with the idea of a paradigm. They see service objects or Interactors and think, “This is how we’ll code now!” They have a service-object-shaped hammer and now everything is a nail.
Imperative vs. declarative
An imperative programming style expresses code mainly in terms of what to do. A declarative programming style expresses code mainly in terms of what things are.
For those not very familiar with imperative vs. declarative, here’s an explanation by way of example.
An imperative sandwich
If I were to tell a robot to make me a peanut butter and jelly sandwich using the imperative style, I would say, “Hey robot, get two pieces of bread, put some peanut butter on one, put some jelly on the other, put them together, put it on a plate, and give it to me.”
A declarative sandwich
If I were to do the same thing in a declarative style, I would say, “A sandwich is a food between two pieces of bread. It comes on a plate. A peanut butter and jelly sandwich is a sandwich that contains peanut butter and jelly. Make me one.”
My preference for declarative
I’m strongly of the view that declarative code is almost always easier to understand than imperative code. I think for the human brain it’s much easier to comprehend a static thing than a fleeting action.
At some point the things do have to take action (the peanut butter and jelly sandwich has to get made) but we can save the action part for the very highest level, the tip of the pyramid. Everything below can be declarative.
Service objects are all imperative
Service objects and Interactors are basically all imperative. Their names tend to be verbs, like
AuthenticateUser. Their guts tend to be imperative as well. To me this is a pity because, like I said above, I think most things are more clearly expressed in a declarative style. Even things that seem at first glance to lend themselves more naturally to an imperative style can often be expressed even more naturally in a declarative way.
- The command pattern is a pattern, not a paradigm. It’s not a style, like OOP or FP, that should be applied to all your code.
- Declarative code is often easier to understand than imperative code.
- Regular old OOP is a fine way to program. Not everything in Rails has to utilize a fancy tool or concept.