软件架构之职责边界风格(RB)
风格名 :职责边界风格(简称 RB 风格 )
英文名 :Responsibility Boundary Style(RB )
一句话:业务按职责切成独立模块,负载与基础设施放在支撑层;写业务代码时不必关心扩容、队列、缓存实现------像写单应用一样简洁。
本文定义一种可独立采用的开发风格与标准。它不绑定某一框架、语言或仓库;任何按同一原则切分业务、外置支撑的系统都可以称为 RB 实现。文末附一份落地案例,仅作对照,不是标准本身。
1. 要解决什么问题
多数系统不是毁在「不会写功能」,而是毁在职责没切干净:
| 常见后果 | 根因 |
|---|---|
| 改一处、炸一片 | 业务模块互相引用实现,耦合成一张网 |
| 业务代码越写越脏 | 队列、缓存、线程池、分库键散落在用例代码里 |
| 扩容必须改业务 | 负载能力绑在业务方法上,而不是绑在支撑层 |
| 拆服务等于重写 | 边界只在口头上,代码里仍是进程内直调实现 |
良好的软件工程,首先应定好职责边界。边界一旦含糊,模块化、微服务、高并发都只是把混乱换了一种部署形态。
RB 风格的立场是:
- 业务模块化,避免代码耦合、牵一发动全身。
- 支撑能力外置,负载能力不侵入、不绑定业务代码。
- 开发体验单应用化:封装支撑细节后,业务开发者只写用例。
2. 风格定义
职责边界风格是一种以「职责」为第一刀、以「支撑外置」为第二刀的模块化风格:
text
┌─────────────────────────────────┐
│ 业务层(可替换、可拆分) │
│ 按域划分的业务模块 │
│ 只表达:谁在什么条件下做什么 │
└────────────────┬────────────────┘
│ 只认契约(接口 / 事件)
┌────────────────▼────────────────┐
│ 支撑层(可升级、可扩容) │
│ 通信 · 缓存 · 安全 · 部署装配 │
└────────────────┬────────────────┘
│
┌────────────────▼────────────────┐
│ 数据层(由库自身承担负载) │
│ 索引 · 事务 · 隔离 · 容量规划 │
└─────────────────────────────────┘
三层含义:
- 业务与业务之间:强边界。只依赖对方的契约,不依赖对方的实现与表结构。
- 业务与支撑之间:单向依赖。业务使用支撑提供的稳定 API;支撑不得反向依赖某个业务域。
- 负载与业务之间:解绑。吞吐、排队、重试、水平扩展是支撑层与数据层的职责,不是业务方法里的「技巧」。
这与「先上微服务再谈治理」相反:先把边界写进代码与仓库结构,部署形态可以后变。
3. 三条主原则
3.1 业务分模块:边界写进结构,而不是写进口头
模块是职责单元,不是包名装饰。
| 要求 | 含义 |
|---|---|
| 一个模块一件事 | 各业务域自成一体,不互相吞并实现 |
| 契约与实现分离 | 对外只暴露契约;实现与持久化留在模块内部 |
| 禁止实现级耦合 | 禁止业务模块依赖他域的实现包或持久化模型 |
| 变更半径可控 | 重构某一模块内部,只要契约不变,其他模块零改动 |
判定:一个模块的内部重构,只要契约不变,其他模块应零改动。若做不到,边界就还不够强。
3.2 支撑外置:负载能力不绑定业务代码
业务开发者不必关心本模块明天是 1 个实例还是 20 个实例。负载能力应作为支撑层提供:
| 支撑能力 | 放哪 | 业务侧只看见 |
|---|---|---|
| 模块间通信 | 消息中间件(同步请求-应答 + 异步事件) | 调用契约方法 / 发布领域事件 |
| 水平扩展 | 装配单元 + 竞争消费 + 入口分流 | 配置与部署,不改用例代码 |
| 重试 / 死信 | 统一的消息基线 | 业务只保证幂等 |
| 会话、验证码等短时数据 | 统一的短时存储门面 | 按命名空间读写,不手写中间件方言 |
| 认证与安全 | 支撑层过滤器与会话存储 | 业务只取当前用户,不实现令牌解析 |
消息中间件在本风格中的位置 :它不是「异步的可选项」,而是模块间通讯的支撑介质。中间件天生具备排队、缓冲、多消费者竞争、按域拆通道等负载能力;把通讯交给这一层之后,业务代码不必自己做线程池堆积、手工远程调用他域、或在用例里写扩容分支。
封装支撑细节的目标只有一句:
写业务就像写单应用一样简洁。
3.3 支撑能力不得侵入业务代码
「用了缓存 / 消息 / 数据库」不等于「业务里要出现这些实现细节」。
| 能力 | 正确职责 | 侵入业务时的样子(禁止) |
|---|---|---|
| 缓存 | 加速与临时存储(短 TTL 凭证、会话快照) | 把缓存当第二数据库;业务里散落客户端、自创存储分区、用缓存表达领域状态 |
| 消息 | 跨模块通讯与削峰 | 业务里手写中间件客户端、手配通道绑定、事务未提交就发消息 |
| 数据库 | 持久化与数据层负载 | 用缓存硬扛本该由索引 / SQL / 库容量解决的查询;把分库键、路由策略写进用例 |
数据库负载的理想结果 :查询慢、写入争用、容量上限,应尽量由数据库自身解决------索引、执行计划、隔离级别、只读副本、分库分表(若必须)落在数据层及其配置,而不是渗进业务分支。业务只描述「存什么、按什么业务键查」。
缓存可以加速热点读、保存临时凭证,但不能成为业务正确性的来源,也不能替代本该建的索引与合理的表设计。
4. 标准条款
以下条款把风格落成可检查的标准。违反任一条,即视为偏离 RB 风格。条款描述的是职责与约束,不规定必须使用哪一种框架或命名。
4.1 职责边界
- 跨模块只依赖契约:接口、传输对象、枚举、可订阅的事件定义。
- 契约层不得依赖任何业务实现,不得引用他域(或本域实现层)的持久化模型。
- 仅本模块使用的配置维护、渠道插件、管理端专属操作,留在模块内部,不升格为跨模块契约。
- 模块边界由自动化检查守护(架构测试、依赖扫描等),而不是只靠人工记忆。
4.2 支撑外置
- 传输与中间件实现只存在于支撑层。业务模块禁止自实现跨模块远程客户端。
- 同步协作:注入契约并调用方法,体验等同本地调用;底层走进程内直调或远程请求-应答,由配置切换,业务代码不变。
- 异步协作:在数据提交成功之后发布事件,由他域订阅处理;禁止用进程内广播冒充跨模块协作。
- 有数据库事务时,发消息等副作用必须在提交之后执行;由支撑层提供「提交后再做」的能力。
4.3 负载与数据
- 扩容优先改装配与拓扑(多实例、按域拆部署、入口分流),而不是改业务算法「以适配高并发」。
- 缓存只用于加速与临时存储;业务状态以数据库为准。
- 数据库访问保持业务语义(按业务键查询与关联);负载与存储优化下沉到库与持久层配置。
- 业务代码不堆砌厂商中间件方言(连接工厂、通道名、缓存分区名等由支撑层约定或自动生成)。
4.4 开发体验
- 默认形态可以是模块化单体(多业务模块进同一进程),以便交付;契约已按远程协作书写,拆进程时业务代码不变。
- 新增跨模块能力时先问「要不要立即返回」:要 → 同步契约调用;不要且需广播 → 事件。不要在业务里临时开一条「方便的直连」。
5. 与相邻思想的关系
RB 风格不否定既有方法,而是给出一套组合与优先级。
| 思想 | 关系 |
|---|---|
| 模块化单体 | 常见的默认部署形态:边界先于进程。单进程不等于无边界。 |
| 微服务 | 演进结果,不是起点。契约不变,只换装配与入口分流。 |
| 整洁架构 / 六边形 | 同样强调业务不依赖基础设施细节;RB 进一步把「负载」明确划给支撑层与数据库。 |
| DDD 限界上下文 | 模块 ≈ 上下文。RB 要求把上下文做成仓库结构与自动化检查都能守护的硬边界。 |
| 消息驱动 | 消息是支撑层通讯,不是把所有用例都做成最终一致。需要立即返回的编排仍走同步契约。 |
刻意不做的事:一上来按流量拆服务、在业务里用缓存「模拟」数据库、让每个模块自己封装一套远程调用。那些都会把职责边界重新搅浑。
6. 一分钟自检
做设计或 Review 时只问五句:
- 这件事属于哪个业务模块? 不能回答,说明职责还没切。
- 他域是否只依赖契约? 若引用了别人的持久化模型或实现类,边界已破。
- 这段代码是在表达业务,还是在表达负载? 后者应回到支撑层或数据层。
- 拿掉缓存实现 / 换消息中间件 / 把该域拆成独立进程,用例方法是否仍可读? 若大量改业务,说明支撑已侵入。
- 这个读压力是否本该由数据库解决? 先索引与查询,再谈缓存。
全部能干净回答,即符合职责边界风格。
7. 命名说明
全称 职责边界风格 ,简称 RB 风格(Responsibility Boundary)。选用这一名称,而不是「模块化单体」或「微服务」,是因为:
- 职责边界是第一刀:先切清谁负责什么,再谈进程数与技术栈。
- 风格表示可复用的开发约定,不是某一种部署拓扑的别名。
- 模块化是落实边界的手段;单体或微服务只是支撑层上的装配选择。风格的稳定性来自边界与外置,而不是来自「现在有几个进程」。
附录 · 案例:TG-boot
以下仅说明 TG-boot 如何实现 RB,便于对照本仓库代码。条款本身不依赖这些类名与模块名;换一套实现,只要满足上文标准,仍是 RB。
| RB 概念 | TG-boot 中的落点 |
|---|---|
| 业务模块 | spring-boot-starter-components / business 下各域 *-api + *-biz |
| 支撑层(通信、缓存、安全、消息基线) | spring-boot-starter-common |
| 装配与负载拓扑 | spring-boot-starter-runner;可按域再拆 runner |
| 边界守护 | spring-boot-starter-architecture-tests |
| 同步契约 | Api**Service + ModuleApi 代理(LOCAL 或 MQ Request-Reply) |
| 异步契约 | *-api/subscribe/XxxConsumer;MqPublisher.publishAfterCommit |
| 短时缓存 | TgEphemeralCache(单区域 + namespace::key) |
| 持久化约定 | BaseEntity + 业务主键 xxxCode;负载问题优先在库侧解决 |