SSF0SSF0
首页
前端
  • Node
  • Go
  • C#
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
Timeline
Github
标签
分类
首页
前端
  • Node
  • Go
  • C#
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
Timeline
Github
标签
分类
  • Node.js

    • 在 Docker 容器中运行 Node.js Koa 应用的网络配置与常见问题
    • 在 Node.js 后端实现邮箱验证码功能
    • Redis 与 JWT 结合的 Token 验证方案
    • Node.js 后端三层架构中的错误处理最佳实践
    • 在 Koa 中批量设置路由 JWT 校验的最佳实践
  • Go

    • Go1
    • Go2
  • MySql

    • 数据库连接

Node.js 后端三层架构中的错误处理最佳实践

引言

在构建可靠的后端系统时,错误处理是提高系统稳定性和可维护性的关键环节。本文将详细分析基于仓库(Repository)、服务(Service)和控制器(Controller)三层架构中的错误处理策略,通过实际代码示例展示如何实现清晰、一致的错误处理流程。

三层架构中的错误处理责任

仓库层(Repository)

主要职责:

  • 捕获原始数据库错误

  • 记录详细的技术错误信息

  • 转换为更具体的技术错误

  • 通过抛出异常向上传递

示例实现:

public static async getTimeline(userId: number): Promise<TimelineDB[]> {
  try {
    const [rows] = await pool.query<RowDataPacket[]>(
      "SELECT * FROM timeline WHERE userId = ?",
      [userId]
    );
    return rows.map((row: TimelineDB) => ({
      id: row.id,
      userId: row.userId,
      content: row.content,
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    }));
  } catch (err: any) {
    console.error("数据库查询错误:", err);
    throw new Error(`获取时间线数据失败: ${err.message}`);
  }
}

服务层(Service)

主要职责:

  • 捕获仓库层错误

  • 执行业务逻辑验证

  • 转换为业务领域错误

  • 添加业务上下文信息

  • 通过抛出异常向上传递

示例实现:

public static async getTimelineData(userId: number): Promise<TimelineDB[]> {
  try {
    const timeline = await TimelineRepository.getTimeline(userId);

    if (!timeline || timeline.length === 0) {
      throw new Error("未找到时间线数据");
    }

    return timeline;
  } catch (err: any) {
    if (err.message.includes("获取时间线数据失败")) {
      throw new Error(`获取时间线失败: ${err.message}`);
    }
    throw err;
  }
}

控制器层(Controller)

主要职责:

  • 捕获所有下层异常

  • 转换为 HTTP 响应

  • 设置合适的状态码

  • 返回统一格式的错误信息给客户端

示例实现:

public static async getTimelineData(ctx: Context): Promise<void> {
  try {
    const userId = ctx.state.user.id;
    const timeline = await TimeLineService.getTimelineData(userId);
    ctx.body = success(timeline, "获取时间线数据成功");
  } catch (err: any) {
    ctx.body = error(err.message, 500);
  }
}

错误处理的核心原则

1. 层级分明的错误转换

每一层应该对接收到的错误进行适当转换,使其符合当前层的抽象级别:

  • 仓库层:数据库错误 → 数据访问错误

  • 服务层:数据访问错误 → 业务领域错误

  • 控制器层:业务领域错误 → HTTP 响应错误

2. 错误信息逐层增强

错误在向上传递过程中,应不断增强其语义和上下文信息:

数据库错误: "ER_BAD_FIELD_ERROR"
↓
仓库层错误: "获取时间线数据失败: 字段'content'不存在"
↓
服务层错误: "获取时间线失败: 获取时间线数据失败: 字段'content'不存在"
↓
控制器响应: { success: false, message: "获取时间线失败...", code: 500 }

3. 统一的错误处理模式

在每一层中保持一致的错误处理模式:

  • 使用 try-catch 捕获异常

  • 区分不同类型的错误

  • 记录合适级别的日志

  • 重新抛出增强后的错误

实现最佳实践

仓库层错误处理

try {
  // 数据库操作
} catch (err: any) {
  // 1. 记录详细的技术错误
  console.error("数据库操作错误:", err);

  // 2. 区分错误类型
  if (err.code === "ER_DUP_ENTRY") {
    throw new Error(`数据已存在: ${err.message}`);
  }

  // 3. 提供清晰的技术上下文
  throw new Error(`数据库操作失败: ${err.message}`);
}

服务层错误处理

try {
  // 调用仓库方法
  const result = await repository.method();

  // 业务逻辑验证
  if (!isValid(result)) {
    throw new Error("业务规则验证失败");
  }

  return result;
} catch (err: any) {
  // 1. 区分错误来源
  if (err.message.includes("数据库操作失败")) {
    // 2. 为技术错误添加业务上下文
    throw new Error(`业务操作失败: ${err.message}`);
  }

  // 3. 原样传递业务错误
  throw err;
}

控制器层错误处理

try {
  // 参数验证
  if (!isValidRequest(ctx.request.body)) {
    return (ctx.body = error("请求参数无效", 400));
  }

  // 调用服务
  const result = await service.method();
  return (ctx.body = success(result, "操作成功"));
} catch (err: any) {
  // 1. 确定适当的HTTP状态码
  const statusCode = determineStatusCode(err);

  // 2. 返回统一格式的响应
  ctx.body = error(err.message, statusCode);
}

错误处理中的日志级别

不同层应使用适当的日志级别记录错误:

  • 仓库层:error - 记录详细的技术错误和堆栈

  • 服务层:warn - 记录业务处理异常

  • 控制器层:info - 记录请求处理结果

性能考虑

虽然 try-catch 结构有轻微的性能开销,但在现代 JavaScript 引擎中已经高度优化。相比错误处理带来的系统稳定性和可维护性提升,这种性能影响可以忽略不计:

  1. 错误处理代码路径很少执行

  2. 数据库和网络操作的延迟远大于 try-catch 的开销

  3. 代码的可读性和一致性比微小性能优化更重要

结论

一个良好的错误处理系统应该像过滤器一样,在错误向上传递的过程中,不断过滤技术细节,增强业务语义,最终向客户端返回清晰、友好且安全的错误信息。

通过在仓库、服务和控制器三层中实施一致的错误处理策略,可以显著提高系统的可靠性、可维护性和用户体验。无论是简单应用还是复杂系统,这些原则都能帮助开发团队构建更加健壮的后端服务。

最重要的是,好的错误处理不仅仅是捕获异常,而是将异常转化为有价值的信息,帮助开发者快速定位问题,帮助用户理解发生了什么。

最后更新时间:
贡献者: 何风顺
上一页
Redis 与 JWT 结合的 Token 验证方案
下一页
在 Koa 中批量设置路由 JWT 校验的最佳实践