Beware of “service objects” in Rails

by Jason Swett,

Note: I no longer endorse this post, which was a rough first attempt to convey my thoughts on service objects. For my newer take on service objects, see my other post, How I code without service objects.

The good and bad thing about Active Record models

In the early stages of a Rails project’s life, the pattern of putting all the model code into objects that inherit from Active Record works out pretty nicely. Each model object gets its own database table, some validations, some associations, and a few custom methods.

Later in the project’s lifecycle, this pattern of putting everything into Active Record objects gets less good. If there’s an Appointment model, for example, everything remotely related to an appointment gets put into the Appointment model, leading to models with tens of methods and hundreds if not thousands of lines of code.

Despite the fact that this style of coding—stuffing huge amounts of code into Active Record models—leads to a mess, many Rails projects are built this way. My guess is that the reason this happens is that developers see the organizational structures Rails provides (models, controllers, views, helpers, etc.) and don’t realize that they’re not limited to ONLY these structures. I myself for a long time didn’t realize that I wasn’t limited to only those structures.

Service objects as an alternative to the “Active Record grab bag” anti-pattern

A tactic I frequently hear recommended as an antidote to the “Active Record grab bag” anti-pattern is to use “service objects”. I put the term “service objects” in quotes because it seems to mean different things to different people.

For my purposes I’ll use the definition that I’ve been able to synthesize from several of the top posts I found when I googled for rails service objects.

The idea is this: instead of putting domain logic in Active Record objects, you put domain logic in service objects which live in a separate place in your Rails application, perhaps in app/services. Some example service class names I found in various service object posts I found online include:

  • TweetCreator
  • RegisterUser
  • CompleteOrder
  • NewRegistrationService

So, typically, a service object is responsible for carrying out some action. A TweetCreator creates a tweet, a RegisterUser object registers a user. This seems to be the most commonly held (or at least commonly written about) conception of a service object. It’s also apparently the idea behind the Interactor gem.

Why service objects are a bad idea

One of the great benefits of object-oriented programming is that we can bestow objects with a mix of behavior and data to give the objects powerful capabilities. Not only this, but we can map objects fairly neatly with concepts in the domain model in which we’re working, making the code more easily understandable by humans.

Service objects throw out the fundamental advantages of object-oriented programming.

Instead of having behavior and data neatly encapsulated in easy-to-understand objects with names like Tweet or User, we have conceptually dubious ideas like TweetCreator and RegisterUser. “Objects” like this aren’t abstractions of concepts in the domain model. They’re chunks of procedural code masquerading as object-oriented code.

A better alternative to service objects: domain objects

Let me take a couple service object examples I’ve found online and rework them into something better.

Tweet example

The first example I’ll use is TweetCreator from this TopTal article, the first result when I google for rails service objects.

class TweetCreator
  def initialize(message)
    @message = message
  end

  def send_tweet
    client = Twitter::REST::Client.new do |config|
      config.consumer_key        = ENV['TWITTER_CONSUMER_KEY']
      config.consumer_secret     = ENV['TWITTER_CONSUMER_SECRET']
      config.access_token        = ENV['TWITTER_ACCESS_TOKEN']
      config.access_token_secret = ENV['TWITTER_ACCESS_SECRET']
    end
    client.update(@message)
  end
end

I think it’s far better just to have a Tweet object.

class Tweet
  def initialize(message)
    @message = message
  end

  def deliver
    client = Twitter::REST::Client.new do |config|
      config.consumer_key        = ENV['TWITTER_CONSUMER_KEY']
      config.consumer_secret     = ENV['TWITTER_CONSUMER_SECRET']
      config.access_token        = ENV['TWITTER_ACCESS_TOKEN']
      config.access_token_secret = ENV['TWITTER_ACCESS_SECRET']
    end

    client.update(@message)
  end
end

Isn’t it more natural to say Tweet.new('hello').deliver than to say TweetCreator.new('hi').send_tweet? I think so. Rather than being this weird single-purpose procedure-carrier-outer, Tweet is just a simple representation of a real domain concept. This, to me, is what domain objects are all about.

The differences between the good and bad examples in this case are pretty small, so let me address the next example in the TopTal article which I think is worse.

Currency exchange example

