面向对象不是写更多类,而是提前为变化留出空间。
明确需求与用例(Why / What)
1. 为什么要单独设计消息中心?(Why)
在大多数系统早期,消息往往是“顺手写”的:
- 评论时顺便插一条消息
- 点赞时 if-else 发通知
- 活动时手动拼文案
短期能跑,但很快就会出现:
- 消息类型越来越多
- 业务系统相互调用
- 重复消息、刷屏
- 一改样式牵一大片代码
- 增加一种消息类型,就需要前端适配,并且服务端需要区分版本
根因只有一个:没有把「消息」当成独立领域建模。
2. 消息中心到底要解决什么问题?(What)
抽象后的核心需求只有三点:
- 告诉用户“发生了什么”
- 以合适的形式展示
- 在合适的时机送达
而不是:
- 拼字符串
- 堆字段
- 写死跳转
要做什么?核心行为是什么
1. 用户场景(Happy Path)
- 用户收到评论回复
- 用户被 @ 提醒
- 用户收到活动通知和中奖通知
- 用户收到系统通知
- 用户查看消息列表 / 未读数
2. 边界条件(Edge Cases)
- 评论被删除
- 点赞被取消
- 用户拉黑 / 无权限
- 消息重复触发
3. 失败场景(Failure Scenarios)
- 消息系统异常
- Redis 不可用
- MQ 重复投递
结论:
消息系统必须具备 幂等性、降级能力、最终一致性。
领域建模:识别对象与职责
核心领域对象
Message:消息实体,描述“通知本身”MessageRenderer:决定“如何展示”MessageCreateService:控制消息创建规则MessageEvent:描述“发生了什么
非领域对象(基础设施)
Redis:缓存/去重MQ:异步解耦DB:最终一致性存储
关键点:
领域对象不直接依赖基础设施,通过接口隔离
确定交互流程(责任与协作)
消息创建流程
业务行为发生
→ 发送 MessageEvent
→ 消息系统消费
→ 去重 / 防刷
→ 创建 Message
消息展示流程
查询消息列表
→ 获取 Message
→ Renderer 渲染
→ 返回 MessageVO
职责非常清晰:
- 创建 ≠ 展示
- 消息 ≠ 内容
定义接口与边界
渲染器接口(稳定扩展点)
public interface MessageRenderer {
boolean support(MessageBizType bizType);
MessageVO render(MessageDO message, Long userId);
}
新增消息类型,只新增类,不改旧逻辑。
创建服务接口
public interface MessageCreateService {
void create(MessageCreateCommand command);
}
业务系统只依赖接口,不感知实现细节。
设计类与聚合(实体、不变式)
Message 聚合的边界
Message是聚合根bizType+bizId是外部引用- 不持有业务实体本身
这是为了保证:
- 消息系统不被业务拖死
- 业务数据变化可自然兜底
不变式约束
-
同一 bizType + bizId + 用户,不可短时间重复
-
不给自己发消息
-
不可见消息不展示
这些规则统一收敛在 MessageCreateService 中
选择合适的设计模式(按需)
| 场景 | 模式 |
|---|---|
| 消息渲染 | Strategy |
| Renderer 注册 | Factory |
| 消息创建 | Command |
| 异步通知 | Event / Observer |
不是为了用模式而用,而是问题自然导向。
先写测试,再实现
在这个设计下,测试变得非常自然:
- Renderer 可以独立单测
- 去重逻辑可以 Mock Redis
- 消息立减规则可纯内存验证
轻松自然的测试,都是一个好设计的直接收益。
实现最小可行版本
最小版本必须具备:
Message(消息)MessageCategory(消息分类)BizType(业务类型)Renderer(消息渲染)- 同步创建 + 基础去重
总结
这套消息中心设计并不复杂,但它完整体现了:
- 从需求到模型
- 从行为到职责
- 从变化点到拓展点
真正的面向对象,不是“有多少个类”,而是:
- 变化是否隔离
- 责任是否清晰
- 新需求是否只需要“加代码”
这篇文章并不是在展示一个“功能完整”的消息中心实现,而是刻意停在设计方法与演进思路这一层。
在真实工程中,消息系统往往是慢慢长出来的:
- 一开始只是简单通知
- 后来出现分类、未读数、跳转
- 再后来引入去重、防刷、异步化
- 最终演变为一个独立、可扩展的消息中心
因此,这一篇只是第一篇,重点回答的是:
当我们还不知道未来会有多少消息类型、多少复杂场景时,第一步应该怎么走?
在接下来的系列文章中,我会围绕真实业务,进一步展开:
- 消息模型的详细设计:DTO / DO / VO 如何分层,哪些字段该存,哪些不该存
- 不同消息类型的建模方式:系统通知、违规通知、评论 / 点赞 / @、活动中奖等
- 复杂渲染场景的设计:部分文字跳转、卡片样式、多布局支持
- 消息去重与防刷策略:如何在不牺牲体验的情况下控制系统复杂度
- 异步化与性能设计:从同步直写到事件驱动的演进路径
- 缓存与一致性取舍:未读数、列表缓存如何设计才不“反噬”系统
如果你正在构建或维护一个消息中心,希望这套面向对象的思考路径,能帮助你在需求不断变化时,依然保持系统的清晰与可控。
后续文章会更偏具体设计与代码实践,敬请期待。