← 返回文章列表

DDD 实战(六):消息、通知、搜索、开放平台的跨域协作

2026年6月26日架构设计
DDD领域事件事件驱动IM

DDD 实战(六):消息、通知、搜索、开放平台的跨域协作

推荐 BeWild 代充 Codex ,工作猛猛提效。

如果说账号、组织、关系、群组的难点是“边界怎么切”,那么消息、通知、搜索、开放平台的难点就是“边界切开以后怎么协作”。

协同办公 IM 的消息发送看似只有一个动作:用户发出一条消息。

但在真实系统里,一条消息可能触发很多后续动作:

  • 写入消息事实。
  • 更新会话列表。
  • 计算未读数。
  • 多端同步。
  • 离线推送。
  • 更新搜索索引。
  • 触发机器人。
  • 推送开放平台事件。
  • 记录审计日志。
  • 触发风控策略。
  • 生成 AI 摘要。

如果把这些动作都放进一个同步事务,消息链路会越来越慢,也越来越脆。

DDD 的做法不是把所有东西拆开以后互相 RPC,而是识别核心事实和派生动作,用领域事件组织跨上下文协作。

1. 公开能力能观察到什么

飞书开放平台提供 IM 消息相关 API,应用可以向用户、群组等目标发送消息。飞书发送消息 API

飞书和钉钉开放平台也都提供事件订阅、机器人、通讯录、审批等开放能力入口。飞书开放平台文档钉钉开放平台文档

这些公开资料能说明两件事:

第一,成熟协同办公 IM 会把消息能力开放给内部应用和外部应用。消息不只是用户手动发送,也可能来自机器人、审批、系统通知、第三方应用。

第二,消息会和很多平台能力协作。开放平台需要事件,机器人需要回调,通知需要投递,搜索需要索引,审计需要记录。

但公开资料不能说明厂商内部如何拆服务、如何设计表、如何投递事件。因此本文只基于公开能力推导一种合理架构。

2. 消息发送主链路应该尽量短

一个稳健的消息发送主链路,可以控制在这些步骤内:

  1. 接口层接收请求,解析发送者、租户、设备和客户端消息 ID。
  2. Identity & Access 校验登录态和设备状态。
  3. Group & Conversation 校验会话是否存在、成员是否可发送、群是否禁言。
  4. Governance & Security 做必要的前置策略校验,比如敏感词、频率限制、内容大小。
  5. Messaging 使用客户端消息 ID 做幂等,写入消息事实。
  6. Messaging 记录 MessageSent 领域事件或写入 outbox。
  7. 返回发送成功或明确失败。

这个链路的目标,是让“消息成为事实”这件事尽快完成。

下面这些动作,不应该阻塞主链路:

  • 离线推送。
  • 搜索索引。
  • 机器人回调。
  • 开放平台事件投递。
  • AI 摘要。
  • 消息审计报表。
  • 未读数的复杂聚合。

它们重要,但不是消息写入事实的前置条件。

3. MessageSent 是业务事实,不是技术通知

MessageSent 应该表达一个已经发生的业务事实:某个发送者在某个会话里成功发送了一条消息。

事件内容可以包含:

MessageSent
├── event_id
├── occurred_at
├── tenant_id
├── conversation_id
├── message_id
├── sender_id
├── message_type
├── content_digest
└── visibility_scope

注意,它不应该包含所有消息正文和所有下游所需字段。

领域事件要在“足够表达事实”和“不泄漏领域内部模型”之间取平衡。搜索需要正文时,可以根据 message ID 回查消息上下文,或者由消息上下文发布面向索引的受控事件。开放平台只需要部分字段时,就不应该把内部敏感字段暴露出去。

事件设计得越像数据库行复制,边界越容易被破坏。

4. 通知上下文:投递是派生能力

通知上下文消费各种领域事件:

  • MessageSent
  • GroupMemberJoined
  • ApprovalCompleted
  • DocumentShared
  • OrganizationMemberRemoved

它负责把业务事实转成用户可感知的提醒:

  • 站内通知。
  • 移动端 Push。
  • 桌面端提醒。
  • 邮件提醒。
  • 短信提醒。
  • 应用内红点。

通知上下文拥有的是通知偏好、投递任务、投递状态、频控规则。它不拥有消息事实,也不应该决定消息是否发送成功。

这里有一个重要 trade off:未读数应该归消息域还是通知域?

我的建议是分层看:

  • 会话内的读状态和消息游标,属于 Messaging 或 Conversation。
  • 跨应用红点、桌面提醒、Push 状态,属于 Notification。
  • 页面上展示的总未读数,可以是查询侧组合视图。

不要为了一个 UI 数字,把消息和通知强行揉成一个模型。

5. 搜索上下文:索引不是事实源

搜索上下文最容易被误用。

用户希望全局搜索:

  • 联系人。
  • 群。
  • 消息。
  • 文档。
  • 应用。
  • 审批。
  • 机器人。

于是很多系统会让搜索服务直接读各业务库,甚至反向修改业务状态。这会让搜索服务变成隐形中心。

