Where's Your Business Logic?

If I sat down with your code base and asked you how such-and-such a feature is implemented, what would you say? Would you lead me through controller filters and actions? What about model methods, includes, callbacks or observers? How about objects in lib/ (oh, and there’s also this other call to a mailer to make sure emails get sent out…), or would you even dive down into the database itself? Or would you send me all of these paths at once? Can you even remember or properly follow the code flow for a given use case? If any of this sounds familiar, and you’re nodding in a “yeah I know but…” fashion, but feel stuck, I’ve got an answer.

Before we continue, if you haven’t watched Uncle Bob Martin’s keynote talk Architecture: The Lost Years yet, please go do so now. I’ve yet to find a better explanation of this problem nor a better solution than what Uncle Bob presents.

The fundamental problem with almost every Rails project (and I’m sure in other frameworks as well), is that there is no direct codifying of the business rules and use cases of the application. There is no single location you can point to and say “here, these objects implement our use cases”. I put some of the blame on Rails itself, which has guided developers to use Controllers, Models, or Libraries, and nothing else. However I put most of the blame on us, the developers, for two reasons. First, we rarely spend enough time up front thinking about the problem space and designing a solution, and second, we haven’t been listening and reacting to test pain (you are doing TDD right?). Are your tests slow (>1s to run a single unit test)? Do your tests have large ungainly setup? Are you using factory_girl in your unit tests? Are you mocking implementation instead of interfaces?

If you answered ‘yes’ to any of these questions, your tests are screaming at you that your design is wrong or nonexistent. If you do TDD right, following the Red, Green Refactor cycle will lead you towards small, simple objects that do one thing and do it well. That said, getting from here to there is a very daunting task, but it’s not impossible.

Enter the Interactor. An Interactor handles a use case. It pulls together the models and libraries it needs to process a single business rule, and then it’s done. These objects are very easy to test and use and in proper OO fashion can be used anywhere the app needs to apply the use case or business rule. If an Interactor’s test ever feels painful, then the Interactor is probably doing too much and you actually have two rules being processed by one object, so refactor! Take control of your tests and your code again, and you’ll wonder why you never did this before (I sure did!).

I’m still working on the general API an Interactor should have, but this is what I currently recommend:

  • Interactor class names are Verbs: LogUserIn, ProcessComment, etc.
  • The constructor takes current-state information, e.g. the currently logged in user
  • It has one or more instance methods to fire off the process, I normally try to have one called #run.
  • These methods take any required parameterized information, like login and password from the user.
  • It can use other Interactors as needed

As an example, here’s my LogUserIn interactor from my personal project raidit that applies all five of these rules. This object takes a login type (:web, :api, :mobile, etc) and the login / password from the user, and applies the rules necessary to log the user in:

require 'securerandom'

require 'models/user'
require 'interactors/find_user'
require 'repository'

class LogUserIn

  attr_reader :login_type

  def initialize(login_type)
    @login_type = login_type
  end

  def run(login, password)
    action = FindUser.new
    user = action.by_login login
    if user && user.password == password
      user.set_login_token @login_type, new_login_token
      user
    else
      nil
    end
  end

  protected

  def new_login_token
    SecureRandom.hex(32)
  end
end

The controller action that uses this Interactor is such:

  def create
    action = LogUserIn.new :web

    if user = action.run(params[:login], params[:password])
      reset_session
      cookies[:web_session_token] = {
        value: user.login_token(:web),
        httponly: true
      }

      redirect_to root_path
    else
      flash.now[:login_error] = true
      render action: "new"
    end
  end

As you can see, the controller action takes care of everything Rails should: setting cookies, passing in parameters, clearing out the session, showing messages and redirecting. Everything else not Rails specific is handled in the Interactor. The test for this Interactor can be found here: log_user_in_test.rb and if you look at unit/test_helper.rb you’ll notice that this test doesn’t load Rails at all; it’s not needed! This gives the test an extremely fast start-up and improves the TDD experience dramatically.

So I’ll ask again, Where is your Business Logic? Do you have a nicely Object Oriented application that Rails simply uses or is your code spread everywhere as from a shotgun? If it’s the latter, Interactors are a great way to start cleaning up your code and giving your application some structure and architecture.

jason.roelofs@collectiveidea.com

