The proof is always in the pudding. While there are good and reasonable times to introduce "interactors", this particular example is poor. The tests presented are anemic, and the code is absolutely not any clearer by being extracted and wrapped. I would indeed have kept all this in the controller.
The key point for an "interactor" extraction is imo when you have multiple models being created in symphony, like a Signup model. Or if you for some reason need to reuse the behavior.
But if all your controllers look like this, with one "interactor" model per action, you're doing it wrong.
Whatever floats your boat, though. If this is what you prefer, great. But please hold the "beginner's version" crap. Plenty of large apps are built with vanilla Rails. Basecamp is one.
I rewrote the code in this example to use the "Beginner's Version" of Rails (sigh). You judge which you like better: https://gist.github.com/dhh/9333694
Here's another version that doesn't even use private methods in the controller and uses a PORO for the email grouping: https://gist.github.com/dhh/9333991
I've spent the last few months re-writing a medium-sized code-base (several hundred thousand LoC) that looks like your version into code that looks like the blog version. Test suite run time has dramatically decreased, code is more loosely coupled and we see far fewer bugs.
> You obviously need to choose the patterns that fit the problem you’re trying to solve – it’s rarely one-size-fits-all, and some of these principles may be overkill in a very simple 15-minute blog application. Our objection is that Rails feels like it’s a Beginners’ Version, often suggesting that one size does fit all
This quote from the post (minus the "Beginners" jibe - sorry!) is my core objection. Your example works fine if you're working with a simple application. But the "Rails Way" feels like it starts to fall over when you get to medium or large codebases.
I feel like Rails could benefit hugely from a few core "Advanced" features that help you grow a small application into a larger, functioning business. Sure, you can add them yourself, but then you lose the advantage of shared standards and strong architectural conventions. Every new developer we hire has to learn what exactly we mean by "Interactors" or "Decorators" or "Policies".
I've only been involved in a couple of Rails projects (at least since the wedding list management app I wrote for my own wedding back in the v1 days), I'm in complete agreement with David.
And honestly, Rails already has something that works beautifully for an “interactor” extraction. It's called ActiveModel. Add a bit of other code (include ActiveModel::ForbiddenAttributesProtection, ActiveModel::Validations::Callbacks, and ActiveModel::SerializerSupport; extend ActiveModel::Naming) and you've got an object that acts like an ActiveRecord object but orchestrates changes across multiple models in a clean, constructive way—without introducing an entirely different way of working with your code.
I've done this at both places I've been doing Rails and it works at both cleaning up the models it orchestrates and keeping the business logic in the places where it belongs. I'm working actively to get rid of some of the Rails Fads that have come through and wreaked havoc on the current codebase (we've got Presenters and Commands and all sorts of other stuff that is just making the code harder to understand with little value).
There's a million ways to skin this particular cat, and claiming that “adult” Rails needs this particular gem or that particular way of writing code says a lot more about the person claiming it than the people who aren't following that particular fad.
> And honestly, Rails already has something that works beautifully for an “interactor” extraction. It's called ActiveModel.
The thing which Rails and DHH offer no guidance whatsoever is the separation of persistence from the domain model. The Active Record pattern is itself a conflation of those two things, so this is an opinionated choice, and I respect it as a sane default for a wide variety of applications. It works perfectly fine up to a certain scale but when the business logic reaches a certain complexity and the persistence logic reaches a certain complexity then it makes sense to separate them. A lot of proposed solutions might be overkill, but keep in mind that DHH and the company formerly known as 37signals specialize in minimalist software, so they combat this complexity from the UX down rather than conceiving architectures to support it. I agree with this philosophy insomuch as all else being equal, simpler is better, but the problem is that some applications are necessarily more complex than Basecamp, and sometimes we need more than what Rails provides out of the box. Convincing DHH of this is pointless because he doesn't have to deal with it and he has no incentive to understand anyone else's pain in this regard.
I agree with you - Interactors are particularly useful when you're creating multiple models, and the example could have been better.
But they're useful for dealing with side-effects of a particular operation too (sending multiple emails, notifying admins). Much better than ActiveRecord callbacks.
If you put this logic in the controller, what happens when you want a separate API controller that does the same thing? Or some admin functionality elsewhere in the codebase? There's zero possibility of code re-use.
Coulda, woulda, damn shoulda. You're future coding with your "what ifs". Most controller actions are not reused. The time to extract for reuse is when you need to reuse. Not crystal balling about that you probably will be in the future.
Because when the reuse case actually arrives, you might find that you need to reuse some, but not all of the action. If you're literally doing the exact same thing for both web and api, why are you using a separate API controller? Just use respond_to with formats.
Second, yes, you shouldn't have side-effects like sending email in your models. But you don't need to! Just stick that logic in your controllers. That's what it's there for -- to render the views (of which emails is one of them -- see http://david.heinemeierhansson.com/2012/emails-are-views.htm...).
The AR callbacks are wonderful for coordinating the domain model. Often times you'll want to create auxiliary objects when something else is created. That's what's it's there for. Or to otherwise keep the integrity of the domain model in place.
>> If you put this logic in the controller, what happens when you want a separate API controller that does the same thing?
You refactor.
In my experience, anytime you're writing something for the sake of "possible code re-use", you're wasting time. Code should and does get refactored often. By adding levels of indirection from the outset, you add a barrier to refactoring and likely additional unnecessary code.
the example is poor, butI think you're missing the core point here -- slow tests. I don't think this really improves readability or organization much -- but my real world experience with a giant slow rails test suite gives me 100% confidence that patterns like this are the only way to not have a completely intractable giant test suite.
Perhaps because it's written with examples in java but I often feel like no one in the rails community has ever read Eric Evan's Domain Driven Design[1]. It's far and away the best material I've ever seen on how to organise large code bases. It covers pretty much every suggestion that I've seen from the rails community. Sometimes the rails community can feel like the fitness industry, everybody just rebranding things that have been done before.
I would argue that both the Rails community and fitness industry may operate that way, but for good reasons. Any time you have an ongoing an significant influx of beginners, looking to hit the ground running, bad practices will be everywhere. A handful of established "bibles" or textbooks aren't going to be enough to get the message out to the masses. It may not be appealing to those already "in the know", but good practice messages need to be repeated and communicated in new ways. You also see the same dynamic at the gym, of the experienced gym-rat lamenting the things that personal trainers get paid to teach newbies.
In that same vein, this may be a repeat message of a concept that is not novel, but as an experienced non-web developer who is fairly new to Rails, I learned something from reading this.
I also appreciate you pointing out what sounds like a good development resource (Domain Driven Design).
Sure - I don't think anyone in the Rails world is claiming to have invented these principles.
The problem is that Rails ships with a very limited set of core architectural concepts, and many inexperienced Rails developers feel like they've got to cram all of their code into a Model, View or Controller.
Once your codebase reaches a certain complexity, principles from other programming paradigms are extremely useful.
> "The problem is that Rails ships with a very limited set of core architectural concepts, and many inexperienced Rails developers feel like they've got to cram all of their code into a Model, View or Controller."
The problem is that people think Rails is an architecture to begin with. Rails is just a framework that uses the MVC pattern (mangled slightly to fit the realm of HTTP). In the end, MVC is nothing but a directory structure for our files. What you put in those files and directories is up to you. Uncle Bob gave a great keynote on this called Architecture: The Lost Years. Here's a link: http://www.confreaks.com/videos/759-rubymidwest2011-keynote-...
Interactors, domain objects, service objects, etc. are still models; people just don't generally believe that their models are allowed to inherit from things other than ActiveRecord::Base.
To be fair, Rails does have concerns which let you easily compose your models out of modules, rather than having big god-object models, and it is easy to load other arbitrary collections of code too. So it is not really limited to MVC.
For beginners, I'm not sure it would be helpful to introduce a whole load of named patterns, as it just leads to cargo-culting and overuse of patterns without understanding whether they even apply.
It was interesting to read about a different approach though - thanks for the article.
In my first Rails job, I was handed a copy of this book to read. I would agree with your comments, although I cannot speak for the Ruby Community as such. Well worth reading.
I've spent more time thinking about clean architecture and design than most and the conclusion that I've come to is while Ruby gives you all the tools to write clean code and great architecture, it doesn't offer the best tools to do that job.
Ruby and Rails feel best as a prototyping platform for getting something out the door fast, proving a concept, and not worrying so much about correctness or maintenance.
I don't think if you are doing a lot of TDD and larger systems that piling on more Ruby and Rails is the right answer. I think once you know what you are working with, a well designed language with a compiler is a huge help and would remove a ton of useless tests and stuff that you end up writing in Ruby by hand.
This very likely leads to an API driven system with an API written in a strongly typed, compiled language like C#, Java, Scala, Haskell, or Go and writing your front end in whatever makes the most sense for your team.
At that point you get the benefits of a nice rapid development platform like Rails for your front end, and a fast, testable, API written in something else using all the clean code architecture you want.
The trick is, you do everything ugly in Rails or PHP or whatever in your initial prototype and you might not even write tests. You just ship working code until your prototype has proven a business case. Then, you move it towards something with lower TCO over time. Make the investment in your code when it makes sense to invest in it and use the best tool for the job at each step.
You probably never need to leave the standard Rails MVC stuff on most projects unless they are wildly successful and/your long term needs change. Even then, you can probably keep the rails front end stuff and just talk to an API and be very happy.
I agree completely that the Interactor pattern makes for cleaner Models and Controllers in a Rails codebase. I've used both the Interactor gem and PORO (in a directory outside of /app or /lib).
Having worked with the Interactor gem for a little while once you break things down into small interactors that can be used by the Organizers, I have two main complaints.
1) inputs are unclear. With calling new or some custom class method you can use descriptive variable names to say what the object expects to be present to do it's job. With the Interactor gem you end up adding in comments describing what keys need be present in the context and what keys will be added to the context so the next programmer can use the interactor you've created without having to go through and grok everything.
2) You end up having to (re)create a failure protocol to communicate with the controller and display to the user. We take the AR errors functionality for granted in our controllers/views with interactors you have to come up with a similar system.
2.5) as a result you end up writing a lot of boilerplate fail! and rollback code
2.5.2) and a non-atomic operation like notifying the payment gateway can break the whole model of rolling back and you have to end up raising so your user doesn't end up in a invalid state or get charged twice.
Yeah - I feel like concerns are a bit of an anti-pattern, especially as they're normally used in Rails.
You've got a God class that exposes 500+ public methods, so you split it into concerns. But now you've still got a God class with 500 methods that's split across 10 files. Good luck understanding that.
Concerns are useful when they're genuinely sharing functionality between classes - not just for splitting up "Big Bags O' Methods".
> Concerns are useful when they're genuinely sharing functionality between classes - not just for splitting up "Big Bags O' Methods".
Hmmm. I'd say it depends.
Of course having concerns (a fancy word for modules IMHO) shared between classes is great. But i've also found cases where separating a god class' behaviour into different concerns helped a lot. The important thing in those cases was recognizing and avoiding dependencies between different concerns, so instead of ending up with a god class split across many files of inter-dependant code (i.e. worse than before), you can end up with a "god" class split into its minimal "core" part and a couple of concerns that only depend on that core part but not between each other. Yes, you could still call that a god class, as it will have a very fat public API, but at least you can reason about what it does in terms of different concerns, so you gain something :)
Concerns are useful when they're genuinely sharing functionality between classes
I've found them genuinely useful for that - there is a lot of code for things like urls, publish status, ratings, roles, permissions etc that can be shared between models if they have similar functionality.
They're just a recognition that composition via modules is often better than inheritance.
I don't like Concerns because usually they are just hiding behaviors in another file (I do like routing concerns though!). You can test Concerns outside of Rails though, all you need is ActiveSupport which actually loads quite fast.
This assumes that MyCoolConcern doesn't depend on too many things in your main model class or in (god forbid) other concerns that you've included in that model.
Or, perhaps, to turn this argument around, it's better to say it this way: A Concern is poorly-constructed if it cannot be tested in isolation, so therefore something like MyCoolConcern should be written with its testability (inside of DummyClass) in mind.
One thing I really enjoy with this post is the conclusion. It doesn't try to tell you it's the only way, just that it's the way they found to fix their problem with the constraints they had, as it should be.
Rails is easy to extend, people often forget that. Great post.
We've also come across the limitations of Rails as a one-size-fits-all solution for larger codebases. I don't think this is a fault of Rails, but more of the developers using the framework. Software engineering in general has more focus on a wide variety of design patterns and doesn't limit itself to one framework. In my opinion, Rails is the best framework to create wonderful web applications. Modeling business logic requires different kind of architectures than the simple MVC-ish structure Rails provides.
At MoneyBird we are using the Mutations gem to represent something like the interactors mentioned in this article. One major advantages of Mutations, is that is also does input filtering.
The key point for an "interactor" extraction is imo when you have multiple models being created in symphony, like a Signup model. Or if you for some reason need to reuse the behavior.
But if all your controllers look like this, with one "interactor" model per action, you're doing it wrong.
Whatever floats your boat, though. If this is what you prefer, great. But please hold the "beginner's version" crap. Plenty of large apps are built with vanilla Rails. Basecamp is one.
> You obviously need to choose the patterns that fit the problem you’re trying to solve – it’s rarely one-size-fits-all, and some of these principles may be overkill in a very simple 15-minute blog application. Our objection is that Rails feels like it’s a Beginners’ Version, often suggesting that one size does fit all
This quote from the post (minus the "Beginners" jibe - sorry!) is my core objection. Your example works fine if you're working with a simple application. But the "Rails Way" feels like it starts to fall over when you get to medium or large codebases.
I feel like Rails could benefit hugely from a few core "Advanced" features that help you grow a small application into a larger, functioning business. Sure, you can add them yourself, but then you lose the advantage of shared standards and strong architectural conventions. Every new developer we hire has to learn what exactly we mean by "Interactors" or "Decorators" or "Policies".
And honestly, Rails already has something that works beautifully for an “interactor” extraction. It's called ActiveModel. Add a bit of other code (include ActiveModel::ForbiddenAttributesProtection, ActiveModel::Validations::Callbacks, and ActiveModel::SerializerSupport; extend ActiveModel::Naming) and you've got an object that acts like an ActiveRecord object but orchestrates changes across multiple models in a clean, constructive way—without introducing an entirely different way of working with your code.
I've done this at both places I've been doing Rails and it works at both cleaning up the models it orchestrates and keeping the business logic in the places where it belongs. I'm working actively to get rid of some of the Rails Fads that have come through and wreaked havoc on the current codebase (we've got Presenters and Commands and all sorts of other stuff that is just making the code harder to understand with little value).
There's a million ways to skin this particular cat, and claiming that “adult” Rails needs this particular gem or that particular way of writing code says a lot more about the person claiming it than the people who aren't following that particular fad.
The thing which Rails and DHH offer no guidance whatsoever is the separation of persistence from the domain model. The Active Record pattern is itself a conflation of those two things, so this is an opinionated choice, and I respect it as a sane default for a wide variety of applications. It works perfectly fine up to a certain scale but when the business logic reaches a certain complexity and the persistence logic reaches a certain complexity then it makes sense to separate them. A lot of proposed solutions might be overkill, but keep in mind that DHH and the company formerly known as 37signals specialize in minimalist software, so they combat this complexity from the UX down rather than conceiving architectures to support it. I agree with this philosophy insomuch as all else being equal, simpler is better, but the problem is that some applications are necessarily more complex than Basecamp, and sometimes we need more than what Rails provides out of the box. Convincing DHH of this is pointless because he doesn't have to deal with it and he has no incentive to understand anyone else's pain in this regard.
But they're useful for dealing with side-effects of a particular operation too (sending multiple emails, notifying admins). Much better than ActiveRecord callbacks.
If you put this logic in the controller, what happens when you want a separate API controller that does the same thing? Or some admin functionality elsewhere in the codebase? There's zero possibility of code re-use.
Because when the reuse case actually arrives, you might find that you need to reuse some, but not all of the action. If you're literally doing the exact same thing for both web and api, why are you using a separate API controller? Just use respond_to with formats.
Second, yes, you shouldn't have side-effects like sending email in your models. But you don't need to! Just stick that logic in your controllers. That's what it's there for -- to render the views (of which emails is one of them -- see http://david.heinemeierhansson.com/2012/emails-are-views.htm...).
The AR callbacks are wonderful for coordinating the domain model. Often times you'll want to create auxiliary objects when something else is created. That's what's it's there for. Or to otherwise keep the integrity of the domain model in place.
You refactor.
In my experience, anytime you're writing something for the sake of "possible code re-use", you're wasting time. Code should and does get refactored often. By adding levels of indirection from the outset, you add a barrier to refactoring and likely additional unnecessary code.
YAGNI, and all that.
[1] http://www.amazon.com/Domain-Driven-Design-Tackling-Complexi...
In that same vein, this may be a repeat message of a concept that is not novel, but as an experienced non-web developer who is fairly new to Rails, I learned something from reading this.
I also appreciate you pointing out what sounds like a good development resource (Domain Driven Design).
The problem is that Rails ships with a very limited set of core architectural concepts, and many inexperienced Rails developers feel like they've got to cram all of their code into a Model, View or Controller.
Once your codebase reaches a certain complexity, principles from other programming paradigms are extremely useful.
The problem is that people think Rails is an architecture to begin with. Rails is just a framework that uses the MVC pattern (mangled slightly to fit the realm of HTTP). In the end, MVC is nothing but a directory structure for our files. What you put in those files and directories is up to you. Uncle Bob gave a great keynote on this called Architecture: The Lost Years. Here's a link: http://www.confreaks.com/videos/759-rubymidwest2011-keynote-...
So you could cram every design pattern known to man and they would still cram everything in their controller.
API's are hard to discover and it takes time through trial and errors or being teached the way things are.
For beginners, I'm not sure it would be helpful to introduce a whole load of named patterns, as it just leads to cargo-culting and overuse of patterns without understanding whether they even apply.
It was interesting to read about a different approach though - thanks for the article.
[1] http://www.infoq.com/minibooks/domain-driven-design-quickly
Dead Comment
Ruby and Rails feel best as a prototyping platform for getting something out the door fast, proving a concept, and not worrying so much about correctness or maintenance.
I don't think if you are doing a lot of TDD and larger systems that piling on more Ruby and Rails is the right answer. I think once you know what you are working with, a well designed language with a compiler is a huge help and would remove a ton of useless tests and stuff that you end up writing in Ruby by hand.
This very likely leads to an API driven system with an API written in a strongly typed, compiled language like C#, Java, Scala, Haskell, or Go and writing your front end in whatever makes the most sense for your team.
At that point you get the benefits of a nice rapid development platform like Rails for your front end, and a fast, testable, API written in something else using all the clean code architecture you want.
The trick is, you do everything ugly in Rails or PHP or whatever in your initial prototype and you might not even write tests. You just ship working code until your prototype has proven a business case. Then, you move it towards something with lower TCO over time. Make the investment in your code when it makes sense to invest in it and use the best tool for the job at each step.
You probably never need to leave the standard Rails MVC stuff on most projects unless they are wildly successful and/your long term needs change. Even then, you can probably keep the rails front end stuff and just talk to an API and be very happy.
Having worked with the Interactor gem for a little while once you break things down into small interactors that can be used by the Organizers, I have two main complaints.
1) inputs are unclear. With calling new or some custom class method you can use descriptive variable names to say what the object expects to be present to do it's job. With the Interactor gem you end up adding in comments describing what keys need be present in the context and what keys will be added to the context so the next programmer can use the interactor you've created without having to go through and grok everything.
2) You end up having to (re)create a failure protocol to communicate with the controller and display to the user. We take the AR errors functionality for granted in our controllers/views with interactors you have to come up with a similar system.
2.5) as a result you end up writing a lot of boilerplate fail! and rollback code
2.5.2) and a non-atomic operation like notifying the payment gateway can break the whole model of rolling back and you have to end up raising so your user doesn't end up in a invalid state or get charged twice.
We've got a convention that all Interactors must be commented to specify what inputs they take.
Responds in much the same way active record does, allowing validations, errors, forms built from interaction objects, etc.
[1] http://github.com/orgsync/active_interaction
You've got a God class that exposes 500+ public methods, so you split it into concerns. But now you've still got a God class with 500 methods that's split across 10 files. Good luck understanding that.
Concerns are useful when they're genuinely sharing functionality between classes - not just for splitting up "Big Bags O' Methods".
Hmmm. I'd say it depends.
Of course having concerns (a fancy word for modules IMHO) shared between classes is great. But i've also found cases where separating a god class' behaviour into different concerns helped a lot. The important thing in those cases was recognizing and avoiding dependencies between different concerns, so instead of ending up with a god class split across many files of inter-dependant code (i.e. worse than before), you can end up with a "god" class split into its minimal "core" part and a couple of concerns that only depend on that core part but not between each other. Yes, you could still call that a god class, as it will have a very fat public API, but at least you can reason about what it does in terms of different concerns, so you gain something :)
I've found them genuinely useful for that - there is a lot of code for things like urls, publish status, ratings, roles, permissions etc that can be shared between models if they have similar functionality.
They're just a recognition that composition via modules is often better than inheritance.
require 'active_support/concern'
require 'my_cool_concern'
class DummyClass
endOr, perhaps, to turn this argument around, it's better to say it this way: A Concern is poorly-constructed if it cannot be tested in isolation, so therefore something like MyCoolConcern should be written with its testability (inside of DummyClass) in mind.
Rails is easy to extend, people often forget that. Great post.
At MoneyBird we are using the Mutations gem to represent something like the interactors mentioned in this article. One major advantages of Mutations, is that is also does input filtering.
https://github.com/cypriss/mutations