返回专题首页

Node.js 专题

一个 Node API 应该怎么拆:路由层、服务层、仓储层与 DTO 边界

Node.js 服务端项目最容易越写越乱的地方,不是框架本身,而是职责边界。参数解析、业务规则、数据库访问、响应组装如果都堆在路由函数里,项目规模一上来很快就失控。这一篇会把常见分层方式放到同一个语境里说明白。

Node.js 专题第 27 篇 / 40 篇5 分钟

Node.js 服务端项目最容易越写越乱的地方,不是框架本身,而是职责边界。参数解析、业务规则、数据库访问、响应组装如果都堆在路由函数里,项目规模一上来很快就失控。这一篇会把常见分层方式放到同一个语境里说明白。

为什么路由函数很容易失控?

因为路由是请求最先进入的地方,天然很容易变成“顺手什么都写一点”的位置。最开始看起来很方便,但只要业务稍微复杂,就会变成下面这种状态:

  • 参数解析在这里;
  • 权限判断在这里;
  • 数据库操作在这里;
  • 错误处理也在这里;
  • 响应包装还在这里。

这样一来,任何改动都会牵一发动全身。问题不在于某一层写错了,而在于所有层都挤在一起了。

更麻烦的是,一旦这些职责混在一起,很多“暂时方便”的写法会快速固化成项目默认姿势。到了后面你想补测试、做复用、统一错误处理或替换存储实现时,就会发现每个接口都像一小段独立脚本,彼此之间很难形成稳定协作。

常见分层到底在分什么?

一个更成熟的最小分层,通常至少会把下面几件事区分开:

  • 路由层:负责接收请求和返回响应;
  • 服务层:负责业务规则和流程编排;
  • 仓储层:负责和数据库或持久化实现协作;
  • DTO / Schema:负责输入输出结构边界;
  • 实体或领域模型:负责内部业务数据含义。

这并不是说所有项目都必须层层齐全,而是说你要知道这些职责天然是不同的。如果不分开,复杂度迟早会在代码结构里爆出来。

其中一个关键点是:路由层和服务层的边界越清楚,后面很多能力越容易接入。比如日志、鉴权、参数校验更适合靠近入口;业务规则和流程编排更适合放在服务层;数据库读写细节则应该尽量被仓储层吸收。边界一稳,整个项目才不会每次改需求都牵动整条链路。

DTO、Schema 和实体模型为什么容易混?

因为它们都在“描述数据”,但描述的是不同视角的数据:

  • DTO / Schema 更偏接口输入输出契约;
  • 实体模型更偏业务内部语义;
  • 仓储层模型更可能贴近数据库结构。

如果把这几层完全混成一份对象,短期会省事,长期则会让接口、业务和存储强耦合。到那时,任何一侧变化都会拖动其他所有层。

现实项目里最常见的问题,是数据库表结构一改,接口返回跟着变;或者前端多要一个字段,结果服务内部实体也被迫调整。表面上看像是“少写了几份类型”,本质上却是在用耦合换短期速度。

所以,哪怕不追求教科书式分层,也应该尽量承认这些模型面对的是不同对象:

  • DTO / Schema 面向调用方;
  • 实体和业务对象面向内部规则;
  • 仓储模型更贴近持久化实现。

分层是不是越细越好?

也不是。分层的目标从来不是“让架构图看起来高级”,而是让协作边界更清楚。如果一个项目还很小、问题也很直接,那么层数过多本身也会制造成本。

所以,更稳妥的原则通常是:

  • 先拆开真正职责不同的部分;
  • 只在复杂度真的上升时再继续细分;
  • 不为了抽象而抽象。

也就是说,分层不是模板,而是对复杂度的回应。

一个很好用的判断标准是:当前这层是否真的帮你减少了重复、降低了耦合、提升了替换能力?如果答案不是很明确,那它可能只是把简单问题包装得更复杂了。对小项目来说,保持两三层清楚边界,通常比堆出一大串“看起来很完整”的目录更有意义。

一个更稳妥的 API 拆分顺序是什么?

如果你正在从零搭一个 Node API,与其先纠结目录树漂不漂亮,不如先按下面的顺序建立边界:

1. 先把入口层管住,让路由只负责接收和响应;2. 再把业务规则抽到服务层,避免逻辑散落在接口里;3. 然后把数据库细节收敛到仓储层,减少持久化泄漏;4. 最后再按输入、输出和内部模型区分数据结构。

这样拆的好处是,每一步都在回应真实复杂度,而不是为“架构感”提前铺很多暂时用不上的层。

总结

这一篇我们把 Node API 项目最核心的结构问题拉清楚了:路由层不是万能入口,服务层不该和仓储层混在一起,接口模型和业务模型也不该想当然地完全共用。

分层真正的价值不在于层数,而在于边界。边界清楚,项目才更容易协作、测试和长期维护。

下一篇我们会继续顺着边界问题往下走,讨论接口契约层最关键的资源命名、状态码和统一响应结构。