4、业务逻辑层设计:业务模块的划分、领域模型设计、服务编排与调度、状态管理
好,我们继续往下走。这一章聊的是业务逻辑层,说白了就是整个多屏系统的“大脑”。屏幕再多、交互再炫,最终还是要靠业务逻辑来驱动。我个人习惯把这一层看作系统的灵魂——它决定了你的多屏方案到底是个“花架子”,还是真正能解决实际问题的生产力工具。
4.1 业务模块的划分:别让代码长成“毛线球”
先说说模块划分。我在项目中遇到过不少团队,一开始图省事,把所有业务逻辑都塞在一个大模块里。结果呢?三个月后,代码就成了一团“毛线球”,改一个地方,崩三个功能。尤其是在多屏场景下,屏幕A的某个操作可能影响到屏幕B和屏幕C,这种耦合一旦失控,调试起来简直要命。
我的建议是:按“业务领域”切分,而不是按“屏幕”切分。举个例子,一个智能会议室系统,可能有预约屏、签到屏、投屏控制屏。如果你按屏幕切,每个屏幕的代码里都混着“预约逻辑”、“签到逻辑”、“投屏逻辑”,那维护起来就是噩梦。正确的做法是:
- 预约领域模块:负责会议室预订、时间冲突检测、通知推送
- 签到领域模块:负责身份验证、签到状态记录、数据上报
- 投屏领域模块:负责设备发现、连接管理、媒体流控制
每个模块只关心自己的“一亩三分地”。屏幕A需要预约功能?直接调用预约模块的接口就行。这样,哪怕以后新增了第4块屏幕,也只需要复用现有模块,不用动底层逻辑。
核心原则:模块之间通过接口通信,不直接依赖内部实现。每个模块可以独立测试、独立部署、独立演进。
4.2 领域模型设计:把业务“翻译”成代码
模块划分好了,接下来就是领域模型设计。嗯,这里我要多说两句。很多新手容易犯一个错:把数据库表结构直接当成领域模型。你想想看,数据库表是面向存储的,而领域模型是面向业务的。两者有交集,但绝不是一回事。
我习惯的做法是:先和业务方聊透,把业务概念抽象出来。比如“会议室”这个实体,在数据库里可能只有“名称、容量、设备列表”几个字段。但在领域模型里,它应该包含“当前状态(空闲/占用/维护中)”、“关联的预约列表”、“支持的投屏协议”等业务属性。说白了,领域模型是业务逻辑的“活地图”,不是数据的“死快照”。
举个例子,一个多屏协同的“投屏会话”模型:
class ScreenSession {
String sessionId; // 会话唯一标识
String hostScreenId; // 发起投屏的屏幕
List<String> clientScreenIds; // 接收投屏的屏幕列表
SessionStatus status; // 状态:连接中、已连接、已断开
MediaStream stream; // 媒体流对象
DateTime createdAt; // 创建时间
// 业务方法
void addClientScreen(String screenId);
void removeClientScreen(String screenId);
void switchStream(MediaStream newStream);
void terminate(); // 终止会话
}
你看,这个模型里既有数据,也有行为。它知道自己能做什么、不能做什么。这就是领域模型的价值——它把业务规则“固化”在代码里,而不是散落在各个if-else中。
小技巧:设计领域模型时,多问问自己“这个对象能做什么事?”而不是“这个对象有哪些字段?”前者是面向对象,后者是面向数据。
4.3 服务编排与调度:让多屏“跳好一支舞”
多屏系统最头疼的是什么?是协调。屏幕A要投屏,屏幕B要显示签到信息,屏幕C要播放视频。如果各自为政,那画面就乱了。这时候就需要服务编排与调度。
我曾经做过一个展厅项目,里面有6块屏幕,每块屏幕的内容需要根据参观者的位置动态切换。一开始我们用的是“硬编码”的方式——每个屏幕自己判断该显示什么。结果呢?参观者走到屏幕A前,屏幕A切换了,但屏幕B还停留在上一帧,体验极差。
后来我换了一种思路:引入一个“编排中心”。所有屏幕的状态变化、内容切换,都由这个中心统一调度。屏幕只负责“执行”,不负责“决策”。
具体来说,服务编排可以分三层:
| 层级 | 职责 | 示例 |
|---|---|---|
| 编排层 | 定义业务流程的“剧本” | “当参观者进入区域A,屏幕1播放视频,屏幕2显示文字介绍” |
| 调度层 | 管理资源、分配任务、处理冲突 | “屏幕1正在被占用?那就排队等待,或者降级显示” |
| 执行层 | 实际调用各模块的接口 | 调用投屏模块的start()方法 |
调度层尤其重要。你想想看,如果两个业务同时要求屏幕1显示不同内容,怎么办?我的做法是引入“优先级+抢占”机制。比如,紧急通知的优先级最高,可以打断正在播放的视频。但如果是两个普通任务,那就按“先来后到”排队。
避坑指南:我曾经遇到过一个问题——调度中心成了单点故障。后来我加了“本地缓存+心跳检测”机制。如果调度中心挂了,每个屏幕可以基于本地缓存的“剧本”继续运行一段时间,不至于完全瘫痪。
4.4 状态管理:别让屏幕“失忆”
最后聊聊状态管理。多屏系统里,状态管理是个大坑。屏幕A和屏幕B之间需要共享状态吗?状态变化了怎么通知其他屏幕?用户操作后,状态是同步更新还是异步更新?这些问题不搞清楚,系统就会出各种“灵异事件”。
我个人的经验是:把状态分为“全局状态”和“局部状态”。
- 全局状态:所有屏幕都需要知道的信息。比如“当前会议是否正在进行”、“投屏会话是否活跃”。这类状态放在一个中心化的状态仓库里(比如Redis或者一个专门的状态服务)。
- 局部状态:只有某个屏幕关心的信息。比如“屏幕A的亮度设置”、“屏幕B的当前音量”。这类状态就留在屏幕本地,不需要同步。
那全局状态怎么同步呢?我推荐用“发布-订阅”模式。状态变化时,状态仓库发布一个事件,所有订阅了该事件的屏幕都会收到通知。举个例子:
// 状态仓库发布事件
stateStore.publish('meeting:started', { meetingId: '123', host: 'screenA' });
// 屏幕B订阅事件
stateStore.subscribe('meeting:started', (data) => {
// 屏幕B自动切换到“会议模式”
switchToMeetingMode(data.meetingId);
});
这样做的好处是:屏幕之间解耦了。屏幕A不需要知道屏幕B的存在,它只管发布事件。谁关心这个事件,谁就去订阅。系统扩展起来非常灵活。
注意:全局状态的更新一定要考虑“一致性”问题。如果两个屏幕同时修改同一个状态,怎么办?我的做法是:指定一个“状态所有者”。比如,投屏会话的状态只能由发起投屏的屏幕修改,其他屏幕只能读取。这样就不会出现冲突。
好了,这一章的内容就这些。业务逻辑层设计,说白了就是三件事:分好模块、建好模型、管好状态。多屏系统之所以复杂,就是因为多了“屏幕间协作”这个维度。但只要把业务逻辑层设计得清晰、解耦、可扩展,再多的屏幕也能玩得转。下一章我们聊聊数据层,到时候再细说。