← 返回文章列表

DDD 实战(四):协同办公 IM 的领域全景

2026年7月5日架构设计
DDD协同办公IM领域建模

海外 AI 服务代充推荐:BeWild(推广链接)。

前三篇——规则该归谁管、战略设计、战术设计——谈的是方法,从这一篇开始把它用在一个具体系统上。

笔者第一次给这个系统画领域地图时,犯的正是把产品菜单抄成服务清单的错:通讯录一个服务,聊天一个服务,工作台一个服务。图画得挺整齐,可一被追问“搜索结果里的成员姓名谁说了算”,就答不上来了——菜单描述的是用户从哪里进入,说明不了数据归谁、规则由谁决定。

协同办公 IM 还有一层麻烦:它表面上是聊天工具,实际承载的东西远不止聊天。钉钉、飞书这类产品的开放平台把通讯录、消息、审批、机器人、事件订阅都开放出来,等于把内部能力边界摆在了明面上。它更像一个企业协作操作系统,IM 只是最高频的那个入口。

前面三篇建立了 DDD 的基本概念,这篇将它们用于企业协作 IM。钉钉、飞书/Lark 的官网和开放平台文档只作为能力观察入口;下文的上下文、模型和拆分建议都是设计推导,不代表任何厂商的内部架构。

从公开能力整理业务范围

协同办公 IM 看起来是聊天工具,但成熟产品早已不是单纯发消息。

从钉钉官网可以看到,它面向企业沟通、组织协作、业务流程、AI 办公和开放生态等场景。钉钉官网

飞书/Lark 也把即时沟通、日历、文档、审批、开放平台和智能协作组合在一起,强调一站式企业协作。飞书官网、Lark 官网

开放平台文档进一步暴露了很多业务能力边界。比如飞书开放平台文档中可以看到通讯录、IM 消息、群组、事件订阅、机器人等能力入口。飞书开放平台文档

钉钉开放平台也提供通讯录、身份、消息、机器人、事件订阅、审批等开放能力入口。钉钉开放平台文档

这些入口可以帮助整理账号、通讯录、消息、群组和应用集成等能力。至于它们是否共用数据库、是否独立部署,公开 API 无法回答,领域地图也不需要先假定答案。

第一版地图以数据所有权为线索

下面列出一组候选边界。需要先说清楚:这张地图不是服务拆分清单,它的用途是讨论业务边界、数据所有权和演进方向。工作台与流程、安全与治理等暂时并列在一行,便于检查能力覆盖;它们未必适合共享一个领域模型,后续要按规则继续细分。

先用一张图看清事实的流向:

身份与访问、企业与组织是事实源,向下传给关系、群组与会话、消息三个规则所有者,再以可延迟的业务事件传给通知、搜索、治理三个派生视图

实线是事实与查询,虚线是可以延迟的业务事件。图里没有画工作台与开放平台——它们和关系、群组、消息处在同一层,只是不在这条事实主干上。这张图先回答“谁的事实从哪来、往哪去”,下面再把每个位置的职责摊开成表。

限界上下文主要职责核心模型不应该承担
Identity & Access账号、登录、认证、设备、登录会话、安全策略Account、Credential、Device、LoginSession、AuthPolicy不管理企业部门和好友关系
Tenant & Organization企业、部门、成员、组织角色、通讯录可见性Tenant、Department、Member、OrgRole、VisibilityRule不处理登录凭证和群成员治理
Contact & Relationship好友、联系人、外部联系人、黑名单、关系权限ContactRelation、FriendRequest、ExternalContact、BlockRelation不保存组织主数据
Group & Conversation群、群成员、会话、群治理、会话设置Group、GroupMember、Conversation、ConversationSetting不保存消息正文和搜索索引
Messaging消息发送、消息状态、撤回、引用、转发、已读未读Message、MessageReceipt、RecallRecord、MessageCursor不管理组织架构和群治理规则
Notification站内通知、离线推送、提醒、通知偏好Notification、PushSubscription、Reminder、NotifyPreference不作为业务事实源
Workbench & Workflow工作台、应用入口、审批、表单、任务、公告WorkbenchLayout、AppShortcut、ApprovalInstance、Form、Task不承担消息投递主链路
Open Platform应用、机器人、Webhook、事件订阅、API 凭证、配额App、Bot、WebhookSubscription、ApiCredential、ApiQuota不拥有内部业务数据
Search联系人搜索、消息搜索、文档搜索、应用搜索SearchDocument、IndexTask、SearchPermission不反写业务事实
Governance & Security审计、风控、水印、敏感词、合规、数据隔离AuditEvent、RiskRule、DlpPolicy、ComplianceReport不绕过业务域直接修改聚合

