面向对象开发实践之消息中心设计(一)

58 阅读5分钟

面向对象不是写更多类,而是提前为变化留出空间。

明确需求与用例(Why / What)

1. 为什么要单独设计消息中心?(Why)

在大多数系统早期,消息往往是“顺手写”的:

  • 评论时顺便插一条消息
  • 点赞时 if-else 发通知
  • 活动时手动拼文案

短期能跑,但很快就会出现:

  • 消息类型越来越多
  • 业务系统相互调用
  • 重复消息、刷屏
  • 一改样式牵一大片代码
  • 增加一种消息类型,就需要前端适配,并且服务端需要区分版本

根因只有一个:没有把「消息」当成独立领域建模。

2. 消息中心到底要解决什么问题?(What)

抽象后的核心需求只有三点:

  1. 告诉用户“发生了什么”
  2. 以合适的形式展示
  3. 在合适的时机送达

而不是:

  • 拼字符串
  • 堆字段
  • 写死跳转

要做什么?核心行为是什么

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 如何分层,哪些字段该存,哪些不该存
  • 不同消息类型的建模方式:系统通知、违规通知、评论 / 点赞 / @、活动中奖等
  • 复杂渲染场景的设计:部分文字跳转、卡片样式、多布局支持
  • 消息去重与防刷策略:如何在不牺牲体验的情况下控制系统复杂度
  • 异步化与性能设计:从同步直写到事件驱动的演进路径
  • 缓存与一致性取舍:未读数、列表缓存如何设计才不“反噬”系统

如果你正在构建或维护一个消息中心,希望这套面向对象的思考路径,能帮助你在需求不断变化时,依然保持系统的清晰与可控。

后续文章会更偏具体设计与代码实践,敬请期待。