← [ BACK_TO_NOTES ]

BodySense 数据库第 1 课:Database Ownership、关系模型与 Durable Truth

从 BodySense 当前 PostgreSQL 18 schema 出发,建立数据库作为 durable business truth 的心智模型,并区分表、关系、投影、缓存与事件日志的 ownership。

#course #growing #backend #database #postgresql #bodysense

[!info] related notes

BodySense 数据库第 1 课:Database Ownership、关系模型与 Durable Truth

[!abstract] 本课目标 先不学索引和事务细节,先回答数据库工程最根本的问题:为什么这一份数据应该由 PostgreSQL 持有,它究竟承诺了什么?

从零建立概念:Database、DBMS、Relational Database 到底分别是什么

Database:被系统长期管理的一组数据

最宽泛地说,Database 是一组按照某种模型组织、需要被长期读取和修改的数据。

但工程里我们通常不会让业务进程自己:

open file
seek bytes
自己处理并发写
自己处理 crash recovery
自己维护索引

而是交给一个专门的软件。

DBMS:真正替应用管理数据的软件

DBMS = Database Management System。

PostgreSQL 才是 DBMS。

BodySense Go API 并不是直接“打开一个数据库文件”,而是:

Go API
↓ SQL / PostgreSQL wire protocol
PostgreSQL Server
↓
tables / indexes / WAL / files

DBMS 负责的事情包括:

解析 SQL
管理连接
执行查询
管理 transaction
控制并发
维护 constraint
管理 page / buffer
维护 index
记录 WAL
crash recovery

所以“会用数据库”不是只会 SELECT,而是逐渐理解这些能力怎样共同保证业务事实。

Relational Database:关系模型不是“表格长得像 Excel”

Relational 来自 Relation。

初学时可以把 Relation 暂时近似成 Table,但它背后的重要思想是:

一类事实
→ 由固定属性描述
→ 每一行是一条事实
→ 行之间通过 Key 建立关系
→ 查询通过关系运算组合事实

例如:

users
→ 用户事实

conversations
→ 会话事实
→ user_id 引用 users

messages
→ 消息事实
→ conversation_id 引用 conversations

这里不是“一个用户对象里面嵌一个 messages 数组”这么简单,而是把不同生命周期、不同基数的事实拆成独立 Relation。

Schema 与 Data:规则和当前内容是两层东西

Schema

Schema 描述数据库允许什么形状的数据存在:

有哪些表
有哪些列
列是什么类型
谁是主键
谁引用谁
哪些字段必填
哪些组合唯一
哪些值必须满足 CHECK
有哪些 index

Data / Instance

Data 是此时此刻真正存在的行。

例如:

Schema:
users(id uuid primary key, email text unique ...)

Data:
(u1, arlon@example.com)
(u2, ...)

这一差异后面会直接连接 Migration:

Model / Schema definition
≠
已经存在的生产数据状态

为什么业务服务需要 Durable Truth

Durable 这里先理解成:

一旦业务已经确认这个事实成立,它不能因为 API 进程重启就消失。

例如:

用户完成 Assessment
Diagnosis 被正式接受
Treatment revision 被保存
Job 已被创建
RuntimeEvent 已经告诉前端

这些都不能只放 Go memory。

如果 API 重启后全部消失:

HTTP 200 success
↓
process restart
↓
事实不存在

整个业务 contract 就不成立。

因此 PostgreSQL 在 BodySense 中最核心的职责不是“比文件方便”,而是成为 durable business truth 的主要拥有者。

Source of Truth / Authority / Projection 三个词

Source of Truth

对某一事实来说,最终应该相信哪一份。

例如:

BodyState Fact
→ PostgreSQL row = truth

Authority

更强调“谁拥有最终裁决权”。

例如:

数据库 UNIQUE constraint
vs
Go 代码里先 SELECT 看有没有重复

真正并发最终裁决 uniqueness 的 authority 是数据库 constraint。

Projection

Projection 是为了某个读取场景重新组织出来的数据形状。

normalized tables
→ JOIN / aggregate / select latest
→ HealthWorkspace response

Projection 可以:

  • 临时计算;
  • 缓存;
  • 持久化成 read model;

但要明确:

Projection 是由哪些 truth 推出来的?失效后能不能重建?

