这是提供企业级软件项目文件结构布局指南,通过严格分离纯业务逻辑与基础设施代码,实现高可维护性与可测试性,并支持按项目规模灵活简化。点击标题
项目结构终极参考
刚打开一个新项目,脑袋里嗡的一声——“这代码我该往哪儿放?” 文件夹一层套一层,文件名起得神神秘秘,改一行代码要翻三四个目录,最后干脆新建个 tmp.ts 先写着,结果三天后连自己都找不到那行救命逻辑了?
大型软件项目就像盖摩天大楼,地基打歪了,再牛的程序员也救不回来。
这份布局指南就是那个帮你把钢筋水泥摆对位置的施工图,专治各种“代码乱葬岗”晚期患者。
为什么要有这个布局?
写代码不是写日记,不能想到哪写到哪。当项目从一个人的小玩具变成十个人一起维护的企业级系统,混乱的结构会像病毒一样传染——今天你把数据库操作塞进业务逻辑,明天他把 HTTP 处理混进核心算法,后天新人接手直接原地爆炸。
这套布局的核心使命就一条:让每一行代码都知道自己该待在哪个“房间”,绝不串门乱跑。它不是强迫症的产物,而是用无数个深夜加班和线上事故换来的血泪经验包,专治“改一处崩三处”的绝症。
核心思想
这套结构最狠的地方在于它把代码世界划成了两个平行宇宙:干净的纯逻辑宇宙(core) 和 脏兮兮的现实世界接口宇宙(infrastructure)。
想象你是个天才科学家,只负责发明永动机原理(这就是 core),而你的助手团队负责买零件、接电线、防雷击、应付环保局(这就是 infrastructure)。科学家永远不用碰螺丝刀,助手也绝不干涉公式推导。
src/ core/ inbound/ infrastructure/
|
这种隔离让核心逻辑像数学定理一样稳定可靠,不管外面是用 MySQL 还是 MongoDB,是 HTTP 还是 WebSocket,永动机原理纹丝不动。测试的时候更爽——不用启动数据库,不用连网络,直接给纯函数喂数据,秒出结果,稳如老狗。
完整包结构
. // 项目根目录 ├── css // 主样式文件夹,由Tailwind CLI自动生成 │ └── style.css // 主样式文件 ├── db // 数据库相关文件夹,用纯SQL写数据库结构(跟源代码分开) │ ├── core // 核心数据库结构,所有环境都用这个;支持一个业务领域用多个数据库 │ │ ├── cache // 缓存数据(丢了能重建,可有可无) │ │ │ └── migrations // 比如:AI回复的缓存、会话查询表 │ │ ├── var // 持久化数据(真正的数据,不能丢) │ │ │ ├── migrations // 比如:用户信息、交易记录 │ │ │ └── seeds // 种子数据(初始数据) │ │ │ └── user-roles.sql // 用户角色初始数据 │ │ └── tmp // 临时数据(几秒到几分钟就过期) │ │ └── migrations // 比如:限流计数器、临时锁、离线锁 │ ├── environments // 特定环境的数据库扩展 │ │ ├── project-E // 项目E的数据库扩展 │ │ │ ├── migrations // 项目E特有的扩展表 │ │ │ │ └── 001_project_e_extensions.sql │ │ │ └── seeds // 项目E的初始数据 │ │ │ └── default_admin_user.sql // 默认管理员账号 │ │ └── customer-F // 客户F的数据库扩展 │ │ ├── migrations │ │ └── seeds │ └── scripts // 数据库维护脚本(备份、恢复、数据脱敏) ├── scripts // 运维和自动化脚本(bash/python/ts) │ └── ... // 其他脚本 ├── src // 源代码 │ ├── index.ts // 运行时调度器(选哪个项目启动;不推荐用,因为没有进程隔离) │ ├── environments // 组合根目录(每个客户/租户/开发配置一个文件夹);依赖基础设施层和核心层 │ │ ├── shared // 跨环境共享的库(配置逻辑太具体,不适合放核心层) │ │ │ └── templates // 所有环境共用的模板 │ │ │ ├── sms-notification.eta // 短信通知模板 │ │ │ ├── email-body-text.eta // 邮件正文纯文本版 │ │ │ └── email-body-html.eta // 邮件正文HTML版 │ │ ├── project-E // 项目E的环境配置 │ │ │ ├── delivery // 项目E特有的UI/协议覆盖 │ │ │ │ ├── templates // 项目E独有的Eta模板 │ │ │ │ ├── http // 项目E特有的路由定义 │ │ │ │ │ ├── controllers // 控制器 │ │ │ │ │ └── route.ts // 路由文件 │ │ │ │ └── websocket // WebSocket配置 │ │ │ ├── env.ts // 项目E的配置实现 │ │ │ └── index.ts // 接线文件:启动项目E的业务领域+基础设施+环境配置 │ │ ├── customer-F // 客户F的环境配置 │ │ │ ├── env.ts │ │ │ └── index.ts │ │ └── dev-shell // 开发环境,当成一个项目来处理;包含开发工具UI │ │ ├── infrastructure │ │ │ ├── reload.ts // 热重载逻辑 │ │ │ ├── time-mock.ts // 测试用的模拟时间 │ │ │ └── watch.ts // 文件监听器配置 │ │ ├── delivery // 交付层 │ │ │ └── overview.eta // 开发状态面板模板 │ │ ├── env.ts // 仅开发用的配置 │ │ └── index.ts // 接线文件:启动目标项目+开发工具 │ ├── infrastructure // 实现细节;依赖核心层和外部库(SQLite、AWS S3、文件系统等) │ │ ├── shared // 通用技术能力 │ │ │ ├── time // 横切关注点(到处都用) │ │ │ │ └── time-real.ts // 系统时钟实现 │ │ │ ├── log // 横切关注点 │ │ │ │ └── log-winston.ts // 日志实现 │ │ │ ├── inbound // 输入传输(入口) │ │ │ │ ├── http // Express/Fastify设置,全局中间件 │ │ │ │ ├── cli // 命令行参数解析逻辑 │ │ │ │ ├── websocket // Socket.io/ws服务器设置 │ │ │ │ └── tcp // 原始net.Server逻辑 │ │ │ ├── outbound // 输出传输(出口) │ │ │ │ ├── persistence // 数据存储适配器 │ │ │ │ │ ├── sqlite // SQLite适配器 │ │ │ │ │ ├── mongodb // MongoDB适配器 │ │ │ │ │ └── s3 // S3适配器 │ │ │ │ └── gateways // 外部服务客户端 │ │ │ │ ├── smtp // NodeMailer / SES包装器 │ │ │ │ ├── sms // Twilio / MessageBird包装器 │ │ │ │ └── push // Firebase / 苹果推送逻辑 │ │ │ ├── serialization // 转换层(翻译器) │ │ │ │ ├── templates // 模板引擎设置 │ │ │ │ │ └── parent.eta │ │ │ │ ├── json // 自定义解析器(处理日期、大整数) │ │ │ │ ├── protobuf // Proto定义和编码器 │ │ │ │ ├── xml // XML格式化器 │ │ │ │ ├── html // HTML清理/格式化 │ │ │ │ └── formatters // 技术格式化(比如字节转MB) │ │ │ └── security // 认证、JWT、加密驱动 │ │ ├── bounded-context-A // 业务领域A的基础设施(应用服务) │ │ │ ├── app.ts // 该领域的入口 │ │ │ ├── persistence // 仓库实现(原始SQL、ORM等) │ │ │ │ └── customer-repository.ts // 实现核心层的接口 │ │ │ ├── queries // 查询实现(原始SQL、ORM等) │ │ │ │ └── customer-queries.ts │ │ │ ├── commands // 写操作编排 │ │ │ │ └── customer-evaluation.ts │ │ │ └── scripts // 领域A特有的维护脚本 │ │ ├── bounded-context-B // 业务领域B的基础设施 │ │ └── runner.ts // 应用编排器 │ └── core // 纯业务逻辑,没有副作用;不依赖任何东西(除了少量工具函数) │ ├── shared-kernel // 共享领域(多个业务领域共用的业务逻辑;少用) │ │ ├── types.ts // 共享值对象(比如货币、邮箱) │ │ └── constants.ts // 常量 │ ├── bounded-context-A // 纯领域逻辑(领域A) │ │ ├── events // 比如:用户注册、订单下单 │ │ ├── queries // 读模型 │ │ └── domain // 写模型(实体、值对象) │ │ └── customer.ts │ ├── bounded-context-B // 纯领域逻辑(领域B) │ │ └── ... │ ├── utils.ts // 通用TS辅助函数 │ └── utils.test.ts // 工具函数测试 ├── static // 静态文件 │ ├── dev // 开发用的浏览器文件(比如dev-ws.js) │ └── main // 生产环境资源(图片、编译后的CSS/JS) │ └── style.css ├── test-integration // 跨领域测试(端到端流程) │ ├── project-e.test.ts // 测试完整装配好的项目E │ └── xyz.test.ts ├── tmp // 本地开发文件(版本控制忽略) │ ├── dev.state.db // 本地沙盒数据库 │ └── ... ├── eslint.config.mjs // ESLint配置 ├── package.json // 包配置 ├── prettier.config.cjs // Prettier配置 ├── tailwind.config.ts // Tailwind配置 ├── tsconfig.json // TypeScript配置 └── yarn.lock // Yarn锁定文件
|
主要文件夹说明(大白话)
先看 db 文件夹,这里存的是数据库的“户型图”。core 子目录放所有客户通用的基础表结构,比如用户表、订单表这些谁都要用的标配;environments 则是 VIP 客户的定制装修方案,比如某银行需要额外加个风控字段,某电商要多存个优惠券历史,全塞这儿互不干扰;scripts 里躺着各种数据库急救包——一键重建测试库、紧急清理垃圾数据、半夜自动备份,全是保命用的硬核脚本。记住,db 里只有“图纸”,没有一行代码,真正的建表操作由 infrastructure 里的工具执行。
scripts 文件夹是项目的“自动化管家”。部署新版本?运行 scripts/deploy.sh。生成 API 文档?敲 scripts/gen-docs.sh。甚至每天凌晨三点自动给老板发周报?也能塞这儿。所有重复性体力活全交给脚本,人类只负责动脑子。这玩意儿看着不起眼,但团队效率能翻倍——新人第一天入职,不用问“怎么打包”,直接 ./scripts/build 就完事,省下八百个微信群消息。
src 才是真正的战场,分三大战区。inbound 是项目的“外交部门”,专门处理外界骚扰:HTTP 请求来了转成内部格式,WebSocket 消息到了解包成事件,命令行指令输入了翻译成操作码。这里只干一件事:把五花八门的外部信号统一成项目内部能听懂的普通话,绝不掺和业务判断。比如用户注册请求进来,inbound 只负责检查 JSON 格式对不对,邮箱是不是字符串,密码长度够不够——至于密码强度规则?那是 core 的事,inbound 连问都不问。
infrastructure 是“后勤保障部”,负责跟现实世界肉搏。数据库连接池在这儿管理,Redis 缓存策略在这儿配置,文件上传到 AWS S3 的代码也藏这儿。所有带副作用的操作——读写磁盘、调用第三方 API、发短信——全归 infrastructure 管。它像个翻译官,把 core 的纯逻辑指令(比如“保存用户”)转换成具体的数据库 INSERT 语句,再把数据库返回的原始数据包装成 core 能理解的对象。关键原则:infrastructure 可以依赖 core,但 core 绝不反向依赖 infrastructure,保证核心逻辑永远干净。
core 是项目的“大脑中枢”,只存放最纯粹的业务规则。用户密码必须包含大小写字母和数字?订单金额不能为负数?优惠券只能用一次?这些铁律全写在 core 的 domain 实体里。这里的代码有个变态要求:不能 import 任何外部库,不能调用任何 I/O 操作,甚至不能获取当前时间(时间必须作为参数传入)。听起来反人类?但正是这种极端洁癖,让核心逻辑能像乐高积木一样随意组合测试——想验证密码规则?直接 new User("test", "weak"),看它抛不抛异常,0.001 秒出结果,不用等数据库连接。
static 文件夹简单粗暴,就是前端资源的垃圾桶。CSS、JS、图片、字体文件全扔这儿,构建工具会自动压缩合并。test-integration 则是项目的“压力测试场”,专门模拟真实用户操作流程:从 HTTP 请求入口开始,一路穿透 inbound → infrastructure → core → infrastructure → 数据库,最后验证返回结果是否符合预期。这种端到端测试虽然慢,但能抓住各模块衔接时的隐藏 bug,比如 core 返回了正确数据,但 infrastructure 忘了序列化某个字段。
一个真实流程长啥样?
用户点下“注册”按钮的瞬间,一场精密的接力赛就开始了。
HTTP 请求先撞进 src/infrastructure/inbound/http/controllers/auth.ts,这里像安检门一样扫描请求格式,把 JSON 转成内部 DTO 对象。
接着接力棒传给 src/infrastructure/bounded-context-A/app.ts,这个协调器像乐队指挥,调用 core 里的用户实体进行规则校验。
src/core/bounded-context-A/domain/user.ts 接过棒,冷酷执行“密码必须8位以上含特殊字符”的铁律——如果不合格,直接抛异常中断流程。
校验通过后,协调器把干净的用户对象塞给 src/infrastructure/bounded-context-A/persistence/user-repository.ts,这里才真正接触数据库,把对象转成 SQL 插入。
最后结果沿原路返回,HTTP 控制器吐出 201 Created。
全程 core 不知道有 HTTP,数据库不知道有密码规则,各司其职,稳如泰山。
总结一下
这套布局的本质是用空间换秩序。多建几个文件夹,多写几层转发,换来的是代码的绝对清晰和可维护性。当项目膨胀到百万行代码时,新人依然能靠目录结构猜出功能位置;当需要替换数据库时,只需重写 infrastructure 层,core 逻辑纹丝不动;当老板突然要加个 CLI 命令行入口,直接在 inbound 下新建个 cli 目录,其他代码完全不用动。它不是银弹,但绝对是大型项目的防弹衣——穿上可能有点闷,但能让你在需求海啸中活下来。
如何折叠层次结构(按比例缩小)
别被吓到!这套结构像变形金刚,能大能小。如果你在搞个人小项目,用下面四招立刻瘦身:
第一招:单租户模式直接砍掉 environments。所有环境配置一股脑塞进 src/app,省去三层嵌套。
第二招:CRUD 项目大胆合并 core 和 infrastructure。比如用户模块直接搞个 src/modules/users/service.ts,里面既有业务规则又有数据库操作——牺牲一点纯洁性,换来开发速度起飞。
第三招:纯 HTTP 项目干掉 inbound 目录。Express 或 Fastify 的路由配置直接写在 src/app.ts,少一层抽象多十分痛快。
第四招:用 TypeORM 这种同步神器?db/migrations 可以删了,让 ORM 自动建表,但记得保留 db/seeds 存测试数据。
记住,架构是仆人不是主人,该简化时就简化,别被教条绑架。
顺口溜快板:
代码分区如军营,纯脏分离保太平;
入口基建各司职,核心逻辑永纯净,永纯净!