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.
Comments
How do you mock behaviour provided by other “Interactor” classes? It seems like it is hard coded.
@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:
Aren’t you simply describing the Command pattern? (Try: http://en.wikipedia.org/wiki/Command_pattern)
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.
@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.
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.
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.)
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).
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
@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!
Another wonderful implementation of this idea: http://silk.codeplex.com/ .NET folks are a clever bunch, we have a lot to learn from them.
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:
Test setups tend shine a light on these two fallacies.
Great article.
Isn’t this DCI?
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.
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.
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
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.
@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.
@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?
Nice ideas, though your interactor class is really just a function forced into an object oriented perspective.
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?
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.
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.
I am trying to use this code but I am not getting the exact pattern.
Check out https://github.com/cypriss/mutations
@jon You just made my day. That gem is exactly what I’ve been looking for for the past… month, at least?
Its not DCI you’re describing, its DDD. This drum has been beating for over 10 years now.
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
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.