Files
ayatori/docs/database/domain-model.md
T

10 KiB
Raw Blame History

领域模型设计草案

状态:Draft,含已确认决策。日期:2026-09-13。

本文定义领域职责、身份与一致性边界,并用对象规格细化字段和方法合同;方法使用 设计签名,不固定 Go 目录、SDK 或框架,也不批准实现。外部行为以 系统规格 为准;下列未决问题不能由实现自行决定。 PR #6 的代码和已有 registry 表结构是可评估的实现素材,不反向决定领域模型。

1. 领域与统一语言

本系统的领域是“在共享 PostgreSQL 上供应并管理应用租户”,不是数据库服务器运维。 v1alpha1 先采用一个限界上下文,不把 PostgreSQL、Bao、Kubernetes 各自当成业务上下文。

术语 含义 不是什么
Instance 平台登记的外部 PostgreSQL 管理对象及其供应策略 连接池、VM 或 controller 单例
Tenant 一个应用的数据库使用合同及受管资源生命周期 PostgreSQL database 的别名
Database 租户数据库的名称、owner、扩展等期望描述与实际观察 包含 Bao 登录与连接关闭的操作接口
LoginRole 同时作为 database owner 和应用登录身份的角色 额外的 NOLOGIN owner
OwnershipClaim 某个 Tenant 身份对一组资源名称与凭据位置的所有权声明 工作流阶段或仅凭名称推断的归属
CredentialLocation 固定推导的凭据位置及所有权关联 密码本身或用户可任意选择的 KV path
CredentialProjection 把既定凭据交付到目标 Secret 的要求与观察结果 controller 直接写入明文 Secret

UID 表示一次 Kubernetes 对象身份;namespace/name 用于定位,不足以证明归属。 database OID 是诊断观察值,不充当本系统的租户身份。

2. 候选聚合边界

Instance:实例能力与供应策略

Instance 是候选聚合根,持有自身身份、endpoint、管理凭据引用、实际可用扩展观察, 以及用于判断当前能力的观察结果。它不持有所有 Tenant 对象的集合。

其行为包括:

  • 判断租户申请的 extension 是否在本实例实际可安装列表中;v1alpha1 暂不实现 allowlist。
  • 根据管理连接、服务器信息、registry 和权限检查结果判断是否具备供应能力。
  • 判断配置变化使哪些能力观察过期,禁止以旧 generation 的 Ready 证明新配置可用。
  • 在 registry 初始化完成并回读验证后,接受新的就绪结果。

“探测实例”“准备管理 registry”是应用用例协调的外部操作,不是 Instance 的 IO 方法。 领域对象只接收观察值,负责前提、规则和状态决策;应用层调用适配器获取事实与执行 获准操作。领域对象不持有或调用外部访问端口、客户端或回调。具体选择见 Instance 字段与行为,仍处于待评审状态。

Tenant:供应合同与资源生命周期

Tenant 是另一个候选聚合根,通过身份引用 Instance,而不是 Instance 的聚合成员。 操作一个 Tenant 不应要求装载、锁定或保存整个实例的租户集合。

Tenant 持有有效的 database/role 名称、请求的扩展、凭据交付目标、删除策略,以及 已建立的资源绑定。它负责:

  • 检查绑定后的不可变字段、extension 只追加规则。
  • 判断外部部分状态属于本 Tenant、尚不存在,还是与未知资源冲突。
  • 决定是否允许继续供应、何时达到 Ready、是否允许释放受管资源。
  • 按 Retain/Delete 合同限制行为,禁止把保留资源自动认领给同名新 UID。

Database、LoginRole 和 CredentialProjection 暂不设独立聚合根或独立 CRUD 用例。 它们可作为 Tenant 内的资源描述与观察值;有规则才增加行为,不为了“充血”添加方法。 真实 PostgreSQL database/role 的存在不意味着内存中必须各有一个有身份的实体。

聚合边界是业务规则的保护边界,不表示 Tenant 对应的 PostgreSQL、Bao、ESO 资源 能够一次事务提交。跨系统供应必须允许部分完成。

OwnershipClaim:跨租户唯一性与持久证据

名称唯一性不可能只靠某个 Tenant 的内存检查保证。需要一项领域能力,在持久化边界 原子认领资源;已有 registry 是其适配器候选,仍需结合 catalog 和 Bao metadata 检查。

Claim 与 Tenant 关联,但不随 Tenant CR 消失:Retain 后证据必须继续存在。因此不能 把它仅视为 CR 的附属 status。是否作为独立的小聚合,先以“可独立持久化、保留并保护 归属不变量的声明”建模;不因此引入新的 CRD。

  • 同一身份、同一绑定的重复认领可以成功;不同 UID 或不同绑定不能覆盖。
  • Claim 预留名称不等于证明同名外部资源由本 controller 创建。
  • 实际写入仍须核对所有权,不能把先查后建当成并发安全保证。
  • 当前 registry 的数据库事务不能覆盖 Bao;跨实例的凭据路径竞争也不能靠单个 registry 的唯一约束解决。写入前提与条件创建协议需单独设计和验收。

