78 lines
7.7 KiB
Markdown
78 lines
7.7 KiB
Markdown
|
|
# **2.1 系统架构设计原则**
|
|||
|
|
|
|||
|
|
本章节规定了 CPS 研发团队在进行系统设计和架构演进时应遵循的核心指导原则。我们的目标是构建**高内聚、低耦合、易于演进且对开发者友好**的软件系统。
|
|||
|
|
|
|||
|
|
### **2.1.1 架构与框架边界原则**
|
|||
|
|
|
|||
|
|
**核心理念:对外统一规范,对内顺应框架(求同存异)。**
|
|||
|
|
|
|||
|
|
很多团队在追求“跨语言/跨框架统一”时,往往会造出过度抽象的中间层,导致“对抗框架”的现象。我们主张明确界定“宏观系统架构”与“微观框架实现”的边界:
|
|||
|
|
|
|||
|
|
* **系统间(宏观边界):遵循绝对统一的标准。**
|
|||
|
|
* 微服务之间、前后端之间的通信,必须遵循团队统一的 API 规范(如 RESTful / gRPC)、统一的鉴权机制、统一的链路追踪(TraceId)和日志埋点标准。
|
|||
|
|
* 系统间的交互应当是语言无关、框架无关的。
|
|||
|
|
* **系统内(微观落地):优先顺应所选框架的最佳实践。**
|
|||
|
|
* **避免过度抽象:** 不要为了所谓的“框架无关性”去生搬硬套。例如,在 Spring Boot 中应当充分利用其依赖注入(DI)和注解生态(如 @Transactional);在 Django 中应当拥抱其强大的 ORM 和信号机制。
|
|||
|
|
* **不强求内部绝对一致:** 如果团队同时存在 Java 和 Python 栈,不要强求 Python 按照 Java 的厚重分层来写,也不要强求 Java 采用 Python 的极简脚本流。尊重并利用每种语言/框架的原生优势。
|
|||
|
|
|
|||
|
|
### **2.1.2 微服务与单体划分原则与边界**
|
|||
|
|
|
|||
|
|
**核心理念:单体优先,演进式拆分,警惕“分布式单体”。**
|
|||
|
|
|
|||
|
|
* **默认选择:模块化单体 (Modular Monolith)**
|
|||
|
|
* 对于新业务或探索型项目,默认采用**模块化单体**架构。通过良好的包划分(Package/Module)和代码审查来保持内部逻辑的隔离,而不是一上来就拆分物理微服务。
|
|||
|
|
* **微服务拆分的触发条件(何时拆分?):**
|
|||
|
|
* **隔离性要求:** 核心高可用业务(如交易链路)与边缘易错业务(如报表导出)需要物理隔离,防止边缘业务 OOM 拖垮核心逻辑。
|
|||
|
|
* **扩展性要求(Scale):** 计算密集型模块(需要大量 CPU)与 IO 密集型模块(需要大内存/高并发连接)需要采用不同的扩容策略。
|
|||
|
|
* **独立部署与演进:** 某些模块变更极其频繁,而核心模块要求极度稳定,通过拆分实现独立发版。
|
|||
|
|
* **康威定律(团队规模):** 单一代码库的协同开发人数过多,导致大量的代码冲突和发布阻塞。
|
|||
|
|
* **拆分边界与底线:**
|
|||
|
|
* **禁止跨库 JOIN:** 微服务必须拥有独立的数据库(或独立的 Schema)。如果两个服务频繁需要跨库 JOIN,说明它们本该是一个服务。
|
|||
|
|
* **避免网状依赖:** 服务间的调用链路应尽量保持单向或树状,严禁出现 A 调用 B,B 调用 C,C 又调用 A 的循环依赖。
|
|||
|
|
|
|||
|
|
### **2.1.3 分层架构与灵活决策原则**
|
|||
|
|
|
|||
|
|
**核心理念:没有银弹。架构必须匹配业务阶段与框架特性,绝不为了分层而分层。**
|
|||
|
|
|
|||
|
|
软件分层的唯一目的是**隔离变化,控制复杂度**(将业务复杂度与技术实现复杂度剥离开来)。在实际落地时,团队必须基于以下基本原则,**结合项目实际情况进行灵活决策,并在架构决策记录(ADR)中予以说明**。
|
|||
|
|
|
|||
|
|
#### **1\. 分层设计的通用底线原则(必须遵守)**
|
|||
|
|
|
|||
|
|
* **关注点分离 (Separation of Concerns):** 无论采用何种架构,必须严格区分“协议处理(如 HTTP/RPC)”、“业务逻辑计算”和“数据持久化”。**严禁在 Controller/View 层直接拼接 SQL,也严禁在数据访问层包含核心业务判断。**
|
|||
|
|
* **单向依赖流 (Unidirectional Dependency):** 上层可以依赖下层(或外层依赖内层),**绝对禁止**下层反向依赖上层(例如:数据库持久化类绝不能引入 API 层的 request 对象或状态码)。
|
|||
|
|
* **演进式设计 (Evolutionary Design):** 架构是长出来的,不是一开始就设计完美的。允许系统从简单的 CRUD 逐渐重构为复杂的领域驱动模型,前提是模块间的边界清晰。
|
|||
|
|
|
|||
|
|
#### **2\. 因地制宜的模式选择建议(灵活应用)**
|
|||
|
|
|
|||
|
|
以下模式是我们的“架构工具箱”,严禁教条主义,应根据业务场景的实际痛点组合或选择使用:
|
|||
|
|
|
|||
|
|
* **场景 A:敏捷交付与数据驱动型业务(如:后台管理、报表、内容发布)**
|
|||
|
|
* **决策建议:拥抱框架原生的 MVC / MTV 模式,追求效率。**
|
|||
|
|
* 如果使用 Spring Boot,可以采用经典的 Controller \-\> Service \-\> Mapper。Service 作为事务边界,处理所有逻辑。
|
|||
|
|
* 如果使用 Django / Rails,应当顺应框架特色,采用\*\*“胖模型、瘦视图 (Fat Model, Skinny View)”\*\*。利用框架内置的 ORM、信号量和 Serializer 快速打通 CRUD,不要强行造一个虚伪的 Service 层。
|
|||
|
|
* **场景 B:核心交易与复杂规则型业务(如:订单流转、促销计价、资产核心)**
|
|||
|
|
* **决策建议:采用 领域驱动设计 (DDD) 或 干净架构/六边形架构,保护业务资产。**
|
|||
|
|
* 将复杂的业务规则收拢到独立于任何框架的**领域层 (Domain Layer)** 中(纯粹的 POJO/POCO)。
|
|||
|
|
* 基础设施(如数据库、Redis、MQ)通过**依赖倒置 (DIP)** 接入。即使未来从 MySQL 换到 MongoDB,或者从 Spring 换到其他框架,核心业务规则代码一行都不需要改。
|
|||
|
|
* **场景 C:多端展现或前端聚合诉求强烈的业务(如:同时支持 App、小程序、复杂 Web 端)**
|
|||
|
|
* **决策建议:引入 BFF (Backend for Frontend) 层。**
|
|||
|
|
* 不要让底层的核心微服务为了适配某个特定的 UI 而妥协返回臃肿的数据。
|
|||
|
|
* 在前端与核心服务之间建立 BFF 层,专门负责接口聚合、字段裁剪、格式化。BFF 归属前端/客户端团队主导,底层服务归属后端团队主导。
|
|||
|
|
* **场景 D:读写模型严重不平衡,存在性能瓶颈(如:秒杀扣减与复杂商品详情查询)**
|
|||
|
|
* **决策建议:局部或全局引入 CQRS(命令与查询职责分离)。**
|
|||
|
|
* 写操作走复杂的领域模型,保证数据一致性(写入主库)。
|
|||
|
|
* 读操作绕过所有复杂的业务对象,直接通过原生 SQL 或缓存(Redis/ES)构建扁平的 DTO 输出给前端,实现极致的性能。
|
|||
|
|
|
|||
|
|
### **2.1.4 依赖与解耦原则**
|
|||
|
|
|
|||
|
|
**核心理念:降低变更成本,控制爆炸半径。**
|
|||
|
|
|
|||
|
|
* **依赖倒置原则 (DIP \- Dependency Inversion Principle):**
|
|||
|
|
* 高层模块不应该依赖低层模块,两者都应该依赖其抽象(接口)。
|
|||
|
|
* *实践:* 业务逻辑代码不应直接实例化具体的数据库仓储类或第三方 API 客户端,而是通过构造器注入(Constructor Injection)接口,方便后续的替换和单元测试(Mock)。
|
|||
|
|
* **事件驱动与异步解耦:**
|
|||
|
|
* **区分核心流与旁路流:** 主业务流程(如“用户下单”)应当是同步的;而非核心流程(如“下单后发送短信通知”、“增加积分”)应当通过**事件总线 (Event Bus)** 或 **消息队列 (MQ)** 异步解耦。
|
|||
|
|
* *底线:* 旁路流的失败(如短信网关宕机)绝对不能导致主业务流程的回滚或失败。
|
|||
|
|
* **防腐层设计 (ACL \- Anti-Corruption Layer):**
|
|||
|
|
* 在对接老旧的遗留系统或不可控的第三方外部 API(如支付网关、SaaS 接口)时,必须在系统边界建立防腐层(Adapter/Facade)。
|
|||
|
|
* *实践:* 将外部混乱的数据模型在防腐层转换为系统内部干净的标准模型,防止外部概念“污染”我们的核心业务代码。外部接口升级时,只需修改防腐层即可。
|