Comments

  1. June 28, 2012 at 16:30 PM

    How do you mock behaviour provided by other “Interactor” classes? It seems like it is hard coded.

  2. June 28, 2012 at 17:01 PM

    @Samuel Williams: Right now in raidit I’m taking the stance of never mocking objects I own. I know this goes against the “isolate and test” idea, but I’ve been bit pretty hard by code with too many mocks, and mocking done wrong, so I don’t mock them. As this project progresses I’ll see if this idea is manageable or if interface mocking is really needed for keeping tests clean.

    To answer your question directly, it’s easy using Ruby and mocha. In the case of LogUserIn, you’d mock with the following:

    FindUser.any_instance.expects(:by_login).with(login).returns(user)
    
  3. June 28, 2012 at 17:05 PM

    Aren’t you simply describing the Command pattern? (Try: http://en.wikipedia.org/wiki/Command_pattern)

  4. June 28, 2012 at 18:18 PM

    I understand the benefit of separating concerns, but can you elaborate on why this functionality belongs in an object?  It seems like a static method (or even a free function) could accomplish the goal of coordinating interaction between your models.

  5. June 28, 2012 at 18:25 PM

    @David Hoogeberg: There are similarities yes, but this pattern is focused on being an implementation of a single application use-case, which is IMO higher level than a set of Commands that act on your domain models. The other distinction I see is that where Commands are supposed to be given everything they need to act, Interactors are supposed to be the top level access to your application (and Rails is not your application) and thus need to be able to find the information they need to function.

  6. godwindock@gmail.com
    windock
    June 28, 2012 at 19:07 PM

    That’s nice idea, and I’m in the early stages of implementing something similar at https://github.com/windock/kobza_crm (very early stage).

    One thing to consider: there is a very decent implementation of Repository pattern at https://github.com/playlouder/persistence, at least a better starting point.

    The other is implementation of Interactors. Robert Martin has called them Transactions also in his books, where they’re indeed an implementation of Command pattern. What distinguish your Interactors from Commands (or Transactions) is lack of consistent interface: method ‘run’ takes different number of arguments. In comparison, Martin provides all required information for Transactions in constructors and setter methods. I see no reason not to provide consisten interface.

  7. nickgieschen@gmail.com
    Nick
    June 28, 2012 at 19:10 PM

    It looks to me like this is already something Eric Evans has described in Domain Driven Design with Domain Service objects (which have to be distinguished from Application Service objects.)

  8. godwindock@gmail.com
    windock
    June 28, 2012 at 19:24 PM

    That’s interesting, how Services in DDD conflict with Martin’s Transactions:
    DDD tells: Services should not have their state
    Martin tells: Transactions are just Command objects (thus they have state).

  9. vladiim@yahoo.com.au
    Vlad
    June 28, 2012 at 23:17 PM

    Awesome, thanks Jason… another app I was digging into to get my head around Uncle Bob’s principals that your audience might find helpful is https://github.com/qertoip/guru_watch.git

  10. June 29, 2012 at 13:28 PM

    @windock: Thanks for the links, I’ll be sure to watch your CRM. As for the inconsistent API, I can definitely understand your point, particularly as the one part of of the Interactor that I’m not using is Request / Response objects, but instead passing around plain parameters and letting the Interactors return instances of my domain models. I don’t want to drop down into a pure Command object pattern with this yet, as I feel this Interactor pattern works as a higher-level version. So I’ll have to see if my current setup is maintainable or if it gets unwieldy.

    I also went ahead and ordered Clean Code, surprised I didn’t have it yet, thanks!

  11. godwindock@gmail.com
    windock
    June 29, 2012 at 21:35 PM

    Another wonderful implementation of this idea: http://silk.codeplex.com/ .NET folks are a clever bunch, we have a lot to learn from them.

  12. scottburton11@gmail.com
    Scott Burton
    June 30, 2012 at 5:44 AM

    I’m becoming a huge fan of this pattern. It’s been tying the Ruby community in knots for the past week or so, but it’s hard to understand why it’s even controversial. 

    There are two prevalent arguments against OO at the moment:

    1. Your Rails App Is Your App - this argument is used to justify tangling business logic up with your framework. It also makes making changes a nightmare.
    2. It’s Unconventional - conventions are important, but not if they’re bad.

    Test setups tend shine a light on these two fallacies. 

    Great article. 

  13. June 30, 2012 at 12:22 PM

    Isn’t this DCI?

  14. June 30, 2012 at 15:45 PM

    Yes, this appears to be low-ceremony DCI.

    Apart from any other considerations, I think the main advantage of encapsulating business logic in its own object is that it makes testing easier. In particular, you don’t have to test the logic in the context of a controller.

  15. July 01, 2012 at 2:36 AM

    Steve Klabnik*: *Michael Schuerig: Yes this is basically DCI, worded and presented a little differently. I’ve never been a fan of the “injecting functionality into your model” approach that a lot of DCI articles use, I’d prefer to have as much logic for a given use case be in the same object and not spread throughout modules / traits / etc.

    Either way the use case is basically the same: let another object handle the logic for implementing a given feature/business rule/use case. Describing it in these words has helped me finally understand not only what these patterns solve but how to use them to get out of this morass of super coupled code.

    None of these ideas are in any way new, however it’s not something many people in the Rails community have talked about until just recently.

  16. July 02, 2012 at 19:59 PM

    I think ayende has been doing this for a while, with Commands, Tasks and Queries as Interactor, but not everything like LogUserIn but He has a task for ProcessComment

    Here is a link of a series named “Limit your abstractions” maybe you can find it useful for your Interactor API

    http://ayende.com/blog/154081/limit-your-abstractions-you-only-get-six-to-a-dozen-in-the-entire-app

    And here is the implementation
    https://github.com/ayende/RaccoonBlog

  17. July 02, 2012 at 21:38 PM

    Doesn’t this fit better if you just use a first-class function rather than fitting the run() into an object? maybe a function like LogInUser()? Verbs are functions or methods, not objects. 

  18. July 02, 2012 at 21:50 PM

    @Roopesh Shenoy: I disagree. There is no hard rule nor good reason why object names can’t be verbs. If you need to do something, and you want an object and not a function, then name it “Do”. Much easier to read and understand than “SomethingDoer”. This also has the side effect of helping enforce SRP.

  19. July 03, 2012 at 6:40 AM

    @Jason - agreed the naming convention you have used is better than what we normally do, my point was not that it is wrong - my point was why use an object when a function will do much better?

  20. erturne@gmail.com
    eric
    July 03, 2012 at 9:42 AM

    Nice ideas, though your interactor class is really just a function forced into an object oriented perspective.

  21. June 30, 2012 at 5:48 AM

    Thanks for the write up. I agree that thinking about our apps this way is useful. How to you compare your pattern to DCI, and what is your experience with DCI that you’ve decided to ‘roll your own’ use-case-centric method?

  22. July 03, 2012 at 17:49 PM

    Jason Roopesh I’m with Roopesh and eric on this one. Objects couple a bundle of data with operations on it. In these cases, I see no value in using classes over using a function.

    Also, SRP also applies to functions.

  23. martiendejong2008@gmail.com
    Martien de Jong
    July 05, 2012 at 14:59 PM

    I like the idea, but is a use case not usually made up of more than just some background processing? To me it seems that the real problem is handling asynchronous calls in a workflow in an oprganized way. 
    For instance, the SignUp use-case consists of something like this:

    start when signup button is pressed:
    while !signupForm.isValid
    {
    display signupForm
    if button=cancel return
    }
    User.create(signupForm.user)
    signupForm.user.mail(signupConfirmationMail)
    Session.user = signupForm.user

    but we cannot do this because of asynchronous operations, and we have to split it up in multiple functions.
    It would definitely help to keep these together, but I think this can be done in the existing MVC structure.

  24. September 01, 2012 at 4:34 AM

    I am trying to use this code but I am not getting the exact pattern.

  25. jon.i.austin@gmail.com
    jon
    March 02, 2013 at 18:25 PM

    Check out https://github.com/cypriss/mutations

  26. jriley@betaforce.com
    James
    April 19, 2013 at 2:18 AM

    @jon You just made my day. That gem is exactly what I’ve been looking for for the past… month, at least?

  27. toby+collectiveidea@cbcg.net
    Toby DiPasquale
    September 12, 2013 at 0:21 AM

    Its not DCI you’re describing, its DDD. This drum has been beating for over 10 years now.

  28. June 26, 2014 at 5:47 AM

    I have written an article to explain why interactor gem is a horrible idea. http://bparanj.blogspot.com/2014/06/why-using-interactor-gem-is-very-bad.html

  29. July 02, 2015 at 10:03 AM

    Checkout rails_workflow gem (http://github.com/madzhuga/rails_workflow ) - it is engine to configure / manage processes with auto- and user- operations to build complex processes and separate business logic from controllers / models.