这张表首先用于追问所有权。例如搜索结果里的成员姓名来自 Organization,但索引任务的重试状态属于 Search。两者都持有数据,只有前者有权决定姓名的业务事实。

“不应该承担”一列也需要结合用例验证。Open Platform 不拥有内部消息事实,却拥有应用、授权和订阅自己的事实,不能把它理解为无状态转发层。

账号能登录,不代表能访问某家企业

账号上下文处理的是“谁可以登录系统,以及以什么方式登录”。

它关心的是:

  • 账号 ID。
  • 手机号、邮箱、第三方身份。
  • 密码、验证码和单点登录;涉及第三方身份时区分身份认证与 OAuth 授权。
  • 设备绑定。
  • 登录会话。
  • 多端登录策略。
  • 风险登录和安全策略。

组织成员上下文处理的是“某个账号在某个企业里是什么身份”。一个账号可以加入多个企业,可以在不同企业有不同部门、岗位、角色和可见性。

如果把账号和成员合并,麻烦不止一处:移除企业 A 的成员时很难表达“仍可登录,也仍可访问企业 B”;组织字段的调整会顺带污染登录模型;登录安全策略和组织权限策略被绑在一起,改哪一边都要顾及另一边;没有账号的外部联系人、客户和临时协作者,更是找不到地方安放。

因此本案例让账号上下文拥有 Account,组织上下文拥有 Member,两者通过 account ID 或主体 ID 关联。

分开以后还要补足授权判断:有效登录态只证明主体身份,不能代替目标企业内的成员有效性检查。这个关联是模型拆分之后必须承担的成本。

组织维护成员事实,也要控制事实的传播

协同办公 IM 和普通社交 IM 的最大差异之一,是它以组织为中心。

组织上下文通常要管理:

  • 租户和企业。
  • 部门树。
  • 成员。
  • 岗位、角色和职级。
  • 通讯录可见性。
  • 成员生命周期:邀请、入职、转岗、离职、禁用。
  • 组织字段脱敏和权限。

开放平台中的通讯录能力也能说明组织数据的重要性。飞书开放平台和钉钉开放平台都把用户、部门、通讯录作为开放能力的重要部分。飞书开放平台文档、钉钉查询用户详情

消息发送要知道成员是否有效,文档授权可能需要部门身份,开放平台还要通知成员变更。让这些模块都读写组织表,会使组织结构调整变成跨系统改动。

笔者倾向于让 Organization 提供查询接口和变更事件,由下游保存所需的最少快照。同时约定哪些字段可以延迟,哪些撤权必须及时生效。通讯录查询和消息发送可以消费同一成员事实,却未必采用相同的缓存策略。撤权类变更走实时校验还是允许缓存过期,是这两条路径上代价差别最大的一个决定。

联系关系有自己的建立与终止过程

很多早期 IM 系统会把好友关系直接放进用户模块。简单产品可以这么做,但协同办公 IM 往往更复杂。

关系上下文可能需要表达:

  • 好友申请。
  • 好友关系。
  • 外部联系人。
  • 客户联系人。
  • 拉黑关系。
  • 联系人备注。
  • 关系可见性。
  • 组织内外的联系策略。

