数据所有权与服务边界
数据从 DB 直取还是经由 API,不应由“技术上能不能访问”决定,而应由数据所有权、业务规则与服务边界决定。
[!info] related notes
- 所属 MOC:
- 相关概念:
- 易混淆概念:
- 相关资源:
数据所有权与服务边界
覆盖范围
这篇笔记回答一个架构问题:
当一个 Web 层既能访问数据库,也能调用后端 API 时,应该直接查 DB,还是通过服务 API 获取数据?
核心结论不是“少一层 HTTP 一定更优”,而是:先判断谁拥有这份数据和业务规则,再决定访问路径。
数据所有权不是“数据存在哪里”
一张表物理上存在 PostgreSQL,并不代表任何能连上 PostgreSQL 的进程都应该直接读写它。
更稳定的判断是:
谁负责:
- 业务不变量
- 权限
- 事务
- 状态机
- 审计
- 并发控制
- 数据演进
↓
谁才是该领域数据的 owner
因此:
Physical storage location
≠
Domain ownership
当前应用自己拥有数据时
如果一个 Next.js 单体应用自己负责完整领域规则:
Next.js App
↓ owns
Goal / Task domain
↓
PostgreSQL
那么 Server Component 可以通过内部 data layer / repository 直接访问 DB:
Server Component
↓
Data / Repository
↓
ORM / SQL
↓
Database
这时如果为了“必须有 API”而额外绕一圈:
Server Component
↓ HTTP
/api/goals
↓
同一个应用里的 DB
可能只是增加协议、序列化与错误处理成本,并没有增加真正的业务边界。
独立服务拥有数据时
如果领域已经由独立后端服务拥有:
Next.js Web / BFF
↓
Go Domain Service
↓
PostgreSQL
而 Go Service 负责:
- 权限与资源 ownership
- 事务
- 状态转换
- 领域约束
- 审计
- 并发控制
那么 Next.js 即使技术上能连数据库,也不应该绕过 Go:
❌ Next.js → Go DB
✅ Next.js → Go API → Service → Repository → DB
因为绕过的并不是“一个 HTTP 请求”,而是领域规则的权威入口。
BodySense 例子
BodySense 的会话、事件、权限等业务边界由 Go Backend 管理。因此如果 Web 层改成 Next.js:
Next.js Server Component
↓
BodySense Go API
↓
Service / Session / Permission
↓
Repository / DB
↓
Python Agent Runtime(按职责调用)
Assessment Result
访问:
/assessment/123
虽然数据最终可能在数据库里,但 Next.js 应通过 Go Backend 的 Assessment API 读取,而不是直接查 Go 所拥有的表。
Consultation History
历史消息同样属于服务端会话/事件模型的一部分:
Next.js Server Component
→ Go API
→ authoritative history
进入交互阶段后,再由浏览器通过 SSE 消费新事件。
为什么“直接 DB 更快”不是充分理由
直接 DB 可能少一次 HTTP hop,但可能同时失去:
authorization
business validation
transaction boundary
observability
rate/policy enforcement
versioned contract
single source of truth
架构优化不能只比较一次请求的网络开销,还要比较被绕过的语义边界。
BFF 的位置
Next.js 可以作为 Web / BFF 层,负责:
- 为页面聚合多个服务的数据
- 把后端 DTO 转成 UI-safe projection
- 处理 Web 特有认证上下文
- 隐藏第三方 API Secret
- 减少浏览器直接调用多个服务
但 BFF 不应该自动接管独立 Domain Service 的业务 ownership。
Browser
↓
Next.js Web / BFF
↓
Owning Domain Service
↓
Database
最实用的决策问题
不要先问:
Next.js 能不能直接连数据库?
先问:
- 这份数据的 authoritative owner 是谁?
- 业务规则在哪里执行?
- 绕过 API 是否会绕过权限、事务或状态机?
- DB schema 是内部实现,还是本来就由当前应用直接拥有?
- 这个服务是否需要独立演进、扩缩容、审计或部署?
与 Handler-Service-Repository 的关系
如果独立 Go 后端已经采用 Handler-Service-Repository 分层,那么:
HTTP Handler
→ Service
→ Repository
→ DB
就是领域边界的一部分。Web 层直接查表会跳过 Service 层,破坏原有不变量。
常见误区
“Server Component 在服务器上,所以直接 DB 总是最优”
错。Server Component 只说明代码执行位置,不决定数据 ownership。
“有独立 API 就永远不能直查 DB”
也不对。如果当前 Next.js 应用本身就是该数据的 owner,内部 data layer 直查 DB 完全合理。
“数据库才是真相,所以所有服务都应该直接连数据库”
数据库可以保存 durable state,但领域 truth 的访问与修改规则通常由 owning service 定义。
最短记忆
能访问 DB
≠
应该访问 DB
先问 owner,
再选 DB 或 API。