Service Object is a pattern of extracting business logic into a separate “service”. It has gotten considerable traction in the Ruby ecosystem and is worth exploring. Steve Lorek described Service Objects in the following way:
A ‘service’ describes system interactions. Usually, these will involve more than one business model in our application.
As an example; we have a User model and this encapsulates a password. If a user has forgotten their password, the business rules dictate that we have to send them an e-mail with a link to reset it. This functionality is a service.
The action is complex (e.g. closing the books at the end of an accounting period)
The action reaches across multiple models (e.g. an e-commerce purchase using Order, CreditCard and Customer objects)
The action interacts with an external service (e.g. posting to social networks)
The action is not a core concern of the underlying model (e.g. sweeping up outdated data after a certain time period).
There are multiple ways of performing the action (e.g. authenticating with an access token or password). This is the Gang of Four Strategy pattern.
I see Service Objects as a mutation of the Command pattern. They allow separation of business logic from models and controllers, but have further benefits in testing and composition.
Hello method_struct
At Base Lab, we’re implementing Service Objects using the method_struct gem. Let’s go through an example which illustrates the benefits of this approach.
Consider the following example. We have a blog app with a Post model, having a title and body attributes. Let’s look at the create method in the Posts controller:
There’s plenty happening here. The post is created and indexed, tweet is published, and there is a redirect or form shown at the end.
There’s even more happening in the spec, which is mostly generated from a scaffold, but also tests tweeting and indexing:
Refactoring extravaganza
Let’s see how we can clean this up using Service Objects. Let’s move all the logic connected with creating the post and subsequent operations to a method_struct. The result of method_struct is a regular Ruby class, which means we can program it as such, with removed boilerplate of constructor and accessors.
When specyfing a new Service Object with method struct, you configure how many parameters it will accept and by what methods will they be accessible. Be default the method name is call, but it can be changed, like this:
The business logic of creating new Posts is now handled by PostCreator, allowing us to test it as a regular ruby object:
The killer feature of method_struct is the automatic defining of the class method, which streamlines the creation of an instance and calling of the method at the same time. So instead of:
you can do:
Result: thin controller
At this point, we’ve extracted the logic completely from the controller, dialing down its responsibility to redirecting or showing the form with errors. Let’s take a look at how the controller looks like now:
Much better! It’s only doing one thing - it decides where to send the user based on the result of PostCreator.create. After this refactoring the specs still work, so let’s update them to test only the required part, which is redirection. Notice that having the class method shortcut for create makes it trivial to mock the response and test the controller in isolation.
The wrap up
The beauty of Service Object pattern is that we can keep going. We could extract the logic from the PostCreator by moving tweeting and indexing into their own Service Objects, each with a single responsibility. If you want to have a go at it, here’s a sample application which houses the code samples from this post.