The Neuronen Schmiede concerns itself with topics related to software development. One focus are robust web applications.

Alternative Interactor Implementation in Lotus

Permalink

Lotus comes with a built-in interactor module that can be used to implement interactor classes. An interactor is basically an object that represents an use case of an application. It uses entities and repositories to accomplish the task at hand. The interactor usually returns the outcome of the use case to the caller. In a situation where something does not go according to the plan, an error should be communicated to the caller.

The interactor module of Lotus uses a result object for both of these things, in my opinion this is not optimal. This implementation requires the caller to check if the use case performed correctly or if there was a failure. Following code sample illustrates how a controller action would make use of such an interactor. Calling the interactor returns a result object which is then used to check if everything turned out as expected. If that is not the case the errors are returned in the else branch.

module Datsu::Controllers::Identities
  class Create
    include Datsu::Action

    expose :identity

    params do
      param :identity do
        param :email, type: String, presence: true
        param :password, type: String, presence: true
      end
    end

    def initialize(interactor = Interactors::Identity::Create)
      @interactor = interactor
    end

    def call(params)
      result = interactor.new(params).call

      if result.success?
        @identity = result.identity
      else
        halt 422, ErrorSerializers::Hash.new(result.errors).to_json
      end
    end
  end
end

I find this branching logic using the interactors result distracting. In most situations the caller of the interactor assumes that the use case got performed without failures. Therefore the success? method is not needed and only introduces branching logic in the application code. In addition there is the possibility that failures go unnoticed when the application code does not check the result. Since use cases are at the core of an application there should be no possibility that one could fail silently.

A different approach to interactors that I'm using in my applications takes this into account. While a interactor still returns a result object, it is different to the one described above. The returned object only contains resultin values from the use case that are of interest to the caller. There is no success? method to check the status of an use case. It is explicitly assumed that calling an interactor performs the use case successfully.

In case something does go wrong the interactor raises a specific error. This ensures that there are no silent failures when working with an interactor. The caller can decide if it wants to handle the error cases. If there are multiple scenarios where something can go wrong the different errors are intention revealing and help understanding the code. The first time I read about this type of implementation of use cases was in Adam Hawkins work.

Using an interactor implemented as described would turn the first code example into following.

module Datsu::Controllers::Identities
  class Create
    include Datsu::Action

    expose :identity

    params do
      param :identity do
        param :email, type: String, presence: true
        param :password, type: String, presence: true
      end
    end

    def initialize(interactor = Interactors::Identity::Create)
      @interactor = interactor
    end

    def call(params)
      @identity = @interactor.call(params[:identity]).identity
    rescue Interactors::Identity::Create::EmailAlreadyInUse
      halt 422, ErrorSerializers::Hash.new({ email: [:in_use] }).to_json
    end
  end
end

Compared to the first implementation there is no additional branching and it is clear with what kind of error the code is dealing with. The interactor will also fail loudly should there be an error and the application code does not handle the failure scenario.