Sushi SaaS 的代码结构
理解 starter 强制执行的分层、背后的取舍,以及如何添加功能而不引入第二套架构。
本指南面向正在判断第一个产品功能应放在哪里的采用者。Sushi SaaS 的所有业务域共用一套水平架构,并由测试强制执行。
一次请求经过的路径
src/app/** 路由与页面——接收并返回 HTTP
↓
src/services/** 业务规则、编排与不变量
↓
src/models/** 类型化持久化;唯一可调用 db() 的层
↓
src/db/** schema、migration 与连接路由负责解析与响应,服务判断操作是否允许并协调副作用,模型负责数据库读写,数据库约束则是并发请求的最后一道保护。
浏览器侧还有一条边界:
Server Component → 直接调用 service
Client Component → src/api/** → 公共 API client → /api/**这样既避免服务端对自身发起 HTTP 请求,也让客户端错误统一遵循一个响应契约。完整规则见架构与错误契约。
为什么选择水平分层
计费、积分、存储、预约和任务都需要认证、租户作用域、错误处理和持久化。统一分层能让每项职责的位置可预测,并让架构测试发现不经意的捷径。
代价是一个功能会分散在多个目录。大型产品中,垂直 feature 目录可能更容易浏览;但同时混用水平与垂直结构比任一种都更差,因为下一条查询或业务规则会有两个合理位置。
你的选择
- 保留现有结构:适合希望用强约定共享基础设施的小团队。
- 完整迁移为垂直切片:如果这已经是团队标准,可以移动完整业务域并更新
tests/unit/architecture.test.ts;不要在原规则旁新增孤立的src/features。 - 为短期原型简化:前提是接受之后的迁移。如果路由直接写数据库,应明确修改或移除测试,而不是逐个增加例外。
租户模型也是架构选择
每位用户都会获得个人组织,因此个人客户与团队共用组织作用域下的计费、积分、文件和限额,不需要同时维护“归用户”和“归团队”两套查询。
如果未来可能有团队,建议保留。如果产品确定永远是单用户,移除组织也合理——但这会同时影响授权、schema、模型、计费、存储和测试,并非一个 UI 开关。决策前请阅读组织与团队。
为什么管理后台是独立应用
apps/admin 有自己的页面、管理 API、数据访问、MFA 门禁和运营角色。它共享 schema 和认证数据,但可运行在不同 origin。代价是多一次构建与部署,收益是清晰的运营边界。
如果团队更希望单次部署,可以把它合并到客户应用,但必须保留服务端管理授权和审计规则;仅隐藏导航不构成安全边界。先查看管理后台设置。
添加第一个业务域
- 在
src/types与src/config定义产品词汇和限额。 - 在
src/db添加 schema 与可安全扩展的 migration。 - 把类型化 CRUD 放入
src/models。 - 把授权、幂等和副作用放入
src/services。 - 在
src/app添加路由或 Server Component;客户端调用使用src/api。 - 测试 service 规则、路由认证门禁,以及资金或积分操作的重放行为。
通过 capability 检查功能,不要直接比较套餐名称;契约变化时同步更新仓库 runbook。本文基于 starter commit 7580470。