Hacker News new | ask | show | jobs
by archey1 2582 days ago
Been doing Rails development for 6+ years. The most maintainable codebases I've worked on had some kind of service layer between the controller and the model. We used the "interactor" gem to create individual units of business logic that we could reuse and piece together into larger "flows". Business logic stayed in the interactors, persistence logic in the models. This lead to skinny controllers, skinny models and many, many many reusable skinny services. One fortunate side effect is that all these pieces became extremely easy to test in isolation, as well as integration tested.

https://github.com/collectiveidea/interactor

2 comments

I remember as I was working my way through the "Programming Phoenix" book, I felt that the whole idea of "context" modules seemed like an unnecessary indirection. Why put all those functions in a separate module instead of just doing the work in the controller? In my initial use cases I'd end up with an AccountController that just calls a function in the Accounts module which in turn uses the User module to actually interact with the database.

Once I actually started building stuff and writing tests I had my aha-moment. By putting most of my logic in a separate 'context' module, I could both use and test all that without having to do all the bootstrapping that was necessary to run the web-framework controller logic.

While this might seem obvious to some, for me it felt like a proper level-up as a developer, as I suddenly realized how the same principle could be applied in other contexts.

My team discovered the interactor pattern about 8 months ago and never looked back. It’s worked extremely well and easy to read. We’ve found it necessary to maintain a sensible naming convention for the interactors and to namespace them.

The other pattern we’ve found to work decently well is to ensure most operations are idempotent. It makes it easy to ensure the correct state.

What are some of the parts to your naming conventions if there are any prevailing patterns?
Currently we're experimenting with namespacing per "ownership" of that namespace.

For instance, if the interactor is in charge of onboarding a new account, we have it under something like this:

Account::SetupOnboarding

Note that we also have an Account model here and so anything namespaced under it will be assumed to take place in that context.