Ruby多异常处理:服务层与控制器的捕获位置选择及复用方案
异常捕获位置与复用问题解决方案
一、ExampleServiceError 该在哪个层级捕获?
核心判断依据是业务语义与封装需求:
- 如果
SecondExampleService需要对外隐藏底层依赖的细节(比如不想让上层感知到ExampleService的存在),或者需要将底层异常转换为自身业务域的异常,就在SecondExampleService中捕获ExampleServiceError,并重新抛出SecondExampleServiceError。这种方式符合封装原则,上层(控制器)只需要处理当前服务的异常即可。 - 如果
ExampleServiceError需要直接暴露给上层,且SecondExampleService不需要对该异常做任何业务加工,就可以让异常直接冒泡到控制器中捕获。
二、解决控制器复用时代码重复的问题
如果选择在控制器层捕获异常,可通过以下方式避免重复代码:
1. 抽离公共异常处理模块
编写一个包含公共异常处理逻辑的模块,让需要的控制器引入该模块:
module CommonExceptionHandler extend ActiveSupport::Concern included do rescue_from ExampleService::ExampleServiceError, SecondExampleService::SecondExampleServiceError do |e| render json: { error: e.message }, status: :unprocessable_entity end end end
在控制器中使用:
class ExampleController < ApplicationController include CommonExceptionHandler def update SecondExampleService.call end end class AnotherController < ApplicationController include CommonExceptionHandler def create SecondExampleService.call end end
2. 在 ApplicationController 统一处理
如果这些异常是所有控制器都可能遇到的,直接在父控制器中定义全局异常处理逻辑,所有继承它的控制器都会自动复用:
class ApplicationController < ActionController::Base rescue_from ExampleService::ExampleServiceError, SecondExampleService::SecondExampleServiceError do |e| render json: { error: e.message }, status: :unprocessable_entity end end
3. 服务层统一封装异常
回到第一种捕获思路,让 SecondExampleService 把所有内部异常(包括 ExampleServiceError)都包装成自身的 SecondExampleServiceError。这样上层控制器只需要处理这一种异常,就算被多个控制器调用,也无需重复编写多种异常的捕获逻辑。
内容的提问来源于stack exchange,提问作者N0ne
相关产品推荐
相关产品推荐