更合理的方式是:

  1. 各业务上下文发布领域事件。
  2. 搜索上下文消费事件,构建自己的索引文档。
  3. 索引文档只作为查询视图。
  4. 权限过滤必须继承来源上下文的权限规则。
  5. 索引可删除、可重建、可延迟,但不能成为事实源。

例如消息被撤回后,Messaging 发布 MessageRecalled。Search 消费事件后更新或删除索引。即使搜索索引延迟,消息事实仍然以 Messaging 为准。

这也是为什么搜索适合最终一致。要求每条消息写入后立刻被全局搜索命中,通常会显著拖慢主链路。

6. 开放平台上下文:隔离外部复杂度

开放平台是协同办公 IM 的生态入口,但它不应该侵入内部领域模型。

开放平台上下文通常负责:

  • 应用注册。
  • API 凭证。
  • OAuth 授权。
  • 权限范围。
  • 机器人。
  • Webhook。
  • 事件订阅。
  • API 配额。
  • 开发者审计。
  • 回调重试。

它和内部领域的关系,应该是“授权访问 + 事件订阅 + 防腐层”。

例如内部 Messaging 发布 MessageSent,Open Platform 不应该直接把这个内部事件原样推给第三方应用。它应该转换成开放平台的发布语言:

Internal MessageSent
        ↓
Open Platform Event Translator
        ↓
External im.message.receive_v1

转换过程中要处理:

  • 租户隔离。
  • 应用权限。
  • 字段脱敏。
  • 事件版本。
  • 回调签名。
  • 失败重试。
  • 频率限制。

这就是防腐层的价值。它防止外部 API 形态反向污染内部领域模型,也防止内部模型变化直接破坏开发者生态。

7. 机器人:看似消息,实则跨域

机器人是一个很好的边界案例。

机器人可以发送消息,所以它看起来属于 Messaging。机器人有凭证、回调和权限,所以它又像 Open Platform。机器人可能绑定群,所以它也和 Group 有关系。机器人还可能响应审批、文档、告警事件。

更合理的拆法是:

  • Open Platform 拥有 Bot、AppCredential、WebhookSubscription。
  • Group 拥有“某机器人是否被添加到群”的群内关系。
  • Messaging 只接受一个经过授权的发送主体,写入消息事实。
  • Governance 负责风控、频控和审计策略。

这样设计后,“机器人发消息”不是一个特殊消息链路,而是一个经过开放平台授权的发送用例。

这能避免 Messaging 被开放平台概念污染,也能避免 Open Platform 直接改消息表。

8. 事件驱动不是到处发 MQ

领域事件很有用,但它不是万能药。

适合事件驱动的场景通常具备这些特征:

  • 事件表示已经发生的业务事实。
  • 订阅方可以异步处理。
  • 短暂延迟业务可接受。
  • 消费失败可以重试或补偿。
  • 发布方不需要知道所有订阅方。

不适合事件驱动的场景包括:

  • 必须在当前请求内给出确定结果。
  • 必须强一致校验的业务不变量。
  • 失败后不能重试也不能补偿。
  • 事件只是为了绕开清晰接口。

例如“成员是否有权限发送群消息”通常不能异步事件化。发送前就要校验。否则用户会看到发送成功,几秒后又被撤回,体验和一致性都很差。

但“消息发送后更新搜索索引”适合事件化。搜索延迟几秒通常可以接受,失败也可以重试。

9. 一致性策略:先分清强一致和最终一致

跨域协作最大的难点,是一致性。

协同办公 IM 中,通常需要强一致的有:

  • 同一消息 ID 的幂等写入。
  • 群成员权限和发送资格的关键校验。
  • 群主唯一性。
  • 撤回权限校验。
  • 账号禁用后的关键操作拦截。

可以最终一致的有:

  • 搜索索引更新。
  • 离线推送。
  • 机器人回调。
  • 开放平台事件投递。
  • 审计报表。
  • AI 摘要。
  • 全局未读聚合视图。

DDD 不要求所有东西都最终一致。它要求你把一致性边界说清楚。

强一致的规则应该留在聚合和必要的同步校验里。最终一致的派生能力,应该通过领域事件、outbox、幂等消费和补偿任务完成。

10. 推荐的跨域协作链路

一条消息发送后的推荐协作链路可以这样设计:

Client
  ↓
Messaging Application Service
  ↓ 校验
Identity / Group / Governance
  ↓ 写入
Message Aggregate + Outbox
  ↓ 发布
MessageSent
  ↓
Notification / Search / Open Platform / Audit / AI

其中关键规则是:

  • Messaging 拥有消息事实。
  • Notification 拥有通知任务。
  • Search 拥有索引视图。
  • Open Platform 拥有外部应用交互。
  • Audit 拥有审计追加记录。
  • AI 拥有异步分析任务。

每个上下文都可以失败、重试和降级,但不能反向篡改消息事实。

这就是 DDD 在复杂 IM 平台中的实际价值:它不是把系统拆散,而是让每个模块知道自己拥有的事实、消费的事实和不该越界的事实。

下一篇我们把领域模型继续往下推,看它如何影响部署模块:单体、模块化单体、微服务和 monorepo 到底怎么选。

返回博客列表