当申请、确认、拉黑、备注等规则开始独立变化,就有理由为关系建立单独模型。触发这件事的往往是新场景:客户联系人、跨企业好友、临时会话、合作伙伴联系人。每多一种关系形态,塞在账号上的那几个字段就更难解释一层。它可以先留在同一个部署单元,并不要求同时拆服务。

关系上下文不拥有账号,也不拥有组织成员。它只拥有“关系事实”。它可以引用账号 ID、成员 ID、外部联系人 ID,但不能修改账号状态或组织身份。

这里还要决定关系两端引用什么身份。跨企业好友可能关联账号,企业客户关系可能关联成员或外部联系人。直接统一成两个 user_id 会把这种差异藏起来,迁移时需要重新解释已有数据。

群治理、会话入口和消息分别怎样变化

协同办公 IM 中,“群聊”是最容易混淆的模型。

至少要区分三个概念:

  • Group:群组本身,关注群资料、群成员、群主、管理员、入群规则、群公告、群治理。
  • Conversation:参与通信的会话及其标识;用户侧的置顶、免打扰、草稿等通常是各自的 ConversationSetting,不能当作所有参与者共享的状态。
  • Message:消息事实,关注发送、投递、撤回、已读、转发、引用、内容类型。

三者各自回答一个不同的问题:群组回答“这个群是什么、谁在里面、规则是什么”,会话回答“用户从哪个入口进入这段通信”,消息回答“这条消息如何成为事实并被投递”。把这三个问题混在一个模型里,任何一处调整都会牵动另外两处。

一个群可以对应一个群会话,单聊也可以有会话。这里把群治理和会话管理暂时放在同一候选边界,便于早期实现;如果会话列表、多端设置和同步规则独立变复杂,再评估拆出 Conversation。

如果把群、会话、消息都塞进一个 Messaging 模块,早期很快,但后期容易出现问题:

  • 群治理规则和消息投递规则混在一起。
  • 会话列表设置和群成员状态互相污染。
  • 群成员变更频繁影响消息主链路。
  • 大群消息扩散时,群聚合可能被错误放大。

例如修改群公告会影响共享群资料,置顶只影响某个用户的入口,撤回则改变消息可见状态。把这三个用例分别追踪一遍,比仅凭“都在聊天页面”决定模型归属更可靠。

消息先定义发送成功的含义

消息上下文通常是协同办公 IM 的核心域之一。

它关注的是:

  • 消息 ID 和幂等。
  • 消息内容类型。
  • 消息发送状态。
  • 撤回、编辑、引用、回复、转发。
  • 已读未读。
  • 多端同步游标。
  • 离线补拉。
  • 大群扇出。
  • 加密消息和普通消息的差异。

飞书开放平台的 IM 消息 API 暴露了应用发送消息、消息内容、接收方类型等能力,可以作为公开能力观察入口。飞书发送消息 API

公开 API 并不揭示内部模型。本案例选择把消息持久化和派生动作分开:发送成功表示服务端接受并可靠保存消息,不表示所有终端已经收到,也不表示已读。

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

  1. 鉴权和基础校验。
  2. 会话和群组规则校验。
  3. 消息幂等写入。
  4. 在同一事务中记录消息及待发布的 MessageSent 事件。
  5. 返回发送结果。

通知、搜索、机器人和 AI 摘要可以随后异步处理。若审计要求业务成立时必须留下记录,应把必要记录纳入提交边界,再异步生成审计报表。

工作台是入口,流程和开放平台各有规则

协同办公产品的复杂度,往往从“聊天”扩展到“工作发生在哪里”。

工作台和流程上下文可能包含:

  • 应用入口。
  • 审批。
  • 表单。
  • 公告。
  • 任务。
  • 日程。
  • 低代码应用。

