Interactors in Hanami 2.1

Hi,

I have a general questions regarding interactors. I’m starting with 2.1 and previously only used 1.3 with interactors. Since there has been quite some changes regarding the app structure, what is the general view on/approach to interactors in 2.1? I don’t see any reference in the guides.

Best, seba

Hey,

There is no reference because they have been dropped from the framework. I saw no mention of a preferred solution, but I saw plenty of comments from solnic and others from core team, about leaving the choice up to the developer.

Since a large portion of Hanami is built upon dry-rb family of gems, I would assume dry-monads is a nice fit, I however usually prefer to build something upon POROs and see where it gets me. After spending few years with trailblazer operations I aim for the simplest, minimalistic solutions first. Might be an approach that fits the hanami philosophy.

This is the only other topic directly touching on interactors not being transfered from 1.3 to 2.x

Thank you for the link and explanation. I see dry-monads and dry-transactions mentioned. Excuse the question, but could components replace interactors out of the box or am I misreading the guides?

Component is used as a general name, for objects that “get stuff done”, execute your business domain logic etc. Those could be dry-monads, dry-transactions, PORO, service objects, trailblazer operations etc.

You could probably port interactors to be used as components, since it is a general term, that does not force anything specific. I see that guides use the example of “operation” and “rendered” both, being components. Same goes for interactors if you want it to.

The guides say that Hanami handles managment of components, it does not handle implementation of said components. That is up to you.

Hopefuly my explanation is clear (and correct, :stuck_out_tongue: )

Thank you, again. :slight_smile:
I’ll probably stick with the name interactors and put them in the app directory and try to keep it as simple as possible. Browsing for the topic, I’ve just stumbled upon this gem (pun intended) :grinning:.

I’ve built very complex business logic atop dry-monad’s do-notation. All you need is dependency injection from the framework + monad unwrapping from dry-monad. This is a better foundation for PORO service objects than any other framework I have tried.

1 Like

Thank you for all the great conversation here, folks! :heart_eyes:

For the current moment, my recommendation would be to follow @alassek’s advice:

As for Hanami providing something like this out of the box, we plan to have the in-development dry-operation gem completed and included in Hanami apps by default for v2.2.

dry-operation is just a slightly higher-level abstraction over dry-monads, so if you got started there in the meantime, that will place you in good stead to migrate in the future.

I’ll also make sure we announce it here in this forum when dry-operation is ready for some beta testing.

3 Likes

I cobbled together an object to handle this role for me. I call it ProtoInteractor. I don’t love the name, but it was a prototype to replace the old Hanami Interactor, so . . .

You can read about it here and find it on GitLab here.

My goal was:

  • The params functionality of Hanami Actions. This is because I want to manage parameters at the interactor level, not the action level.
  • Do notation from dry-monads.
  • Result objects, also from dry-monads for return values.

ProtoInteractor is opinionated in two ways:

  1. Handling parameters in the interactor, instead of the actions. I do this because parsing of params is a business concern, not a framework concern (in my view, obviously :nerd_face: ).
  2. The interactor expects to receive two argument objects: a user, and params. I do this because I would like to handle authorization in the interactor. That is, I want the interactor to know “who’s asking,” even if the answer is a unauthenticated user or a “system” user.

ProtoInteractor is working well for me so far, but I’m not sure if it’s ready for general release–or meets a community need–so I haven’t officially published it as a gem. Plus I don’t really like the name.

Feel free to try it out and let me know what you think. If you disagree with any of my opinions, you can read how I went about it in my article and modify it however you like.

As @alassek and @timriley pointed out, you can get pretty far with just Do notation and Result objects. My article gives a brief overview of both, if you are not familiar.

Good luck!

Here’s a helper method I make extensive use of: it’s called either and its purpose is to facilitate wrapping error results in tuples.

My Failure results always take the form of Failure[:error_name, *args] unless I am wrapping an exception. This makes pattern-matching errors from the outside much easier.

  # Utility helper to avoid .to_monad.or {} chaining
  #
  # @param [#to_monad] result, Dry::Monads::Result or object that responds to `to_monad`
  # @param [Dry::Monads::Result::Failure, #to_proc, { :error => Symbol }] error
  #
  # @example With Yield
  #   user = yield fetch_user.(id).or { |err| Failure[:not_found, err] }
  #
  # @example With Either
  #   user = either fetch_user.(id), Failure(:not_found)
  #   user = either fetch_user.(id), ->(err) { Failure[:not_found, err] }
  #   user = either fetch_user.(ud), error: :not_found
  #
  # @raise [Dry::Monads::Do::Halt]
  #
  # @return the unwrapped successful value
  def either(result, error)
    failure =
      case error
      in { error: error_name }
        proc { |err| Failure[error_name, err] }
      in T::Procable
        error
      else
        proc { error }
      end

    Dry::Monads::Do.bind result.to_monad.or(&failure)
  end

T::Procable is just a type defined as

Procable = Interface(:to_proc)
3 Likes