For organizing Rails projects, domain objects are good and service objects are bad

by Jason Swett,

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.

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

Isn’t it more natural to say Tweet.new('hello').send 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

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.

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.

I’ll run through one last example, a RegisterUser service object I found on this post.

User registration example

There’s no need for this special-purpose RegisterUser object. There’s already a concept in the domain model (if we think hard enough) of a user registration. Just like how the Devise gem has controller names like RegistrationsController, SessionsController, etc., we can consider there to be a concept of a user registration and simply create a new one.

So the above code becomes this:

By doing it this way we’re neither cluttering up the main User object nor creating an awkward RegisterUser object.

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. Kind of like the UserRegistration example above, the concept of an AvailabiltyBlockPair 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.

  •  
  •  
  •  
  • 7

19 thoughts on “For organizing Rails projects, domain objects are good and service objects are bad

  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
  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

Leave a Reply

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