在单体 Node.js 中,DAL 应按实体拆分为独立 DAO 模块(如 user.dao.js),每个模块仅封装对应数据的 CRUD,不掺杂业务逻辑;统一在 db/index.js 管理连接池,DAO 通过 require 复用连接,事务由上层传入 client;导出纯函数对象,使用参数化查询防注入,严禁反向依赖和服务层耦合。

在单体架构的 Node.js 后端服务中,用 CommonJS 划分数据访问层(Data Access Layer, DAL)模块,核心是按“实体”或“业务域”组织文件,每个模块只负责一类数据的增删改查,不掺杂业务逻辑,也不直接暴露数据库连接细节。
按数据实体拆分模块
每个数据库表或聚合根对应一个独立的 .js 文件,例如:
- user.dao.js:封装用户相关的数据库操作(如 findById、create、updateByEmail)
- order.dao.js:处理订单的查询、状态更新、分页列表等
- product.dao.js:商品信息读取、库存扣减、分类查询等
每个文件导出一个对象,方法内部只调用数据库驱动(如 pg、mysql2、mongoose)或 ORM 方法(如 Sequelize 的 model),不写 if/else 业务分支,也不做权限校验。
统一管理数据库连接与事务
避免每个 DAO 模块重复创建连接。推荐做法:
立即学习“Java免费学习笔记(深入)”;
- 在 db/index.js 中初始化连接池(如 pg.Pool)或 ORM 实例,并导出
- 所有 DAO 模块通过 require('./db') 获取共享连接,而非自行 new Client()
- 需要事务时,由上层服务模块传入 client 或 transaction 对象,DAO 接收并使用,不主动开启/提交
导出方式保持清晰一致
推荐统一使用 module.exports = { ... } 导出命名函数,便于解构导入和单元测试:
// user.dao.js
const db = require('../db');
<p>module.exports = {
findById(id) {
return db.query('SELECT <em> FROM users WHERE id = $1', [id]);
},
findByEmail(email) {
return db.query('SELECT </em> FROM users WHERE email = $1', [email]);
},
create(userData) {
const { name, email } = userData;
return db.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[name, email]
);
}
};上层服务模块可直接解构使用:const { findById, create } = require('./user.dao');
避免常见陷阱
CommonJS 下容易忽略的细节:
- 不要在 DAO 模块里 require 服务层或路由模块——这会倒置依赖,破坏分层
- 不要把 SQL 字符串拼接进 DAO 方法参数,防止注入;用参数化查询($1, $2)或 ORM 安全方法
- 不导出 class 实例,而导出纯函数对象;若需复用配置,可用工厂函数包裹,但默认导出仍是普通对象
- 错误不吞掉:DAO 层抛出的错误应保留原始上下文(如 query 失败时附带 SQL 和参数),方便上层判断重试或降级


