开放平台上下文可能包含:

  • 企业自建应用。
  • 第三方应用。
  • 机器人。
  • Webhook。
  • 事件订阅。
  • API 凭证。
  • 权限范围。
  • 配额和审计。

飞书开放平台和钉钉开放平台都把开放能力作为重要入口,这说明协同办公 IM 不只是用户之间的通信系统,也是企业应用生态的承载层。飞书开放平台文档、钉钉开放平台文档

工作台负责展示和配置入口,不一定拥有审批、任务、日程的执行状态。如果审批有独立的节点、流转和撤销规则,就应有自己的模型。开放平台则负责应用授权、凭证、订阅和对外协议,通过接口或事件访问内部业务事实。这层关系适合用防腐层隔开:应用、授权范围和事件版本属于开放平台自己的模型,内部上下文不该把领域对象直接交给它对外发布;反过来,外部 API 的形态也不该渗进内部模型。

搜索、通知和治理不能按同一种方式接入

搜索、通知、安全治理这些能力经常被误放。

搜索不是业务事实源。消息、联系人、文档、应用才是事实源,搜索只是索引和查询视图。搜索索引可以按约定延迟、从事实源重建;索引中的过期权限却不能直接作为授权依据。撤权或消息撤回时,可能还需要查询时复核或过滤,避免索引滞后暴露内容。

通知也不是业务事实源。通知由业务事件触发,比如成员加入、审批通过、消息发送、文档分享。通知上下文负责偏好、渠道、频控、推送状态,不应该决定业务动作是否成立。

治理和安全更特殊。审计、风控、敏感词、水印、合规策略会影响多个域,但它们也不应该绕过业务模型直接改数据。更好的做法是:

  • 风控前置参与决策。
  • 审计通过事件记录事实。
  • DLP 和水印通过策略影响文档、消息和文件上下文。
  • 合规报表消费审计和业务事件。

安全治理这一行尤其需要继续拆解。发送前的风控判断和事后的审计报表,在延迟要求、失败处理和数据访问上都不同;把它们写进同一个候选域,不代表应该共用一条同步调用链。举个具体的:敏感词命中要在消息受理时拦住,这是同步判断,失败就得拒绝发送;审计报表可以等消息发出去几小时之后再生成,失败重试就好。硬把这两件事串成一条链,等于让报表的积压拖住发送。

用现有用例检验这张地图

如果从零设计一个协同办公 IM,不建议一开始就拆十几个微服务。更务实的做法是:

第一阶段,先在代码里建立模块边界:

  • identity
  • organization
  • relationship
  • group
  • messaging
  • notification
  • workflow
  • openplatform
  • search
  • governance

随后从实际负载、发布频率和隔离要求中找拆分点,下面这些模块值得评估,但没有固定顺序:

  • messaging
  • group
  • openplatform
  • search
  • notification

例如搜索构建已影响消息进程内存,就有拆 worker 的具体理由;仅仅有一个 Search 方框还不够。边界评审可以先走“成员离职”“群主转让”“撤回消息”三条流程,检查写入口、权限与失败补偿是否都有归属,再确定哪些部署变化值得做。

这三条流程各自照出一类问题。“成员离职”照的是传播延迟:组织发出事件之后,群组、消息、搜索分别在什么时间点失效?如果索引允许延迟五分钟,这五分钟里离职者还能不能搜到原企业的消息?“群主转让”照的是不变量归属:旧群主、新群主和群资料上的 ownerID 必须在一次事务里改完,这件事到底写在哪个上下文里?“撤回消息”照的是事件顺序:撤回事件先于发送事件到达索引端时,索引得能识别出来并拒绝用旧事件把内容补回去。

三条走完,通常会发现几处责任悬空——没人说得出该由谁保证。悬空的那些位置,就是这张地图还要修的地方。

接下来打算专门讨论最容易争议的边界:账号、组织、关系、群组和会话。

← 返回文章列表