Hexagonal rails: Rake tasks are adapters

If I’m thinking about my Rails app in terms of a hexagonal architecture, I find it also pays to consider every rake task to be an Adapter. Thus:

Picture: @rosieemackenzie

The true picture is a little more complicated than that, but the principal ideas are there. The rake task acts as a mediator, allowing me to send messages to (ie. call methods on) my application’s objects.

In general, we want our Adapters to be as thin as possible (and no thinner). That’s because the Adapter code inherently depends on some framework, and that fact will usually make its tests difficult and/or slow. We still need those tests, but we want to have as few paths through the Adapter as possible, and thus fewer tests of the Adapter, so that the total test complexity and test run-time are minimized.

As an example, suppose I have a rake task that validates the posts and comments on my blog:

I want to maximize the unit tested percentage of the code executed during the task, so I move the code out of the rake task and into a new domain object. That new object “is” the task, and usually my rake tasks can then slim down to a single line of code.

Stripping all of the code out of the rake task and moving it into a domain object is analogous to the approach currently being explored by @mattwynne and @tooky in their RailsĀ controller refactorings. In my own code I call these task objects “use case objects”. Currently I keep them in a UseCases namespace within app/models, although I’m open to exploring alternative conventions. One of the nice side-effects of this is that I sometimes discover synergies between the work done by rake tasks and that done by controllers. By pulling Use Case objects out of both kinds of adapter I’m creating a convention for some of the code that turns out to be cleaner, more (quickly) testable, and which names things better.

Please let me know if you try this — or indeed if you’re already doing it.

12 thoughts on “Hexagonal rails: Rake tasks are adapters

  1. Myself an my illustrious pair Pete have been doing this a lot recently although we tend to keep the task classes in app/tasks (his preference) or lib/tasks (mine) instead of inside the app/models folder. I like the use case analogy (and namespacing) though.

    Is the verb instead of a noun as a class name part of the use case convention you have or is it something you would recommend generally?

      • The guys at our place are starting to do the same – I wonder why that is..?

        I’m liking it – helps me to visualise & talk about the code.

        Also, it ties in nicely with “test” as a verb as opposed to a noun.

  2. I’ve been naming my service-level classes as Verb phrases since 2006, or so. I find that it helps me think more about what behavior they are responsible for.

      • HAHA! I’ve been doing this a little while. I 2006 was about 2 years into my tdd practice, and I really started noticed it pushing me towards a service- and behavior-oriented design. I was doing C# at the time, and the idiom was to use I as the first letter in an interface name. I didn’t like it hanging there, so I started looking at it as part of the name, IDoThis, then implementation classes were named DoesThisSpecificThing. Worked wonderfully.

  3. This way I define src and target variables that are only scoped within this block of code. Notice that this only helps me if I define the dependency from the :articles task here. If I want to define the dependency in the definition of the :articles task, I would need a constant so I get the visibility across the whole rakefile.

  4. I think it’s great that you have a use_cases folder up under app/models. We’ve started to do that as well. All of our use cases need their dependencies injected at runtime, so you might instantiate them with:

    use_case = UseCase::Facebook::Authenticate(user_class: Persistence::ActiveRecord::User, params: params[:auth])
    result = use_case.execute!
    result.is_a?(UseCase::Result) #=> true

    Where UseCase::Result is a sort of common api that includes success? and a #data method that returns data that we can use from the caller (for instance, self.current_user = result.data[:user])

    One other, similar thing we’ve been doing is described here: http://isotope11.com/blog/commands-as-resources-or-interpreting-uncle-bobs-ruby-midwest-keynote

    Thanks for the blog post. Really very interested in leveling up my architecture chops. Been building big software for 10 years and only just now see a path towards being able to manage the next few levels of complexity :) Are you on avdi’s objects on rails mailing list?

  5. These posts remind me of Gary Bernheardt’s screencast “burn your controllers”. In it, he observes that the form of well-designed controllers is to make a single call to business logic, and render one this or another depending on the success of that.

    He then points out that this logic can be done by the Rails routing mechanism, doing away with the need for a controller altogether, helping to enforce the lack of business logic, and resulting in a more declarative style of code.

    I’m not a Ruby guy, so forgive me if this is obvious or wildly innappropriate.

    • @tartley I agree with Gary’s ideal controller structure, but I’m not convinced that much of that can be solved by the routing. He has been building a rival to Rails called Raptor, which does some of this, but I don’t know how successful that’s been (it has gone a bit quiet).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s