3. 领域、用例与适配器的分工

层 承担的职责 禁止承揽的职责
领域对象/策略 身份、有效合同、归属判断、允许的动作、完成条件 外部 IO(包括通过接口间接调用)、解析 CLI、生成 Kubernetes Condition
应用用例 装载模型与事实、持久化意图、调用能力、回读、提交结果 另写一套绕过领域规则的判断流程
controller 入口 CR 映射、调度、watch、重试、status/finalizer 写入 在 reconcile 中重新定义业务规则
基础设施适配器 PostgreSQL、registry、Bao、ESO 的实际读写与并发保障 自行决定接管、改密码或扩大删除范围
启动装配 校验部署配置,创建共享客户端、连接管理器及用例依赖 把连接生命周期当成 Instance 的业务状态

领域可使用独立的身份、endpoint、identifier、extension 集合等值对象,不依赖 CRD 类型、pgx pool 或 Bao SDK。Kubernetes 对象的存取与 registry 的存取不是一个通用 Save(Tenant) 可以原子完成的事情;不虚构跨系统 Unit of Work。

暂不引入事件总线、事件溯源、通用聚合框架或全套 Repository CRUD。领域建模的依据 是业务规则,而不是接口和目录数量。

4. 状态与恢复

CR status.phase 仍是已批准的工作流 checkpoint,不在内存对象或 registry 再建一套 权威 phase。领域对象可以由 CR 的期望状态、checkpoint 和外部观察重新构造。

phase 只决定候选步骤,外部证据决定该步骤是否允许执行、是否已经完成。应用层在 写操作前保存意图,调用幂等操作后回读,再保存下一 checkpoint。status 写入失败时, 下次从外部事实识别完成结果;不能重发密码,也不能相信伪造的 Ready。

业务失败区分 InvalidSpec、ImmutableField、Conflict 等;依赖故障由适配器转换成 安全的能力失败,应用层决定重试并映射 Condition。凭据不进入模型序列化、status、 事件或错误明细;只能在实际需要它的执行边界短暂传递。

5. 用例走查与验收方向

场景 领域判定 应用与适配器执行/恢复
登记 Instance 当前配置的能力要求是否满足 读取管理凭据,验证连接与权限,准备并回读 registry;完成后才 Ready
供应 Tenant Instance 策略、绑定与归属允许供应 保存意图,认领资源,先写并回读 Bao 凭据,再创建 role/database,登录验证和 ESO 投射
Bao 写入后进程中断 同一身份的部分状态可继续 回读原凭据继续,不生成第二份密码
两个 Tenant 竞争名称 只有匹配所有权的一方可继续 持久化认领和条件写入裁决竞争,失败方 Conflict,不覆盖资源
Delete 中断 已消失资源可视为完成;剩余资源仍须归属正确 按规格顺序继续删除,全部回读不存在后才清 registry 和 finalizer
Retain 后同名 CR 重建 新 UID 不等于原所有者 Conflict,不恢复管理、不改密码

领域测试验证规则与决策;adapter 测试验证锁、条件写入、SQL 与协议行为;controller 测试验证 checkpoint 持久化和重启恢复;E2E 验证最终合同。不能只验证一串 mock 调用 就声称实现了最终一致性。

6. 决策记录与待细化边界

  1. Instance 身份与物理目标(已确认):以管理员声明为准,endpoint 变更不验证 物理服务器/registry 连续性,不增加安装身份绑定检查;旧观察失效,重验新配置 的连接与管理能力。新 CR 视为新 Instance,不自动接管旧 UID 资源或迁移数据。

  2. Retain 完成条件:外部依赖不可用不能永久阻止 CR 删除,但 registry 又需标记 unmanaged。需定义 CR 消失后的补偿/清扫入口及所需身份依据,不能承诺同时原子 完成两者,也不能在没有回读时声称已写入保留标记。

  3. 管理凭据来源与 Ready(已确认):Instance 引用 controller namespace 内的 管理 Secret 名称和字段;管理员维护 ExternalSecret,ESO 同步。controller 不直接 从 Bao 读取管理凭据。已有凭据仍可访问 PG 时,Bao/ESO 故障不撤销 Instance Ready; 首次装配无有效 Secret 则失败。Secret 的有效用户名/密码变化时重建管理连接池并 重验,不因无关字段变化重建;controller 不修改 PG 密码,不回写 Secret 或 Bao。

  4. 绑定时机:系统规格写“首次成功后不可变”,API 文档写“首次创建外部状态后 不可变”。应明确绑定在认领、首次外部写入还是 Ready 时固定,及如何在 status 丢失 后恢复;否则供应中途改名称可能产生无人管理的资源。

  5. Instance 删除(已确认):开始受管即添加 finalizer;删除期间停止新供应, 有 Tenant 引用就等待,无引用才解除,不级联删除外部资源。首版采用 finalizer 与引用检查,不引入跨对象锁/准入控制;不保证并发创建与删除的原子性。

Tenant 的 Retain 等待决行为留到 Tenant 设计,不属于本轮 Instance 设计范围。 相关用例在决策批准前不进入实现,不同时实现整套模型。