【问题标题】:Best practice of error handling on controller and interactor控制器和交互器错误处理的最佳实践
【发布时间】:2019-01-07 11:35:39
【问题描述】:
# users_show_controller.rb
class Controllers::Users::Show
  include Hanami::Action

  params do
    required(:id).filled(:str?)
  end

  def call(params)
    result = users_show_interactor(id: params[:id])

    halt 404 if result.failure?
    @user = result.user
  end
end

# users_show_interactor.rb
class Users::Show::Interactor
  include Hanami::Interactor

  expose :user
  def call(:id)
    @user = UserRepository.find_by(:id)
  end
end

我有一个控制器和一个像上面这样的交互器。 我正在考虑在控制器上区分 ClientError 和 ServerError 的更好方法。

我认为如果我能处理如下错误就好了。

handle_exeption StandardError => :some_handler

但是,hanami-interactor 包装了自身内部引发的错误,因此控制器通过来自交互器的结果对象接收错误。

我不认为在控制器上重新引发错误是个好方法。

result = some_interactor.call(params)
raise result.error if result.failure

如何实现这样的错误处理程序? 我知道if 语句会很容易增加,所以这种方式并不聪明。

def call(params)
  result = some_interactor.call(params)
  handle_error(result.error) if result.faulure?
end

private

def handle_error(error)
  return handle_client_error(error) if error.is_a?(ClientError)
  return server_error(error) if error.is_a?(ServerError)
end

【问题讨论】:

    标签: ruby error-handling hanami


    【解决方案1】:

    实际上不是面向赏花的方式,但请查看dry-monads with do notation。基本思路是可以通过如下方式编写interactor-like处理代码

    def some_action
      value_1 = yield step_1
      value_2 = yield step_2(value_1)
      return yield(step_3(value_2))
    end 
    
    def step_1
      if condition
        Success(some_value)
      else
        Failure(:some_error_code)
      end
    end
    
    def step_2
      if condition
        Success(some_value)
      else
        Failure(:some_error_code_2)
      end
    end
    

    然后在控制器中你可以使用dry-matcher匹配失败:

    matcher.(result) do |m|
      m.success do |v|
        # ok
      end
    
      m.failure :some_error_code do |v|
        halt 400
      end
    
      m.failure :some_error_2 do |v|
        halt 422
      end
    end
    

    匹配器可以在所有控制器的prepend代码中定义,因此很容易去除重复代码。

    【讨论】:

      【解决方案2】:

      我通常会在交互器中引发范围错误,然后控制器只需要挽救交互器引发的错误并返回适当的状态响应。

      交互者:

      module Users
        class Delete
          include Tnt::Interactor
      
          class UserNotFoundError < ApplicationError; end
      
          def call(report_id)
            deleted = UserRepository.new.delete(report_id)
      
            fail_with!(UserNotFoundError) unless deleted
          end
        end
      end
      

      控制器:

      module Api::Controllers::Users
        class Destroy
          include Api::Action
          include Api::Halt
      
          params do
            required(:id).filled(:str?, :uuid?)
          end
      
          def call(params)
            halt 422 unless params.valid?
      
            Users::Delete.new.call(params[:id])
          rescue Users::Delete::UserNotFoundError => e
            halt_with_status_and_error(404, e)
          end
        end
      end
      

      fail_with!halt_with_status_and_error 分别是我的交互器和控制器常用的辅助方法。

      # module Api::Halt
      def halt_with_status_and_error(status, error = ApplicationError)
        halt status, JSON.generate(
          errors: [{ key: error.key, message: error.message }],
        )
      end
      
      # module Tnt::Interactor
      def fail_with!(exception)
        @__result.fail!
        raise exception
      end
      

      【讨论】:

        【解决方案3】:

        Hanami 方式是在每个请求处理程序之前验证输入参数。因此,必须始终在动作逻辑之前识别 ClientError。

        halt 400 unless params.valid? #halt ClientError
        #your code
        result = users_show_interactor(id: params[:id])
        halt 422 if result.failure? #ServerError
        halt 404 unless result.user
        @user = result.user
        

        【讨论】:

        • 谢谢。似乎是对的。但是参数验证只能处理 BadRequest。如何通过交互器内部引发的错误返回不同的状态代码?当result.failure? 为真时,我必须知道它因数据库错误或RecordNotFound 而失败,无法返回正确的状态码。我想知道从代码中隐藏这种复杂性的好方法。
        • 在包装器上引发错误不是一个好主意。 nil 比强制退出错误 RecordNotFound 更好。当然,你可以做单halt handle_error,但没有理由做会失败的事情。
        猜你喜欢
        • 1970-01-01
        • 2019-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-28
        • 1970-01-01
        • 1970-01-01
        • 2014-03-09
        相关资源
        最近更新 更多