I recently stumbled across Jason Swett’s “Beware of ‘service objects’ in Rails” blog post. While I appreciate the perspective, I think it gets a number of things wrong. I replied to this post in a comment, but wanted to post my thoughts here as well in the hopes of encouraging more dialog about this important topic.
If so inclined, please feel free to comment below with your take on this subject.
(NOTE: The following refers heavily to the example
Tweet class in the original blog post, under the heading “A better alternative to service objects: domain objects“, which you may want to review before continuing.)
Thanks for the thoughts. However, I have to disagree. Here are a few counterpoints:
Tweet.sendand the entire
Tweetclass may look very object-oriented, it in fact violates many OOP principles (single responsibility, separation of concerns, etc.). Additionally, the
Tweetclass is a field day. Anyone can and will add methods with anything that they think a Tweet can do, including CRUD operations, searching the DB for tweets, searching the Twitter API for tweets, etc. Generic, noun-based names encourage such misuses/abuses.
- Good service classes don’t use [generic] names like “Manager” or “Service”. Instead, it’s clear from their name what service they provide, IOW what they do:
- Service objects are object oriented when they act on interfaces instead of implementations. So, a hypothetical
TweetClientmight look like:
class TweetClient def initialize @api_client = TwitterApiClient.new @validator = TweetValidator.new end def send(tweet) return unless @validator.valid?(tweet) @api_client.post( tweet.as_json, TwitterApiClient::TWEET_CREATE_URL ) end end angry_rant = Tweet.new('Hello world!') TweetClient.new.send(angry_rant)
The above service class is 100% object oriented while also clearly delineating the responsibility of each class. Also, object composition is better than inheritance for reducing coupling in an app.
One other thing to note that I didn’t include in my original response:
In a standard Rails app, if the
Tweet class lives in the
app/models/ folder then it probably inherits (either directly or indirectly) from
ApplicationRecord. Thus, it’s associated with the M portion of an MVC application and should be concerned with database CRUD operations, so adding a
send method that posts to an external API muddies the water considerably regarding the responsibility of this class, makes testing much more complicated, and is a patent violation of many software development best practices.
Therefore, when adding new functionality to a class that inherits from a parent class, we would do well to consider if said new functionality makes sense in context of the entire inheritance chain. If it doesn’t, then that’s a good indication that we probably need to add that functionality elsewhere, and a service class would be a good candidate.
Just my $0.02. Thanks for reading!