module MoneyManager
  # exchange currency from one amount to another
  class CurrencyExchanger < ApplicationService
    ...
    def call
      ActiveRecord::Base.transaction do
        # transfer the original currency to the exchange's account
        outgoing_tx = CurrencyTransferrer.call(
          from: the_user_account,
          to: the_exchange_account,
          amount: the_amount,
          currency: original_currency
        )

        # get the exchange rate
        rate = ExchangeRateGetter.call(
          from: original_currency,
          to: new_currency
        )

        # transfer the new currency back to the user's account
        incoming_tx = CurrencyTransferrer.call(
          from: the_exchange_account,
          to: the_user_account,
          amount: the_amount * rate,
          currency: new_currency
        )

        # record the exchange happening
        ExchangeRecorder.call(
          outgoing_tx: outgoing_tx,
          incoming_tx: incoming_tx
        )
      end
    end
  end

  # record the transfer of money from one account to another in money_accounts
  class CurrencyTransferrer < ApplicationService
    ...
  end

  # record an exchange event in the money_exchanges table
  class ExchangeRecorder < ApplicationService
    ...
  end

  # get the exchange rate from an API
  class ExchangeRateGetter < ApplicationService
    ...
  end
end

First, the abstractions of MoneyManager, CurrencyExchanger, etc. aren’t really abstractions. I’m automatically skeptical of any object whose name ends in -er.

I’m not going to try to rework this example line for line because there’s too much there, but let’s see if we can start toward something better.

class CurrencyValue
  def initialize(amount_cents, currency_type)
    @amount_cents = amount_cents
    @currency_type = currency_type
  end

  def converted_to(other_currency_type)
    exchange_rate = ExchangeRate.find(@currency_type, other_currency_type)
    CurrencyValue.new(@amount_cents * exchange_rate, other_currency_type)
  end
end

one_dollar = CurrencyValue.new(100, CurrencyType.find('USD'))
puts one_dollar.converted_to(CurrencyType.find('GBP')) # 0.80

Someone could probably legitimately find fault with the details of my currency conversion logic (an area with which I have no experience) but hopefully the conceptual superiority of my approach over the MoneyManager approach is clear. A currency value is clearly a real thing in the real world, and so is a currency type and so is an exchange rate. Things like MoneyManager, CurrencyExchanger and ExchangeRateGetter are clearly just contrived. These latter objects (which again are really just collections or procedural code) would probably fall under the category of what Martin Fowler calls an anemic domain model.

Suggestions for further reading

Enough With the Service Objects Already

I really enjoyed and agreed with Avdi Grimm’s Enough With the Service Objects Already. Avdi’s post helped me realize that most service objects are just wrappers for chunks of procedural code. What I wanted to add was that I think it’s usually possible to refactor that procedural code into meaningful objects. For example, in a piece of schedule code I recently wrote, I have a concept of an AvailabilityBlock and a mechanism for detecting conflicts between them. Instead of taking the maybe “obvious” route of creating a AvailabilityBlockConflictDetector, I created an object called an AvailabilityBlockPair which can be used like this: AvailabilityBlockPair.new(a, b).conflict?. To me this is much nicer. The concept of an AvailabilityBlockPair isn’t something that obviously exists in the domain, but it does exist in the domain if I consider it to be. It’s like drawing an arbitrary border around letters in a word search. Any word you can find on the page is really there if you can circle it.

Anemic Domain Model

Martin Fowler’s Anemic Domain Model post helped me articulate exactly what’s wrong with service objects, which seem to me to be a specific kind of Anemic Domain Model. My favorite passage from the article is: “The fundamental horror of this anti-pattern [Anemic Domain Model] is that it’s so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk. What’s worse, many people think that anemic objects are real objects, and thus completely miss the point of what object-oriented design is all about.”

Martin Fowler on Service Objects via the Ruby Rogues Parley mailing list

This GitHub Gist contains what’s supposedly an excerpt from an email Martin Fowler wrote. This email snippet helped clue me into the apparent fact that what most people call service objects are really just an implementation of the Command pattern. It seems to me that the Interactor gem is also an implementation of the Command pattern. I could see the Command pattern making sense in certain scenarios but I think a) when the Command pattern is used it should be called a Command and not a service object, and b) it’s not a good go-to pattern for all behavior in an application. I’ve seen engineering teams try to switch over big parts of their application to Interactors, thinking it’s a great default style of coding. I’m highly skeptical that this is the case. I want to write object-oriented code in Ruby, not procedural code in Interactor-script.