为什么“高频读取”不能决定 Ownership

假设用户姓名每秒读一百万次。

这只能说明:

可能需要 Cache / Replica / Projection

不能说明:

姓名应该从 PostgreSQL 搬成 Redis-only

Ownership 要看:

lifetime
durability
constraints
relations
audit
recovery
concurrency semantics

Performance 是设计维度之一,不是唯一维度。

BodySense 的最小数据分层

可以先用这张图:

Browser local/UI state
→ 临时交互

Redis
→ cache / fast runtime state / coordination

PostgreSQL
→ durable domain truth / audit / replay / job ledger

Object Storage
→ 大型 binary bytes

后续课程会不断用这张 ownership 图判断“某个技术能不能替代另一个技术”。

1. Database 不是“存 JSON 的地方”

关系数据库同时提供四类能力:

Durable Facts      → 数据跨进程/重启长期存在
Constraints        → 非法状态直接被数据库拒绝
Relations          → PK/FK/Cardinality 表达实体关系
Transactions       → 多个读写形成一致提交边界

BodySense 的 BodyState、Diagnosis、Treatment、Runtime Event、Job 等之所以进入 PostgreSQL,不只是因为“需要保存”,而是因为它们需要长期事实、关系约束、审计、事务或恢复能力。

2. Relation / Row / Column / Key

把表理解成某类事实集合,而不是 Go struct 的磁盘镜像:

Table       = 一类事实
Row         = 一个事实实例
Column      = 该事实的属性
Primary Key = 行身份
Foreign Key = 与另一类事实的引用关系
Constraint  = 哪些数据库状态根本不允许存在

ORM model 只是应用层表示;真正的持久化 contract 在数据库 schema。

3. Source of Truth 与 Projection

BodySense 中应明确:

PostgreSQL durable table
= authoritative fact

Redis cache / TanStack Query
= derived copy

HealthWorkspace / ThreadProjection
= read projection

Projection 可以被重建,durable truth 不能因为 projection 更方便就倒置 ownership。

4. BodySense 真实锚点

从以下文件阅读:

apps/api/migrations/000001_vnext_baseline.up.sql
apps/api/internal/model/body_state.go
apps/api/internal/model/runtime_event.go
apps/api/internal/model/job.go

观察哪些字段是:身份、状态、时间、关系、revision、JSONB,以及哪些不变量已经下沉到数据库。

Prediction

[!question] 先回答

  1. BodyState.current_revision 和前端当前选中的 tab,哪一个更适合 PostgreSQL?为什么?
  2. 如果 HealthWorkspace 可以完全由多个 durable 表重新拼出来,它应该是 authority 还是 projection?
  3. 一个字段每秒读取一万次,是否足以说明 Redis 应成为它的 source of truth?
  4. Go struct 上写了 Status string,是否等于数据库已经限制 status 只能出现合法值?
  5. Runtime Event 需要 reconnect 后从 after_seq 恢复,这个要求对存储 ownership 有什么暗示?

[!success]- Answer / 解析 1. BodyState.current_revision。 它参与持久化并发控制与历史版本语义;tab 是 UI 本地状态。是否持久化取决于业务 ownership/lifetime,而不是值看起来是否简单。

2. Projection。 它是读优化/界面形状,可以重建;真正事实仍在其来源表。

3. 不能。 高频只说明可能值得缓存;source of truth 取决于 durability、约束、恢复和业务语义。

4. 不等于。 Go 类型约束只影响应用路径;绕过应用、并发写或未来代码仍可能写入任意字符串。需要 CHECK/ENUM/外键等数据库约束才能成为持久化 invariant。

5. 暗示事件必须有 durable、可排序、可按 cursor 查询的事实源。 瞬时 Pub/Sub 可以做 wakeup,但不能替代 durable event ledger。

Lab

从 baseline migration 任取三张表,写一张 ownership 表:

数据Authority可重建?需要约束?需要历史/审计?
示例PostgreSQL否是是

L4 Gate

不看笔记,解释一个 BodySense 页面上看到的数据,沿 UI → API → Repository → PostgreSQL 指出哪一步只是 projection,哪一步才是 durable truth。

创建于 2026/9/18 更新于 2026/9/18
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本