3. 对象存储核心领域模型:Bucket 与 Object

在对象存储领域,Bucket(存储桶)与Object(对象)是构成整个系统的两大核心实体。理解它们的边界、属性及生命周期,是进行领域驱动设计(DDD)与数据库建模的基础。本章将严格遵循 DDD 的战术建模方法,定义这两个聚合根,并设计其状态机。

3.1 核心实体定义与边界

从 DDD 视角看,Bucket 是一个聚合根(Aggregate Root),它作为对象的命名空间和权限边界;Object 则是另一个聚合根,其生命周期完全依赖于 Bucket,但拥有独立的标识和版本控制。

实体 DDD 角色 唯一标识 边界职责
Bucket 聚合根 Bucket Name(全局唯一) 命名空间隔离、权限策略、地域锁定、版本控制开关
Object 聚合根 Bucket + Object Key + VersionId 数据内容存储、元数据管理、分片上传、生命周期流转
设计要点:Bucket 与 Object 之间是一对多的强关联关系,但为了支持高并发与水平扩展,Object 不应持有 Bucket 的领域引用,仅通过 Bucket Name 进行逻辑关联。

3.2 属性设计

3.2.1 Bucket 属性

  • BucketName:全局唯一字符串,遵循 DNS 命名规范(小写字母、数字、短横线)。
  • Region:所属地域,决定了数据物理存储位置。
  • StorageClass:默认存储类型(标准、低频、归档)。
  • VersioningStatus:版本控制状态(Enabled / Suspended / Unversioned)。
  • ACL / Policy:访问控制列表与桶策略 JSON。
  • CreationDate:创建时间戳。

3.2.2 Object 属性

  • ObjectKey:在 Bucket 内唯一的对象键,支持路径分隔符 /。
  • VersionId:版本标识,由 UUID 或时间戳生成。
  • ContentLength:对象大小(字节)。
  • ContentType:MIME 类型。
  • ETag:MD5 或 SHA256 校验值。
  • LastModified:最后修改时间。
  • StorageClass:当前存储类型,可覆盖 Bucket 默认值。
  • Metadata:用户自定义元数据(KV 对)。
PostgreSQL 映射建议:Bucket 表使用 VARCHAR(63) 作为主键;Object 表使用 (bucket_name, object_key, version_id) 复合主键,并利用 JSONB 存储用户元数据,以支持灵活的查询与索引。

3.3 生命周期状态机

对象存储的核心价值之一在于自动化的生命周期管理。Object 的状态流转直接决定了存储成本与访问性能。下图展示了 Object 从创建到最终删除的完整状态机。

Object 生命周期状态机 Object 生命周期状态机 创建中 已上传 标准存储 低频存储 归档存储 已删除(标记) 彻底删除 上传完成 默认策略 30天未访问 90天未访问 手动删除 过期自动清理 说明: • 实线箭头表示自动生命周期流转,虚线箭头表示手动操作。 • 版本控制开启时,“已删除”仅创建删除标记,原对象仍可恢复。

3.4 状态流转规则

生命周期状态机遵循以下核心规则,这些规则将直接映射到 PostgreSQL 的触发器或定时任务中:

  1. 自动降冷:对象在标准存储中超过 30 天无读取,自动转为低频存储;超过 90 天转为归档存储。
  2. 手动删除:任何状态的对象均可被手动删除,进入“已删除(标记)”状态。
  3. 过期清理:处于“已删除”状态超过 7 天的对象,由后台任务彻底清除物理数据。
  4. 版本控制:开启版本控制后,删除操作仅新增删除标记,历史版本保留,直到显式清理。
避坑指南:在 PostgreSQL 中实现状态机时,不要使用 ENUM 直接存储状态码,建议使用 VARCHAR(20) 并配合 CHECK 约束,以便未来扩展新的存储类型(如深度归档)。同时,为 last_accessed_at 字段建立索引,以支持高效的生命周期扫描查询。

3.5 领域事件与集成

在 DDD 实践中,状态变更应触发领域事件,以便其他限界上下文(如计费、审计、通知)做出响应。以下是关键事件列表:

事件名称 触发条件 事件负载(关键字段)
ObjectUploaded 对象上传完成 BucketName, ObjectKey, VersionId, ContentLength
ObjectStorageClassChanged 生命周期自动降冷 BucketName, ObjectKey, OldClass, NewClass
ObjectDeleted 手动删除或版本标记 BucketName, ObjectKey, VersionId, IsDeleteMarker
ObjectExpired 彻底清理物理数据 BucketName, ObjectKey, VersionId, ExpiredAt

这些事件可以通过 PostgreSQL 的 LISTEN/NOTIFY 机制或 Outbox 模式发布到消息队列,实现最终一致性。

-- 示例:Object 状态变更的 Outbox 表结构
CREATE TABLE object_event_outbox (
    id              BIGSERIAL PRIMARY KEY,
    aggregate_id    VARCHAR(255) NOT NULL,   -- BucketName/ObjectKey/VersionId
    event_type      VARCHAR(100) NOT NULL,
    payload         JSONB NOT NULL,
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    published_at    TIMESTAMPTZ
);

CREATE INDEX idx_outbox_unpublished ON object_event_outbox (created_at)
    WHERE published_at IS NULL;