Don’t Create Objects That End With -ER

If an object ends in “-er” (e.g. Manager, Processor), chances are it’s not a valid abstraction. There’s probably a much more fitting domain object in there (or aggregate of domain objects) if you think hard enough.

The Devise gem code

I think the Devise gem does a pretty good job of finding non-obvious domain concepts and turning them into sensible domain objects. I’ve found it profitable to study the code in that gem.

Domain-Driven Design

I’m slowly trudging through this book at home as I write this post. I find the book a little boring and hard to get through but I’ve found the effort to be worth it.

Summary/takeaway

I find the last sentence of Martin Fowler’s Anemic Domain Model article to be a great summary of what I’m trying to convey: “In general, the more behavior you find in the services, the more likely you are to be robbing yourself of the benefits of a domain model. If all your logic is in services, you’ve robbed yourself blind.”

Don’t use service objects. Use domain objects instead.

46 thoughts on “Beware of “service objects” in Rails

  1. Dan Milliron

    Don’t dismiss the Interactor pattern/gem. It neatly solves a very common implementation task: creating a single-responsibility class with a sturdy convention for passing in arguments, handling failure, returning results, chaining interactors, and rolling back a chain when one link fails.

    You are correct that the notion of “service objects” has become a hodge-podge of miscellaneous design patterns. Using the interactor pattern for its appropriate subset of domain logic coding is a good idea, leaving the remaining domain logic for other patterns.

    By the way, you and I had a Guinness together at this summer’s RubyHack conference in SLC. We talked about Michigan, if you remember.

    Reply
    1. Jason Swett Post author

      Hey Dan! Good to run into you again, virtually this time.

      The chaining features you describe sound potentially compelling, although I’ve never yet found myself with a chain-shaped hole in my life that I wish something would fill. So I guess if the use case calls for that, then the Interactor gem might be helpful.

      Reply
  2. Guillermo Siliceo Trueba

    It all seems so philosophical i would be convinced of this argument if it was not a discussion of what feels right-er or wrong-er.
    I would love to see examples of the service pattern failing and the command pattern winning instead.
    Otherwise i just go away from this post with the idea of not naming -er objects so instead of AvailabilityBlockConflictDetector with a method detect, to a AvailabilityBlockPair with the method conflict? but the underlying implementation basically the same.
    I just don’t see the concrete problem, just: is better if we name it this other way, because… reasons!

    Reply
  3. Mike Foley

    Thanks, I enjoyed the article. The example code clearly demonstrates that domain objects can lead to more intuitive abstractions.

    Reply
  4. inopinatus

    This is a candidate for Rails Article of the Year. Bravo. It’s everything I think about so-called service objects. Thank you for publishing.

    Sadly, I am absolutely certain that the mediocrity police will not understand (will not _want_ to understand) the nuances that fly off at many angles from this philosophy.

    I subscribe wholeheartedly to the notion that Ruby is an OO language, and Rails is an OO framework, and that going against that grain is setting yourself up for inflexibility and technical debt.

    My favourite exposition of this is Kevin Berridge’s talk, “OOP: You’re Doing It Completely Wrong” (https://vimeo.com/91672848) and it’s tagline, “The Behaviour Emerges from the Composition of the Objects”.

    This is the antithesis of any procedural approach, interactors included. All procedural code makes testing harder, leaves code closed for extension, forces us to depend on concretion not abstraction and so on. Amongst other things, it’s the enemy of SOLID.

    My top guideline is that application code should never be invoking a generic method named #call or something like it, at least not unless the object it’s defined on is representing a telephone, a jury, or a midwife. Quintessential class names, and purposeful method names, are the only way to deliver code that is easy to read & follow.

    As for the naming of things, those who do not grasp the relevance and importance of naming are doomed to reinvent the AbstractSingletonProxyFactoryBean.

    Reply
  5. Douglas Anderson

    I’ve used the Interactor gem extensively in a large codebase and there are two immediate issues which jump out at me from your suggested approach:

    1. Name clashes. Domain objects like Tweet and UserRegistration are nouns and would likely already be claimed by ActiveRecord. Having a service object with a verb-noun combo sidesteps this.

    2. The Interactor pattern follows a common pattern for calling a service, working with context and returning output, while from your examples every domain object would vary.

    Reply
    1. Jason Swett Post author

      Hi Douglas, my response to these would be:

      1. In practice this hasn’t been a problem for me. The non-Active-Record domain objects I’ve created tend have really specific names like, for example, AppointmentSlot, DateTimeAdjustment, and DisallowedInterval. There’s rarely a clash with the AR objects because for me to create a non-AR object with a name matching an AR object would be redundant anyway.

      2. I don’t see what’s bad about the API for different objects being different, and in fact this way seems much more logical to me than if they were the same, since after all they do different things.

      Reply
  6. Dmitry

    “Interactor” is usually used for encapsulating some business action in your application, it launches the interactions between objects/domain objects which are related to this action and process their result. It is very useful if used for this purpose.

    The problem in the examples above(except `UserRegistration`) that the pattern is used for another purpose:
    ex., from the article, “Also, what if you wanted to use the same functionality in another controller? … Why can’t the Twitter API just come with a single prepared object for me to call?”
    The author should encapsulate twitter message sending logic in an ordinary class to solve this, that’s all.

    With regard to `UserRegistration`,
    “There’s already a concept in the domain model (if we think hard enough) of a user registration.”
    Yep, and this “concept” describes the business action and return its result(success/fail). Usually you have a bunch of such concepts in your application, so it’s useful to have a base interface for them(to run/to process errors);`interactor` gem already has an opinionated solution for this. Though, I also prefer to use a noun instead of verb for describing an interaction and I don’t like their `context` and `orgranizer` things, but this is a different topic.

    BTW, if we take the “interactor” description above for granted, the method that runs interactions might be named `run/call/launch` but definitely not `create`

    Reply
    1. Jason Swett Post author

      Hi Dmitry – sounds like you and I agree on most of these topics. Unfortunately, my experience has not been the same as yours if your experience is that the Interactor gem is mostly used for business *actions*. My experience has been that the developers discover Interactor and then shoehorn EVERYTHING into it. As you can imagine, that doesn’t turn out great.

      Regarding your comment about the `create` method name – I would agree if I were trying to make my version match the service object/Interactor version, but I was deliberately trying to make it different. In my mind, a `UserRegistration` gets *created*, so it makes sense for me to call the method `create`.

      Reply
  7. Sammy

    Well sometimes you have some procedure that just needs to be done. Like:
    1 Save some object
    2 Notify some subsystem
    3 Send out an email

    If you strictly go with domain objects then you will probably have callbacks/hooks that makes the next thing happen. These callbacks will make a lot of things more awkward. (Now you cannot create a “some object” in your test setup without having stub out the notify/email logic)

    Also, what’s stopping you from saying that TweetCreator is part of your domain and it’s also just a domain object?

    Naming things in a good way is important, but names don’t change your architecture. I haven’t read the book, but I’m pretty sure domain objects is about designing the right objects and the messages exchanged between them. Not just changing a
    the name of some class/method and saying that you have improved your design.

    Also, I would consider overwriting send (in your first example) typically to be a bad idea.

    Reply
    1. Austin Schneider

      Jason, I think Sammy hits the nail on the head with his comment. Definitely consider it as you write more about this topic.

      I think the right way to think about OO is “what’s the message I need to send?” and “who should receive that message?”, rather than thinking about objects as data + behavior.

      In your tweet example, clearly the message is “send_tweet”. Who should receive this? Probably a twitter service/client of some kind. The tweet is the argument:

      twitter_client = TwitterClient.new
      tweet = Tweet.new(“hello!”)
      twitter_client.send_tweet(tweet)

      Reply
  8. Josh

    The most difficult thing when using domain objecs inside rails applications is that they are often conflicting with my models in terms of naming, becuse the names of my models are the names of my domains.

    For example in your article, RegisterUser.new(user).execute becomes UserRegistration.new(user).create.

    Only thing that changed here is the naming (RegisterUser becomes UserRegistration, execute becomes create).

    Isn’t user registration part of the user domain here? Instead of passing user instance to an object that is basically a function shouldn’t this be written as user.register.

    So for me it is hard to do DDD inside rails without conflicting with my models, do you have any advice/thoughts on this, or am I getting this whole DDD thing wrong?

    Reply
    1. Jason Swett Post author

      I’ve actually never had this naming problem, so the way you approach it and the way I approach it are probably different.

      The `UserRegistration` example is probably not necessarily a great one to focus on. I only used that example because it came from a blog post I was wanting to refute. I would probably never actually end up with an object called `UserRegistration` in a real Rails project (because I would use Devise and let Devise take care of almost all that stuff for me).

      Here are some better examples of real objects I’ve created in a production project. These examples come from a scheduling module. Class names include `AppointmentFilter`, `AppointmentSlot`, `DateTimeAdjustment`, `DisallowedInterval` and `FirstOccurrence`. None of these classes (which are all POROs) map to database tables. I do have a class called `Appointment` which maps to an `appointments` table. The `Appointment` class itself does relatively little. I’ve tried to push most of its behavior out into smaller, more specific objects.

      Reply
  9. Anton

    Great article, although I’m sure (and we can even see it in comments) that most developers will find that difference subtle and mostly philosophical. Still your approach is correct and is in fact more “Rails way”.

    The thing is all these “service objects” are basically fancy Plain Old Ruby Objects, you don’t even need to name a thing like “NewRegistrationService”. The idea of class that just calls 1 method is silly.

    Instead spamming “CurrencyTransferrer”, “CurrencyExchanger”, etc classes it’s more correct to have one Currency class and corresponding methods.

    Anyway, thanks for the article, totally agree with it!

    Reply
    1. Jason Swett Post author

      Thanks. The difference extends well beyond naming, and I find the difference not philosophical but highly practical. Based on the comments I see, I gather that I have more work to do in explaining what I mean. I plan to write some more soon to help make things clearer.

      Reply
  10. Andrew Stewart

    I have found service objects / the command pattern useful for moving logic out of controllers, not models. The controllers become simple adapters between HTTP-land and the domain, reducing coupling.

    Reply
  11. Jon Leighton

    I’m late to the party but I enjoyed this post, thanks.

    Personally I always find it easier to figure out a good domain model when working on non-Rails code bases.

    I think Rails’ fixation on grouping classes by the type of thing they are (model, controller, …) rather than the area of the domain that they relate to, coupled with its aversion to namespacing, makes “thinking outside the MVC” much harder. So some people say, “aha, we need a new box to put some bits of code in — services!”

    Reply
  12. RajaRaviVarma

    I always obsessed about not creating objects which are not noun. But the more I write code, I am slightly leaning towards encapsulating procedures in classes with the necessary states (Service classes). This, IMHO, leads to small testable and easy to change code.

    For example, I have the text transcription of a conversation. There I need to remove some personally identifiable information, which involves calling a third party API. Now where should I put the logic for that? Definitely not in the `Transcription` class. After removing the sensitive data I have to redact the corresponding audio file, but only if there is a need to redact. Otherwise it is unnecessary to do the computation. And it needs to be queued to the background job system. Where should this logic go? Eventually I had to put them in service classes. Would like to know your suggestions on these kind of use cases.

    Reply
    1. Jason Swett Post author

      This is an interesting use case. It’s not immediately clear to me how the redaction work could be expressed in terms of “noun objects”. Maybe this is one of those cases where a procedural object really is better.

      Just to “think out loud” about it, my first instinct would be to have a method on `Transcription` called something like `remove_personally_identifiable_information`. But it sounds like there’s enough stuff going on with the removal of personally identifiable information that it wouldn’t be very nice to have all that in one method. So maybe we’d want to delegate that stuff to one or more other objects.

      One idea I can think of is to have an `UnredactedTranscription` and `RedactedTranscription` class, or something like that. Or maybe `UnredactedTranscription` and just `Transcription`, with `UnredactedTranscription` perhaps inheriting from `Transcription`. The redaction work would of course happen inside `UnredactedTranscription`.

      I imagine I’d probably have more classes than that too but I probably wouldn’t be able to think of what they might be without actually working with the code.

      Reply
      1. RajaRaviVarma

        Hey, Jason, thanks for the reply, I totally forgot about this comment I made and incidentally found this when I read the article (again). I am not working on the code anymore, but IIRC, I couldn’t fit all the `redaction` logic into the `Transcription` class as I didn’t think it belonged there. I used a mix of `RedactionService` and a bunch of `Transcription`.. other classes to get that done.

        Reply
  13. Ivan

    I always try to follow same entity creation concepts/naming approach, but I still want to get back to `UserRegistration` class.
    Consider you need more than just #create, but also manage UserRegistration state, for example #cancel, #archive, etc. Will you put all this new logic into the same class? And if so, isn’t this approach not breaking some of concepts, like SRP or lead to God object? Will you convert it to facade then, and if so, who you will delegate actual work to? Maybe UserRegistrationCanceler|UserRegistrationArchiver?
    I think you got the point 🙂

    Reply
    1. Jason Swett Post author

      Good questions. In this case I would consider “cancel” and “archive” to be actions on the user itself, not the user’s registration. The `UserRegistration` class would only make sense to me if a) it only handled registration-related concerns and b) the registration-related concerns were too non-trivial to be included in the `User` class.

      In my experience, cancelation/archival are both trivial enough that they can be included in the `User` class. In fact, there’s usually not anything special in the `User` class at all, it’s just `user.destroy` in a controller.

      Reply
      1. JP

        I’m also interested in expanding on this further. In my experience once you have something like UserRegistration, so much logic eventually gets lumped into it. It’s somewhat abstract to determine if a new method belongs in the class or not, so you get edge cases creeping in, and logic in UserRegistrations growing.

        The thing I like about the idea of service objects is “one public method per class” so you can easily enforce not having scope creep.

        Are you saying that in naming something like RegisterUser, you take extra care to make the intention of the class very clear, so it would be easy to add a few public methods without issue because of the name of the Domain class here?

        Just trying to understand how to make a hard and fast rule about naming, and what public methods would be allowed in a class like RegisterUser

        Reply
  14. anon

    A user object has no business denoting both the structure and validations that signify a bit of user data and how to retrieve and save/update itself. ActiveRecord as implemented with the ActiveRecord orm in Rails just encourages people to write classes that are data models and repositories at the same time, where your fields can be invisible ,and you can have side effecting call backs that invisibly modify world state outside of your own app.

    I have the pleasure of working with an application that is split into five or six smaller rails applications. That isn’t so bad I hear you say… Trust me it is. You see I am not writing a rails application but I need to either figure out which of the five or six applications has the most correct set of models and copy them directly into my own application and then add a whole bunch of irrelevant dependencies just to make the “models” work or I can copy the models over after I figure out which are the most correct and hack them to take out all of the useless callbacks and other external dependencies that I don’t need.

    Now you might argue that my situation is more of a problem with how these applications were built, and you may be right. However the approach that ActiveRecord took is the root cause of these problems as it required the people who built these things to think outside of how the creators of ActiveRecord/Rails and it’s popular plugins actively encourage these tools to be used in the first place.

    If the data model was separate from the database translation it would be trivial to share models across applications regardless of how complex the business rules were and whether or not they need to access the outside world because those things would be somewhere else.

    Your RegisterUser class example isn’t very good not because it is too trivial but because the first instance should be a module, not a class and some base executer class will probably want to just extend the module. (Possibly at runtime with extend.)

    Some other thing would just execute the execute method if the object it receives as an argument responds to execute. In other words the first example feels alot more like an interface in C#.

    Your second instance of RegisterUser would be better off as a module as well because there is no need for internal state at all. The create function returns the created user but even if we are using an ActiveRecord style model there is no reason to create a class with internal state about a user when we can easily make the potential user a parameter to create.

    A good use of a class in that example would be one in which the actual saving was decoupled to a UserContext instance to borrow phrasing from EntityFramework. A context being the thing that actually handles the data connections and so forth in that ORM. This UserContext instance would be passed into UserRegistration and the created instance would communicate with the given context.

    In short command pattern only seems useless in an Active Record context because the pattern, especially as implemented in ActiveRecord puts too many things together and encourages tightly coupled objects that are a nightmare to try and separate after the
    fact. In a system where things are more correctly separated the command pattern becomes much more useful. See: https://docs.microsoft.com/en-us/ef/core/ for a trivial example of a minimum amount of separation.

    Reply
  15. Ben

    hi Jason

    thx for the post.

    Here are my thoughts on this:

    I am not particularly clear about what a service object is. Nevertheless, I do not see a lot of difference between the ideas you use, and the Toptal author, apart from: (i) the toptal author using the call method (which unfortunately obfuscates meaning and readability), and (ii) having only one method per object. Both yours and his are objects which do particular things: e.g. object.do_stuff. have i understood this correctly?

    (((and a side comment that can safely be skipped: Whenever I see words like: domain object / domain model / business model / application specific logic etc. I can’t help but cringe: these catch words are laden with hidden assumptions which obfuscate meaning and intent – almost like fancy marketing words that nobody can concretely define. I did find an article about it here: https://www.codewithjason.com/difference-domains-domain-models-object-models-domain-objects/ ))

    Reply
  16. Omkar

    Hi Jason,

    Thanks for the article. It’s really well articulated and has detailed examples.

    I had one small doubt, though. I’m working on an API only Rails application. APIs have multiple versions. Sometimes, there are certain things which should only be present in one particular API version.
    For example:
    – I would like to validate params only in the latest version of my API.
    – I want to update the associated models only in a particular version of my API.
    How do we handle these scenarios with Domain objects?
    This year’s RailsConf also had a talk where the host used Service objects to manage versions in his API.
    I really would like to solve this problem in an Object Oriented way rather than a procedural way.
    I am curious about your thoughts on this topic.

    Reply
  17. Daniel

    Hi Jason.

    First, congrats for this post. I’ve been using ServiceObjects over the years, but now, in face of improving my reading I’ve started to question myself about using them so many times and your post is helping me to figure out I’m not alone.

    What I still thinking about is where put these new classes as `UserRegistration` and `CurrencyValue` in Rails structure. I’ve already tried `models`, but I’m afraid of making this folder too much big. Currently I’m trying to use `lib` folders, but it seems “out of application” to me.

    What’s your point about it?

    Reply
      1. Daniel

        Nice! I’ll try it.

        And what about another ‘common’ patterns in Rails world as ValueObject, ViewObject, QueryObject, etc. Have you been used them or you think they’re also as tricky as ServiceObjects?
        Just minding if it’s better to first try GOF’s before going through these ones.

        Reply
        1. Jason Swett Post author

          I’ve used Value Object and Query Object. I don’t know about View Object.

          The main difference between Value Object and Query Object and “service objects” in my mind is that Value Object and Query Object each have an actual meaning while “service object” doesn’t have a single agreed-upon meaning.

          Reply
  18. Petr Bazov

    Hi! Thanks for the article!
    It was interesting to read such a detailed opinion about service objects!

    But it seems to me that you misunderstood the concept of service objects as an architectural layer that must implement business logic, which without them is distributed across controllers and models. With this approach:
    1. The controller is responsible only for receiving requests, parsing incoming parameters and generating a response. It delegates the execution of the action to the service object. The controller only decides “what” to do, and the service object decides “how” to do.
    2. Models act only as an interface for communicating with the database. They shouldn’t have any logic other than getters and setters. These are simple entities for storing data. You shouldn’t include “trivial enough” methods in your models, you should move all methods into service objects.
    3. Service objects implement business rules. They take parameters prepared by the controller and use models as an abstraction to interact with the database. Each service is responsible for only one rule. There cannot be a service that at the same time provides the entire set of public methods – “create”, “update”, “delete”, etc.

    As a result, we get an architecture separated into three logical layers:
    1. The upper layer in the form of a controllers is responsible for receiving data and responding from the server.
    2. The bottom layer in the form of models is responsible for the data.
    3. The middle layer in the form of service objects is responsible for the logic.

    Each of these layers can be tested independently of each other, testing only the logic for which it is responsible. Regardless of the complexity of the project and its size, this approach allows:
    – Write less coherent, more extensible, easily testable and reusable code.
    – Keep models and controllers without a lot of lines of code. Service objects also cannot grow, because for each atomic action its own service object is created.

    When implementing service objects, you have all the power of object-oriented programming. You can inherit service objects from other service objects, you can use service objects within service objects as a composition. You can save the parameters and results of intermediate calculations passed to the service object as instance variables and not pass them through a series of private and protected methods. It’s not just a replacement for functional programming.

    And in general, object-oriented programming does not dictate rules for mapping real-world objects into a class system, so developers can create any classes and name them whatever they want, even if there is no equivalent for them at the domain level. By artificially establishing such a rule, you limit the ability to write abstract code.

    Sorry for the bad English =) I hope that due to my arguments you will try to reconsider your opinion about service objects.

    Reply
    1. Jason Swett Post author

      Thanks! Also, your English is quite good.

      I think you and I have a different conception of what a service object is and what a model is. I use a broad definition of “model”. A model doesn’t have to be an Active Record model, it can just be a Plain Old Ruby Object.

      If you took everything you wrote and search/replaced “service object” with “model”, I think I would agree with pretty much all of it.

      Reply
      1. Petr Bazov

        Thanks for your answer and clarification!

        Once again I was convinced that it is very important to agree on terminology before discussions =)

        Reply
      2. Petr Bazov

        But I don’t think it is a good idea to put objects responsible for business logic and objects responsible for communicating with the database in the same row. By your logic, they are at the same level of abstraction – they are all just models. But this is fundamentally different from the idea of service objects, which introduces an explicit separation of classes into different logical layers – the data layer and the logic layer.

        Your approach only partially solves the problem of bloated models, but does not solve other problems that arise in large projects.

        Reply
        1. Jason Swett Post author

          “They are all just models” is in fact a fair characterization of the way I view it, but I never said that all the models are all the same level of abstraction.

          It seems potentially good to have separate data layers and logic layers, or as I’m used to phrasing it, a persistence layer and a domain logic layer. Sadly this is typically not part of the picture when most people discuss what they call “service objects”.

          Petr, to sum it up, I think the ideas I’m railing against in this post are a different set of ideas from what you’re thinking of when you think about service objects. Most of what you’ve said I would pretty much agree with.

          Reply
  19. Felix

    I think the basic idea is good even though the points you are trying to make is not clearly enough in my opinion. I would love to see an example to extrapolate what kind of problems this might be causing. Your examples are clear but maybe you could point out directly where you see the “wrong” examples might cause some trouble. As far as I can see both solutions seem to work fine in this scenario. I can only imagine once you try to make the service too big it could cause some trouble but for smaller tasks it seems to be fine in my opinion.

    Reply
    1. Jason Swett Post author

      Yeah, I want to create a post where I refactor a “real” service object rather than a trivial example, but I haven’t been able to get my hands on one yet.

      Reply
  20. Pingback: How to clean up messy Ruby on Rails applications - Developers Feed - Abu Sayed

  21. Alex Smith

    Thanks for the thoughts. However, I have to disagree. Here are a few counterpoints:

    1. While Tweet.send and the entire Tweet class may look very object-oriented, it in fact violates many OOP principles (single responsibility, separation of concerns, etc.). Additionally, the Tweet class 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.

    2. Good service classes don’t use names like “Manager” or “Service”. Instead, it’s clear from their name what service they provide, IOW what they do: TweetRepository, TwitterApiClient, etc.

    3. Service objects are object oriented when they act on interfaces instead of implementations. So, a hypothetical TweetClient might 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(‘Outrage at and contempt for differing viewpoints’)
    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.

    Just my $0.02. Thanks for reading!

    Reply
  22. Pingback: Responding to “Beware of ‘service objects’ in Rails”

  23. Bart de Water

    The example from Toptal doesn’t call it out explicitly, but what it does looks like double-entry accounting for a currency exchange. That’s why there are two CurrencyTransferrer calls, and this way of recording money movement has been the standard for hundreds of years. If you’re a fintech/ecommerce business you probably have fees and taxes accounted for along the way as well.

    The suggested fix for the currency exchange example is not the same as the original – it only has the exchange rate component and that’s trivial compared to the above. CurrencyValue could also be just called ‘Money’ – Martin Fowler describes why it’s more than just a number and currency (the `allocate` method for example), see https://martinfowler.com/eaaCatalog/money.html

    For both there are plenty of gems available btw, see envato/double_entry and RubyMoney/money or Shopify/money as examples.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *