- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
本文是 Node.js 最佳实践仓库 nodebestpractices 中「项目架构实践」系列的核心篇章,对应其中 1.1 按业务组件构造解决方案 这条最佳实践。指南将带你理解为什么按技术角色分组(controllers/services/models)会让中型以上的应用快速腐化为"依赖地狱",以及如何把整个技术栈切分为自包含(self-contained)的业务组件——每个组件拥有独立的 API、领域逻辑、数据访问与测试,其他组件只能通过其公共接口消费能力。读完本文,你将获得一套可落地的目录组织范式、组件内三层分层模型,以及通往微服务架构的演进路径。
一、为什么中型以上的单体应用必然走向"代码面条"
对于中型及更大规模的应用,把一切写在一起的单体(monolith)是真正糟糕的选择:一个承载了大量相互依赖的巨型软件,几乎无法被人的心智可靠地推理,久而久之必然滑向 spaghetti code(代码面条)。
这里要澄清一个常见误区:问题不在于"模块化"本身。即使是那些技艺高超、能够驯服这头野兽并把它"模块化"的资深架构师,也仍然要为设计付出巨大的心智成本——因为每一次改动都需要仔细评估它对其他所有依赖对象的影响面。问题在于:即便做了模块化,只要模块之间仍然共享文件、相互渗透,整个系统的认知负担就不会下降。
因此,终极方案不是"把大软件管理好",而是开发小软件:把整个技术栈拆分成一个个不与其他组件共享文件的独立组件,每个组件只包含很少的文件(如 API、服务、数据访问、测试等),使任何一个组件都可以被单独地、低负担地理解和修改。
引用 Martin Fowler 在其微服务文章中的观点,可以更直接地看到单体的代价:
单体应用可以成功,但越来越多的人对它们感到失望——尤其是当更多应用被部署到云端时。变更周期被绑定在一起:对应用一小部分的修改,要求整个单体被重新构建和部署。随着时间推移,往往很难维持良好的模块结构,这使得"本应只影响某个模块的改动"很难被限制在该模块内部。扩展要求扩展整个应用,而不是扩展其中真正需要更多资源的那一部分。
这条引用的两个痛点正是组件化要解决的核心问题:部署耦合(小改动触发全量重建)与扩展错配(无法针对热点模块单独扩容)。
二、理解微服务:原则而非规范
有人会把这种架构称为"微服务"(microservices)。需要特别强调的是:微服务不是一份你必须逐条遵守的规范(spec),而是一组原则(principles)。你可以把全部原则采纳进一个完整的微服务体系,也可以只采纳其中少数几条——只要软件复杂度被保持在低水平,两种做法都是可取的。
这给团队提供了一个务实的决策空间:
- 不必一开始就上全套微服务:服务发现、分布式追踪、独立部署流水线等重型基础设施,会为小团队带来不成比例的复杂度;
- 但组件边界是底线:你至少应该做到在组件之间建立基本边界——在项目根目录为每个业务组件分配一个文件夹,让它完全自包含,并规定其他组件只能通过该组件的公共接口或 API 消费其功能。
这一底线是让组件保持简单的基础:它避免了依赖地狱(dependency hell),并在应用规模增长后,为演进到完整微服务铺平道路——因为届时每个组件目录天然就是候选的独立服务。
三、两种目录组织方式的对比
最佳实践通过两张结构示意图直观对比了"推荐"与"避免"两种做法,两图均出自本仓库 assets/images 目录,原图见 breakintcomponents.md。
推荐:按自包含组件构造解决方案
按业务组件划分的推荐目录结构
这种结构下,每个业务组件(如 orders、users、payments)在项目根目录拥有自己的文件夹,组件内部再按 API、服务、数据访问等细分,组件之间不共享文件。
避免:按技术角色分组文件
按技术角色分组的反面目录结构
这种结构把所有 controller 放一起、所有 service 放一起、所有 model 放一起,表面上"整齐",实际上任何一个功能的改动都会牵动三个平行目录,跨模块的隐性依赖随之蔓延。
仓库在 breakintcomponents.md 中给出了两种目录树的完整示意代码,可以更精确地说明差异:
# 推荐:按业务组件组织(apps 为组件,libraries 为跨组件通用能力) my-system ├─ apps (components) │ ├─ orders │ │ ├─ package.json │ │ ├─ api │ │ ├─ domain │ │ ├─># 避免:按技术角色分组 my-system ├─ controllers │ ├─ user-controller.js │ ├─ order-controller.js │ ├─ payment-controller.js ├─ services │ ├─ user-service.js │ ├─ order-service.js │ ├─ payment-service.js ├─ models │ ├─ user-model.js │ ├─ order-model.js │ ├─ payment-model.js注意推荐结构中专门划出了一个libraries目录,用于存放logger、authenticator 这类跨组件通用能力。这与仓库中的另一条最佳实践 将公共工具封装为 npm 包 一脉相承:当不同组件(甚至不同服务器上的组件)都需要同一份工具代码时,正确做法是把第三方工具包装进自己的代码中、发布为私有 npm 包,让所有组件通过依赖管理工具消费同一份代码,而不是互相复制文件。这也正是"组件不共享文件"原则的落地方式——通用代码以包的形式共享,业务代码以组件隔离。
四、组件内部的三层分层:Entry-points / Domain / Data-access
有了组件边界之后,组件内部如何组织?仓库的另一条最佳实践 createlayers.md 给出了推荐范式:每个组件的根部应包含代表每次事务公共关注点与阶段的三个文件夹:
my-system ├─ apps (components) │ ├─ component-a │ ├─ entry-points │ │ ├─ api # controller 放在这里 │ │ ├─ message-queue # 消息消费者放在这里 │ ├─ domain # 特性与流程:DTO、服务、业务逻辑 │ ├─>// app.js / app.ts:API 声明 const app = express(); app.use(bodyParser.json()); app.use('/api/events', events.API); app.use('/api/forms', forms);// bin/www:网络层声明 const app = require('../app'); const http = require('http'); // 从环境变量获取端口并存入 Express const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); // 创建 HTTP 服务器 const server = http.createServer(app);这样分离带来的直接收益包括:可以在进程内测试 API 而无需发起真实网络调用(从而获得更快的测试执行与准确的代码覆盖率指标)、同一个 API 可以灵活部署到不同的网络条件下,以及更清晰的关注点分离与更干净的代码。这与组件化"最小边界、公共接口消费"的思想完全同构——app 是组件,server 是边界。
六、落地路径与演进建议
综合本仓库 项目架构实践章节 的六条最佳实践,按业务组件组织结构的落地路径可以概括为三步:
- 建立边界:在项目根目录为每个业务组件分配独立文件夹,组件之间禁止共享文件,只通过公共接口/API 相互消费;
- 组件内分层:每个组件按 entry-points / domain />赞
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
Node.js 最佳实践:按业务组件组织项目结构,摆脱依赖地狱
Node.js 最佳实践:按业务组件组织项目结构,摆脱依赖地狱 导读 本文基于开源项目 nodebestpractices https://link.gitco
文档教程后端Node.js 组件式项目结构:按业务组件拆分解决方案(nodebestpractices 实践指南)
Node.js 组件式项目结构:按业务组件拆分解决方案(nodebestpractices 实践指南) 本文是 nodebestpractices https:
文档教程后端Agent-SRE 实践指南:面向 AI Agent 的可靠性工程工具包深度解析
Agent SRE 实践指南:面向 AI Agent 的可靠性工程工具包深度解析 本文以 agent governance toolkit 仓库中的 agent
文档教程后端