You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Express控制器/服务中处理错误?架构优化咨询

方案分析与优化建议

你的核心顾虑非常合理:当前服务层抛出带有statusCode的NotFound错误,确实把HTTP协议相关细节耦合进了本应专注业务逻辑的服务层,违反了单一职责原则。下面分两种思路给出具体分析和优化方案:

一、当前方案的问题

服务层的核心职责是处理业务逻辑与数据交互,它不应该感知HTTP协议的存在——404是HTTP的状态码,属于API接口的表现层范畴,硬编码在服务层的错误类里会导致:

  • 服务层无法跨场景复用(比如后续用该服务给CLI工具或内部消息队列提供数据时,statusCode完全无用);
  • 后续修改HTTP响应规则时,需要改动服务层代码,破坏开闭原则。

二、优化方案1:服务层抛业务错误,中间件做HTTP映射(推荐)

把业务错误和HTTP细节彻底解耦,服务层只抛出描述业务事实的错误,由专门的中间件负责将业务错误映射为HTTP响应。

1. 定义纯业务错误类

去掉HTTP相关的statusCode,用错误名称标识业务类型:

class UserNotFoundError extends Error {
  constructor(message = "用户不存在") {
    super(message);
    this.name = "UserNotFoundError"; // 用name区分不同业务错误
  }
}

2. 修改服务层代码

只关注业务逻辑,抛出业务错误,去掉不必要的try-catch(直接让错误向上传递即可):

findOne: async (userId) => {
  const user = await db.users.findByPk(userId);
  if (!user) {
    throw new UserNotFoundError();
  }
  return user;
};

3. 新增错误映射中间件

在errorResponder之前添加一个中间件,负责把业务错误转换成带HTTP状态码的错误:

const errorMapper = (error, req, res, next) => {
  switch (error.name) {
    case "UserNotFoundError":
      error.statusCode = 404;
      break;
    case "InvalidInputError": // 比如参数验证错误
      error.statusCode = 400;
      break;
    // 其他业务错误类型可以在这里扩展
    default:
      // 未匹配到的错误默认按500处理
      error.statusCode = error.statusCode || 500;
  }
  next(error);
};

4. 保持控制器和原错误中间件不变

控制器只负责参数传递和调用服务,错误依然通过next(error)传递给中间件链。

这种方式的优势:

  • 服务层完全聚焦业务,符合单一职责;
  • HTTP相关逻辑集中在中间件,便于统一维护(比如要修改某个业务错误对应的HTTP状态,只改中间件即可);
  • 业务错误可以在非HTTP场景复用。

三、优化方案2:控制器做空值检查

如果坚持让服务层完全不抛错误,只返回数据或null,可以让控制器负责判断结果并抛出HTTP错误:

修改服务层

findOne: async (userId) => {
  return await db.users.findByPk(userId);
};

修改控制器

findUser: async (req,res,next) => {
  const userId = req.params.userId;
  try {
    const user = await UserService.findOne(userId);
    if (!user) {
      throw new NotFound("User not found"); // 这里抛出HTTP相关错误
    }
    res.status(200).send(user);
  } catch (error) {
    return next(error)
  }
}

这种方式的优缺点:

  • 优点:服务层完全“纯”,只做数据查询;
  • 缺点:控制器会充斥大量重复的空值/错误检查逻辑,如果多个控制器调用同一个服务方法,会产生冗余代码;业务错误的语义不够明确(null可能代表多种情况,不如自定义业务错误清晰)。

总结

更推荐方案1:通过业务错误+中间件映射的方式,既保证服务层的单一职责,又避免控制器的冗余代码,同时让HTTP相关逻辑集中可控。

内容的提问来源于stack exchange,提问作者Andreas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 12:21:31