type
status
date
slug
summary
tags
category
icon
password
catalog
sort
“写代码就像盖城市,短期靠堆人、长期靠规划。” —— 本文题记
前言:为什么要写这篇文章?
过去 12 个月,我在 2个不同行业的团队里做架构治理,发现 90% 的项目在演进过程中都会掉进同一类陷阱:
- 代码像“一锅粥”:业务、数据、协议逻辑搅在一起,改一行毁十行;
- 命名随心所欲:
a1
、temp
满天飞,新人三天就想跑路;
- 领域模型“四不像”:VO、DTO、DO、PO 傻傻分不清,项目一膨胀就崩;
- 微服务拆分拍脑袋:今天拆、明天合,上线后性能雪崩。
为了让知识真正可落地,我花了 14 天把三篇内容吃干抹尽,结合阿里、美团、京东、字节 20+ 公开案例,整理出这份终极笔记。
读完你将获得:
- 一张可打印的Java 工程规范 Checklist;
- 一套从贫血到充血的领域建模路线图;
- 一份分层架构反模式地图(避开 80% 团队踩过的坑);
- 一个可套用的微服务拆分公式。
1. 核心概念:系统、模块、组件、框架与架构
“概念是思考的锚点,锚点不稳,船就飘。”—— 《架构整洁之道》
在讨论规范与架构前,必须先明确基础概念的边界。很多团队的混乱,从“把模块当组件”“把框架当架构”开始。
软件架构作为系统的顶层结构,其核心价值在于通过系统性的思考与决策,在资源约束下构建出既满足当前业务需求,又能适应未来发展的系统骨架。理解架构的本质,是进行规范设计的前提。
软件架构是指系统的顶层结构,包含四个关键要素:
- 系统性决策:涵盖技术选型、解决方案设计等战略性选择
- 明确的系统骨架:定义系统由哪些子系统、模块、组件构成
- 协作关系:确定各组成部分之间的交互方式与接口规范
- 约束与原则:建立指导系统开发与演化的规则体系
正如《架构即未来》中所述:"架构的本质是对系统进行有序化重构,以符合业务发展并支持快速扩展"。一个优秀的架构设计应当是业务驱动的,而非技术驱动的炫技产物。
1.1 五大核心概念对比
概念 | 一句话定义 | 举例 | 常见误区 |
系统 | 完成特定目标的关联个体集合 | 电商交易系统、支付系统 | 把“子系统”等同于“模块” |
模块 | 逻辑上的功能单元 | 订单模块、用户模块 | 与“包(package)”混淆(模块是逻辑划分,包是物理组织) |
组件 | 可独立部署的物理单元 | MySQL、Redis、订单服务 | 把“jar包”当组件(组件需具备独立部署能力) |
框架 | 半成品可复用代码 | Spring、MyBatis | 把“架构”等同于“框架”(框架是工具,架构是设计) |
架构 | 顶层结构+决策约束 | 微服务架构、六边形架构 | 认为“画几张框图就是架构”(架构需包含约束) |
📌 记忆口诀:系统是大楼,模块是楼层,组件是砖头,框架是脚手架,架构是图纸+施工规范。
1.2 概念关系:从抽象到具体
用“盖楼”类比:
- 系统是整栋大楼(如“商业综合体”);
- 模块是楼层功能划分(如“餐饮层”“零售层”);
- 组件是可独立更换的物理单元(如“电梯”“空调系统”);
- 框架是脚手架(如“钢筋骨架”,规定了搭建方式);
- 架构是图纸+施工规范(不仅规定大楼形状,还明确“承重墙不能拆”“管道必须走吊顶”等约束)。
理解这种关系的关键:模块是逻辑划分,组件是物理实现;框架是代码复用,架构是决策集合。
1.3 架构的分类体系
从不同维度可将架构划分为以下类型,各类型既相互独立又协同工作:
架构类型 | 核心关注点 | 典型产出物 |
业务架构 | 业务模块划分与流程设计 | 业务流程图、领域模型 |
应用架构 | 系统逻辑分层与模块边界 | 应用架构图、接口规范 |
数据架构 | 数据存储与流转设计 | ER图、数据字典 |
代码架构 | 代码组织与开发规范 | 代码目录结构、编码规范 |
技术架构 | 非功能性需求实现 | 部署拓扑、中间件选型 |
部署架构 | 物理资源分配 | 服务器部署图、网络拓扑 |
业务架构作为战略层,决定了应用架构的设计方向;应用架构作为战术层,承接业务需求并指导技术选型;而代码架构则是架构落地的最终载体,直接影响开发效率与系统质量。
1.4 架构设计的基本原则
优秀的架构设计应当遵循以下原则:
- 合适性优先:不存在普适的"最优架构",只有适合具体业务场景的架构
- 演进式设计:架构应随业务发展逐步优化,而非一次性完美设计
- 关注点分离:通过分层与模块化降低系统复杂度
- 高内聚低耦合:模块内部功能紧密相关,模块之间依赖最小化
- 前瞻性设计:预留扩展点以适应未来1-2年的业务发展
阿里巴巴《Java开发手册》中强调:"脱离业务的架构设计是耍流氓"。架构设计的最终目标是解决业务问题,任何技术选型都应服务于此。
2. 六大设计原则:架构设计的“宪法”
设计原则是架构规范的底层逻辑,所有分层、命名、模型设计都需遵循这些原则。违反原则的代码,短期可能“能跑”,但长期必然陷入混乱。
2.1 单一职责原则(SRP)
定义
一个类/模块只负责一项职责,即“一个类只有一个引起它变化的原因”。
核心思想
- 职责=引起变化的原因:如果一个类需要因A需求改一次,因B需求改一次,说明它承担了两个职责。
- 目的:降低类的复杂度(只做一件事)、提高可读性(功能聚焦)、减少变更影响(改A不影响B)。
反例(违反SRP)
正例(遵循SRP)
框架中的应用
- MyBatis的
SqlSession
:只负责SQL执行,不处理连接池(连接池由DataSource
负责);
- Spring的
@Controller
:只处理HTTP协议适配,业务逻辑交给@Service
。
2.2 开闭原则(OCP)
定义
对扩展开放,对修改关闭。即“新增功能时,通过扩展现有代码实现,而非修改原有代码”。
核心思想
- 关键:抽象稳定,实现可变。通过接口/抽象类定义稳定契约,具体实现类可扩展。
- 目的:避免修改原有代码引入风险(尤其核心逻辑),提高系统可扩展性。
反例(违反OCP)
正例(遵循OCP)
框架中的应用
- Spring的
BeanPostProcessor
:通过实现接口扩展Bean初始化逻辑,无需修改Spring核心代码;
- 日志框架SLF4J:通过抽象接口定义日志规范,具体实现(Logback/Log4j)可自由替换。
2.3 里氏替换原则(LSP)
定义
子类必须能替换其父类(即“任何使用父类的地方,都能透明地使用子类对象”)。
核心思想
- 子类不能破坏父类的契约(如前置条件不能更强、后置条件不能更弱);
- 目的:保证继承关系的合理性,避免因子类“越界”导致父类逻辑失效。
反例(违反LSP)
正例(遵循LSP)
框架中的应用
- Java集合
List
接口:ArrayList
和LinkedList
都严格遵循List
契约,可互相替换;
- Spring的
Resource
接口:FileResource
/ClassPathResource
都实现了资源读取契约,替换后不影响调用方。
2.4 依赖倒置原则(DIP)
定义
依赖抽象,而非具体实现。即“高层模块不依赖低层模块,两者都依赖抽象;抽象不依赖细节,细节依赖抽象”。
核心思想
- 抽象(接口/抽象类)是稳定的,具体实现是可变的;
- 目的:降低模块间耦合(高层不绑定低层实现),提高系统灵活性。
反例(违反DIP)
正例(遵循DIP)
框架中的应用
- Spring的依赖注入(DI):通过接口注入实现,高层Bean依赖接口而非具体类;
- Java的JDBC:
Connection
/Statement
是抽象接口,具体实现由数据库驱动提供,应用程序依赖接口即可。
2.5 接口隔离原则(ISP)
定义
客户端不应依赖其不需要的接口(即“接口应最小化,避免臃肿”)。
核心思想
- 一个接口只包含客户端需要的方法,不包含无关方法;
- 目的:避免客户端被迫依赖无用方法,减少变更影响(修改接口中无用方法时,客户端无需感知)。
反例(违反ISP)
正例(遵循ISP)
框架中的应用
- Java的
Collection
体系:List
/Set
/Queue
拆分不同接口,避免客户端依赖无关方法;
- Spring的
InitializingBean
/DisposableBean
:拆分初始化和销毁接口,Bean可按需实现,无需依赖无用方法。
2.6 迪米特法则(LOD)
定义
一个对象应尽可能少地了解其他对象(即“只与直接朋友通信”)。
核心思想
- 直接朋友:成员变量、方法参数、返回值中的对象;
- 非直接朋友:方法内部创建的临时对象、通过朋友的朋友获取的对象;
- 目的:减少对象间耦合(知道得越少,受影响越小)。
反例(违反LOD)
正例(遵循LOD)
框架中的应用
- Spring的
ApplicationContext
:提供getBean()
方法,隐藏Bean的创建细节,调用方无需关心Bean的依赖关系;
- MyBatis的
SqlSession
:提供selectOne()
/insert()
等方法,封装JDBC底层操作,调用方无需了解Connection/Statement。
2.7 六大原则关系总结
原则 | 核心解决问题 | 关键词 |
单一职责 | 类/模块的功能聚焦 | 职责唯一 |
开闭原则 | 扩展与修改的平衡 | 抽象稳定,实现可变 |
里氏替换 | 继承关系的合理性 | 子类不破坏父类契约 |
依赖倒置 | 模块间的耦合方向 | 依赖抽象,不依赖具体 |
接口隔离 | 接口的最小化设计 | 接口只包含必要方法 |
迪米特法则 | 对象间的通信边界 | 只与直接朋友通信 |
记忆口诀:单开理依接迪(单一职责、开闭、里氏替换、依赖倒置、接口隔离、迪米特)。
3 Java工程的分层架构设计
合理的分层是Java工程规范的基础,它通过明确各层职责,实现"高内聚、低耦合"的设计目标,使系统更易于理解、开发和维护。
3.1 经典分层架构
主流的Java工程采用多层次架构设计,各层职责清晰且遵循严格的依赖关系。典型的分层结构如下:
- 开放接口层(API/Controller)
- 职责:封装服务接口,处理HTTP/RPC请求,进行参数校验与结果封装
- 特征:轻业务逻辑,仅负责请求转发与基本校验
- 技术实现:Spring MVC的@Controller/@RestController
- Web层
- 职责:请求路由、参数解析、简单权限控制
- 特征:不处理复杂业务,仅做请求预处理
- 典型组件:拦截器、过滤器、请求适配器
- 业务逻辑层(Service)
- 职责:实现核心业务逻辑,处理事务管理
- 特征:包含具体业务规则,可调用多个Manager或DAO
- 技术实现:@Service注解,@Transactional事务控制
- 通用业务处理层(Manager)
- 职责:封装通用业务能力,处理跨领域逻辑
- 特征:高复用性,可封装第三方接口、缓存逻辑等
- 典型应用:MQ消息处理、缓存管理、第三方API适配
- 数据访问层(DAO)
- 职责:与数据库交互,执行CRUD操作
- 特征:仅处理数据存取,不包含业务逻辑
- 技术实现:MyBatis的Mapper接口,JPA的Repository
- 领域模型层(Model)
- 职责:定义各类数据对象(DO/DTO/VO等)
- 特征:POJO类,主要包含属性与getter/setter
- 外部接口层
- 职责:与第三方系统交互
- 特征:封装外部系统接口,处理协议转换与异常映射
各层之间遵循严格的依赖规则:上层可依赖下层,下层不可依赖上层。即API层可依赖Web层和Service层,Service层可依赖Manager层和DAO层,但反之则不允许。
3.2 代码分层:为什么4层比3层更稳?
传统教科书讲“Controller/Service/DAO”三层,但生产环境中,Service层往往因承担过多职责而膨胀。美团、阿里等团队的实践证明,在Service与DAO之间增加“Manager层”能显著提升代码清晰度。
经典三层面临的问题
- Service 膨胀:一个类 3000 行,改一行得读 30 分钟;
- 跨层调用:Controller 直接调 DAO,一删字段全崩;
- 无法单测:Service 依赖 HttpServletRequest,单测要启动 Spring。
四层架构职责划分
层级 | 定位 | 禁止做的事 | 事务边界 |
Controller | 协议适配(HTTP/Dubbo/gRPC) | ① 业务逻辑 ② 直接操作 DAO | 无 |
Service | 编排业务流程(组合多个 Manager) | ① 直接 SQL ② 远程调用无兜底 | 有(@Transactional) |
Manager | 复用性业务(缓存、第三方接口封装) | ① 事务注解 ② 依赖 DAO 以外的下层 | 无 |
DAO/Mapper | 数据访问(MyBatis/JPA) | ① 业务判断 ② 返回 VO/DTO | 无 |
📌 美团点评在 2023 年公开案例里,把 Manager 叫 InfrastructureService,本质一样:屏蔽技术细节,让 Service 更纯粹。
3.3 四层架构实践
- Controller层
- 负责接收前端请求(HTTP/JSON)或RPC调用,转换为DTO;
- 进行参数校验(如
@Valid
注解); - 调用Service层,将结果转换为VO返回;
- 处理协议相关逻辑(如HTTP状态码、异常转换)。
示例:
- Service层
- 负责核心业务流程编排(如“下单=扣库存+创建订单+通知用户”);
- 管理事务边界(确保操作原子性);
- 协调多个Manager或DAO完成业务。
示例:
- Manager层
- 封装复用性逻辑(如缓存操作、第三方接口调用);
- 对DAO层进行二次封装(如分页查询、结果转换);
- 处理跨领域的通用业务(如日志记录、权限校验)。
示例:
- DAO层
- 负责与数据库交互(MyBatis/JPA);
- 只做CRUD操作,不包含业务逻辑;
- 输入输出为DO(与数据库表映射)。
示例:
4 领域模型思想
领域模型的模式是“构建块”,包括实体、值对象、聚合(根)、领域服务、事件等,用于精准映射业务概念;血缘关系是“连接线”,通过结构关联和行为协作,将构建块组织成有机的领域模型。
- 设计模式的核心是“职责划分”:实体负责自身状态和行为,值对象描述特征,聚合保证一致性,服务处理跨实体逻辑,事件解耦协作。
- 血缘关系的核心是“合理依赖”:聚合内强关联、聚合外弱引用,通过事件和服务减少直接耦合,保证模型的灵活性和可维护性。
理解两者的关系,能帮助开发者在复杂业务中设计出既贴合业务、又低耦合的领域模型。
4.1 领域模型的设计模式
领域模型的设计模式是对领域中重复出现的业务场景和结构的抽象,核心目标是精准映射业务逻辑、降低复杂度,并保证模型的稳定性和可扩展性。常见的设计模式包括以下几类:
1. 核心领域对象模式
这类模式是领域模型的基础构成单元,用于抽象业务中的核心实体和概念。
- 实体(Entity)
- 定义:具有唯一标识(Identity),且在生命周期中状态可变化的对象。其核心是“是谁”而非“是什么”。
- 特点:
- 唯一标识贯穿生命周期(如用户ID、订单号),即使属性全变,标识不变则实体不变;
- 状态可变(如用户的地址、订单的状态);
- 通常包含业务行为(如订单的“取消”操作)。
- 示例:电商中的“用户”“订单”,金融中的“账户”。
- 值对象(Value Object)
- 定义:通过属性值来定义自身的对象,无唯一标识,用于描述实体的特征。
- 特点:
- 无唯一标识,属性完全相同则视为同一个对象;
- 不可变(创建后属性不可修改,修改需创建新对象),避免副作用;
- 通常用于封装一组相关属性(如“地址”包含省、市、街道;“金额”包含数值和币种)。
- 示例:“地址”“金额”“时间区间”“商品规格(颜色、尺寸)”。
- 聚合(Aggregate)与聚合根(Aggregate Root)
- 聚合:一组紧密关联的实体和值对象的集合,作为一个整体保证数据一致性(事务边界)。
- 聚合根:聚合中唯一可被外部直接访问的实体,负责管理聚合内的其他对象,维护聚合的 invariants(不变规则)。
- 特点:
- 聚合内的对象可直接关联(如订单包含订单项);
- 聚合外的对象只能通过聚合根的标识引用聚合内的对象(避免跨聚合直接关联,降低耦合);
- 聚合根是事务的最小单元(修改聚合内的对象需通过聚合根,保证原子性)。
- 示例:电商中“订单聚合”包含订单(聚合根)、订单项(实体)、收货地址(值对象);订单聚合外的“库存”只能通过订单ID关联订单,不能直接关联订单项。
2. 行为封装模式
当业务逻辑无法归属到单个实体或值对象时,需通过专门的模式封装行为。
- 领域服务(Domain Service)
- 定义:封装跨多个实体/聚合的业务逻辑,无状态(或仅含临时状态),以动词为核心命名(如“转账”“计算税费”)。
- 特点:
- 依赖实体/聚合根完成业务操作(而非自身存储数据);
- 输出是对实体状态的修改或领域事件的发布;
- 仅包含领域层的纯业务逻辑(不涉及技术细节,如数据库操作)。
- 示例:金融领域的“转账服务”(需操作转出账户和转入账户两个聚合根);电商的“订单结算服务”(需关联订单、支付、库存三个聚合)。
- 领域事件(Domain Event)
- 定义:记录领域中发生的重要事件,用于解耦跨聚合的业务协作(基于事件的通信)。
- 特点:
- 包含事件发生的时间、触发者(如哪个实体)、关键数据(如订单ID、状态);
- 不可变(事件一旦发生,内容不可修改);
- 由实体/聚合根的操作触发(如订单“创建成功”“支付完成”),被其他领域组件(如领域服务、外部系统)订阅。
- 示例:“订单创建事件”(触发库存扣减)、“支付成功事件”(触发订单状态更新)。
- 领域工厂(Domain Factory)
- 定义:封装复杂实体/聚合的创建逻辑,尤其是当创建过程涉及多个步骤、依赖多个对象或需保证 invariants 时。
- 特点:
- 隐藏创建细节(如聚合根的初始化需关联多个子对象);
- 确保创建的对象符合领域规则(如“订单必须至少包含一个订单项”)。
- 示例:“订单工厂”负责创建订单聚合,确保订单创建时订单项非空、收货地址有效。
3. 边界与抽象模式
用于划分领域的边界,隔离不同的业务上下文。
- 限界上下文(Bounded Context)
- 定义:领域中一个语义一致的边界,边界内的领域模型(实体、服务等)有统一的术语和规则,边界外可能有不同的模型映射(如“用户”在电商上下文和支付上下文可能有不同属性)。
- 作用:解决大型系统中“同一术语在不同业务场景下含义不同”的问题,避免模型混乱。
- 示例:电商系统中的“订单上下文”“库存上下文”“支付上下文”;同一“商品”在订单上下文关注价格,在库存上下文关注库存数量。
4.1 模型特征与血缘关系
领域模型的血缘关系指各设计模式之间的关联、依赖和协作关系,体现了领域模型的结构和行为逻辑。血缘关系可分为结构血缘(静态关联)和行为血缘(动态依赖)。
领域模型的设计涉及三种典型模式,各有其适用场景:
- 失血模型
- 特征:模型仅包含属性和getter/setter,无任何业务逻辑
- 业务逻辑全部放在Service层实现
- 优势:简单直观,易于理解
- 局限:Service层负担过重,不符合面向对象设计
- 适用场景:简单CRUD应用
- 贫血模型
- 特征:模型包含简单的原子服务(如获取关联ID),核心业务逻辑仍在Service层
- 优势:一定程度上减轻Service层负担
- 局限:数据与行为分离,仍不完全符合面向对象思想
- 适用场景:中小型应用,大多数Spring项目采用此模式
贫血模型(Anemic Domain Model)是指领域对象(如OrderDO)仅包含属性和getter/setter,无业务逻辑,所有逻辑都放在Service层。
- 充血模型
- 特征:模型包含大部分业务逻辑,Service层仅做事务控制与协调
- 优势:符合面向对象设计,数据与行为封装在一起
- 局限:实现复杂,可能导致模型依赖DAO层
- 适用场景:领域驱动设计(DDD),复杂业务场景
充血模型(Rich Domain Model)是指领域对象包含属性和业务逻辑(如订单状态转换、金额计算)数据与行为合一,Service层仅负责事务和流程编排。
Spring创始人Rod Johnson曾承认,Spring框架采用的贫血模型本质上是"事务脚本"模式(面向过程编程)。但这并不意味着贫血模型应该被摒弃——在实际项目中,应根据业务复杂度选择合适的模型模式。
1. 结构血缘(静态关联)
指模型元素在结构上的包含、引用关系,体现在代码或设计图中。
- 实体与值对象的关系:实体通常“包含”值对象(值对象是实体的属性)。
- 示例:用户(实体)包含“联系地址”(值对象);订单项(实体)包含“商品规格”(值对象)。
- 实体之间的关系:
- 聚合内:实体可直接关联(如订单(实体)包含多个订单项(实体),一对多关系);
- 聚合外:实体通过“聚合根标识”间接引用(如库存实体通过“商品ID”引用商品聚合根,而非直接关联商品实体)。
- 聚合与聚合根的关系:聚合根是聚合的“核心”,聚合包含聚合根、其他实体和值对象(聚合根→实体→值对象,形成层级包含)。
- 示例:订单聚合 = 订单(聚合根)→ 订单项(实体)→ 商品规格(值对象);同时订单聚合根包含收货地址(值对象)。
- 领域服务与实体/聚合的关系:领域服务“依赖”实体/聚合根完成业务逻辑(服务调用实体的方法,或修改实体的状态)。
- 示例:转账服务依赖“转出账户”和“转入账户”两个聚合根,调用账户的“扣款”和“收款”方法。
- 领域事件与实体/聚合的关系:领域事件由实体/聚合根的操作“触发”(实体是事件的“源”),事件中包含实体的标识或关键属性。
- 示例:订单实体执行“支付”操作后,触发“订单支付成功事件”,事件包含订单ID、支付金额、支付时间。
2. 行为血缘(动态协作)
指模型元素在业务流程中的交互关系,体现“谁触发谁、谁依赖谁”的动态逻辑。
- 触发链:实体操作→领域事件→其他实体/服务响应。
- 示例:用户提交订单(实体操作)→ 触发“订单创建事件”→ 库存服务订阅事件,执行“扣减库存”(调用库存聚合根的“扣减”方法)。
- 依赖链:领域服务→实体/聚合根→值对象。
- 示例:结算服务(领域服务)→ 依赖订单聚合根(获取订单金额)和税率值对象(获取税率)→ 计算税费(返回税费值对象)→ 更新订单聚合根的“税费”属性。
- 聚合内的协作:聚合根协调内部实体/值对象完成操作,保证不变规则。
- 示例:订单(聚合根)执行“添加订单项”操作时,需检查订单项的商品是否已存在(通过订单项实体的商品ID),若存在则合并数量(调用订单项的“合并”方法),最终保证“同一商品在订单中不重复”的不变规则。
4.2 领域驱动设计(DDD)核心概念
充血模型是DDD的基础,理解DDD概念有助于更好地设计领域模型:
概念 | 定义 | 示例 |
实体(Entity) | 有唯一标识,状态可变的领域对象 | Order(订单有唯一ID,状态会变) |
值对象(Value Object) | 无唯一标识,状态不可变的对象 | Money、Address |
聚合(Aggregate) | 实体和值对象的集合,有聚合根 | Order(根)+ OrderItem + Money |
领域服务 | 处理跨实体逻辑,无状态 | PaymentService(协调订单和支付) |
仓储(Repository) | 封装实体持久化,模拟内存集合 | OrderRepository |
限界上下文 | 领域模型的边界,上下文内模型语义一致 | 订单上下文、库存上下文 |
领域模型是数据在各层流转的载体,混淆模型职责是代码混乱的主要原因。阿里《Java开发手册》明确规定:“禁止跨层使用模型对象”。
5 命名规范:让代码自解释
命名是代码可读性的核心要素,良好的命名能使代码"自解释",减少注释需求。《Clean Code》中强调:"好的代码本身就是注释",而规范的命名是实现这一目标的基础。
5.1 命名的基本原则
- 明确性原则:命名应准确反映对象的功能或含义,避免模糊词汇
- 正例:
calculateTotalPrice()
、isEligibleForDiscount()
- 反例:
doSomething()
、processData()
- 一致性原则:同一概念在系统中应使用同一名称
- 正例:统一使用"userId"而非混用"userID"、"user_Id"
- 反例:同时存在
getUser()
、fetchUser()
、queryUser()
表达相同含义
- 简洁性原则:在明确性基础上保持简洁,避免不必要的冗余
- 正例:
OrderDAO.findById()
而非OrderDAO.findOrderById()
- 反例:
theUserWhoLoggedInAtTheBeginningOfTheSession
- 可读性原则:使用完整英文单词,避免不规范缩写
- 正例:
message
、configuration
- 反例:
msg
(除非团队达成共识)、conf
- 专业性原则:使用领域术语和技术术语
- 正例:
withdraw()
(银行业务)、commit()
(数据库操作) - 反例:用
removeMoney()
代替withdraw()
5.2 类名命名规范
类名应采用名词或名词短语,首字母大写,遵循驼峰命名法。不同类型的类有特定的命名规则:
- 业务类命名
- Service接口:
XxxService
(如OrderService
) - Service实现类:
XxxServiceImpl
(如OrderServiceImpl
) - DAO接口:
XxxDAO
或XxxMapper
(如UserDAO
) - 控制器:
XxxController
(如OrderController
)
- 领域模型类命名
- DO类:
XxxDO
或XxxEntity
(如UserDO
) - DTO类:
XxxDTO
(如OrderQueryDTO
) - VO类:
XxxVO
(如UserProfileVO
)
- 工具类与组件命名
- 工具类:
XxxUtils
(如DateUtils
) - 管理器:
XxxManager
(如CacheManager
) - 工厂类:
XxxFactory
(如HttpClientFactory
) - 监听器:
XxxListener
(如OrderStatusListener
)
- 设计模式相关类命名
- 建造者模式:
XxxBuilder
(如QueryBuilder
) - 适配器模式:
XxxAdapter
(如LogAdapter
) - 装饰器模式:
XxxDecorator
(如ResponseDecorator
) - 代理模式:
XxxProxy
(如RpcProxy
)
5.3 方法名命名规范
方法名应采用动词或动词短语,首字母小写,遵循驼峰命名法。不同功能的方法有特定的命名规则:
- 查询方法
- 获取单个对象:
getXxx()
(如getUserById()
) - 获取多个对象:
listXxx()
(如listOrdersByUserId()
) - 统计查询:
countXxx()
(如countValidOrders()
) - 条件查询:
findXxx()
(如findActiveUsers()
)
- 修改方法
- 插入数据:
saveXxx()
或insertXxx()
(如saveOrder()
) - 更新数据:
updateXxx()
(如updateUserPassword()
) - 删除数据:
deleteXxx()
或removeXxx()
(如deleteExpiredOrders()
)
- 布尔值方法
- 判断状态:
isXxx()
(如isValid()
、isExpired()
) - 判断能力:
canXxx()
(如canSubmit()
、canCancel()
) - 判断存在:
hasXxx()
(如hasPermission()
、hasStock()
)
- 业务方法
- 业务操作:
doXxx()
(如doCheckout()
、doRefund()
) - 计算操作:
calculateXxx()
(如calculateTotalPrice()
) - 转换操作:
convertXxx()
或toXxx()
(如toOrderDTO()
)
- 工厂方法
- 实例创建:
createXxx()
(如createHttpClient()
) - 实例获取:
getInstance()
(如getInstance(options)
) - 类型转换:
fromXxx()
(如fromString()
、fromJson()
)
方法命名应避免"名不副实",例如一个名为
isExpired()
的方法不应包含删除过期数据的逻辑:5.4 变量与常量命名规范
- 变量命名
- 采用名词或名词短语,首字母小写,驼峰命名
- 布尔变量建议以
is
、can
、has
等前缀开头 - 集合变量建议使用复数形式或添加
List
/Map
后缀
- 常量命名
- 全部大写,单词间用下划线分隔
- 应包含完整含义,避免过度缩写
- 按业务模块分组定义
- 枚举命名
- 枚举类名:
XxxEnum
(如OrderStatusEnum
) - 枚举值:全部大写,下划线分隔
6 项目结构与代码组织
合理的项目结构是工程规范的物理载体,它决定了代码的组织方式和依赖关系,直接影响开发效率和维护成本。一个清晰的项目结构应当使开发者能够快速定位到所需代码。
6.1 顶层目录结构
标准的Java项目采用模块化结构,顶层目录遵循以下规范:
各模块职责明确:
- application:仅包含启动类和核心配置
- api:定义对外接口和数据模型
- core:实现核心业务逻辑
- infrastructure:提供通用技术组件
- test:包含所有测试代码
6.2 业务模块内部结构
每个业务模块内部采用分层目录结构,典型的模块结构如下:
这种结构的优势在于:
- 按功能分层,职责清晰
- 相关类集中存放,便于查找
- 符合多数开发者的认知习惯
6.3 通用组件组织
项目中的通用组件应集中管理,避免分散在各业务模块中。典型的通用组件结构如下:
通用组件的设计原则:
- 无状态:工具类应设计为无状态,仅包含静态方法
- 单一职责:每个工具类专注于特定功能领域
- 可测试:工具类应易于单元测试
- 避免过度设计:不要为了"通用"而实现极少使用的功能
6.4 分层架构反模式地图
反模式 | 现象描述 | 危害 | 解决方案 |
Service膨胀 | 一个Service类3000行,包含所有业务逻辑 | 改一行需通读全文,风险高 | 按业务拆分Service,引入领域对象承载逻辑 |
跨层调用 | Controller直接调用DAO,跳过Service | 事务失控,字段修改影响多层 | 用ArchUnit检测跨层调用,添加Service代理 |
模型混用 | DO直接返回给前端,DTO在DAO层使用 | 数据库字段变更影响前端,安全隐患 | 严格按层使用模型,用MapStruct强制转换 |
依赖倒置错误 | 领域层依赖基础设施层(如Domain用Redis) | 更换技术框架需修改领域层 | 领域层定义接口,基础设施层实现接口 |
事务滥用 | 所有方法加@Transactional,包括查询 | 事务开销大,并发性能差 | 仅对写操作加事务,查询方法不加 |
工具类侵入 | 领域层调用 DateUtils 等工具类 | 领域层依赖技术细节,难以测试 | 工具类逻辑封装到值对象(如 LocalDateWrapper ) |
7 分层架构的演进
随着业务复杂度增长,分层架构也在不断演进,主要经历了三个阶段:
- 单体架构
- 特征:所有模块打包为单一应用部署
- 优势:开发简单,部署方便
- 局限:代码耦合度高,扩展性差
- 适用场景:小型项目或初创阶段
- 分布式架构
- 特征:按业务模块拆分为独立服务,通过RPC/HTTP通信
- 优势:降低耦合度,可独立扩展
- 挑战:分布式事务、服务依赖管理
- 适用场景:中大型项目,业务模块边界清晰
- 微服务架构
- 特征:服务粒度更细,可独立部署与伸缩
- 优势:技术栈灵活,容错性强
- 挑战:服务治理复杂,运维成本高
- 适用场景:超大型项目,高并发场景
架构演进的核心驱动力是业务复杂度的增长,而非技术潮流。京东、阿里等大型电商平台的架构演进历程表明,合理的架构应当与业务规模相匹配,避免过度设计。
8 微服务拆分:从单体到分布式
微服务拆分是架构演进的必然,但90%的团队因“拍脑袋”拆分导致服务间依赖混乱。
8.1 微服务拆分三原则
原则1:业务边界优先(高内聚)
- 按“领域边界”拆分,而非技术分层(如“订单服务”包含订单相关的所有功能,而非“订单API服务”“订单DAO服务”);
- 参考DDD的限界上下文,上下文内业务紧密相关,上下文间松耦合。
美团外卖案例:
- 订单服务:包含下单、支付、取消等订单全生命周期;
- 库存服务:包含库存扣减、库存查询;
- 两者通过“订单创建→扣库存”的RPC调用协作。
原则2:数据隔离(低耦合)
- 每个微服务独占数据库(或独立schema),禁止跨库联表查询;
- 服务间数据交互通过API,而非直接访问数据库。
反例:
订单服务直接查询库存库的
stock
表,导致库存库改表后订单服务报错。原则3:接口契约稳定
- 定义清晰的API契约(如OpenAPI规范),包含参数、返回值、错误码;
- 接口变更遵循“向后兼容”原则(新增字段而非删除);
- 通过契约测试(如Spring Cloud Contract)确保调用方兼容。
8.2 微服务拆分公式
拆分公式 = 领域边界 × 团队结构 × 业务复杂度
- 领域边界:通过事件风暴(Event Storming)识别聚合和限界上下文;
- 团队结构:遵循“康威定律”(系统设计反映团队结构),一个团队负责一个或多个相关服务;
- 业务复杂度:复杂度低的领域可合并(如“评价服务”可并入“订单服务”),高的拆分为独立服务。
拆分步骤(以电商为例)
- 识别核心领域:订单、商品、用户、支付、库存;
- 梳理领域关系:订单依赖商品(查价格)、用户(查地址)、库存(扣减)、支付(回调);
- 确定服务粒度:
- 订单服务:核心,独立;
- 商品服务:核心,独立;
- 用户服务:核心,独立;
- 库存服务:依赖商品,可独立;
- 支付服务:对接第三方,独立;
- 评价服务:依赖订单,复杂度低,可暂并入订单服务。
8.3 微服务反模式与解决方案
反模式 | 现象 | 解决方案 |
按技术分层拆分 | 拆分为“订单API服务”“订单DAO服务” | 按业务功能拆分,一个服务包含完整的分层逻辑 |
服务粒度太细 | 10人团队维护20个服务,每个服务代码少 | 合并相关服务,避免“分布式单体” |
同步调用链太长 | 下单需调用5个服务,响应时间500ms+ | 引入事件驱动(如MQ),缩短同步链 |
共享数据库 | 多个服务访问同一数据库 | 按服务拆分数据库,通过API交互 |
无状态设计失败 | 服务依赖本地缓存,导致集群数据不一致 | 改用分布式缓存(如Redis),确保无状态 |
9 架构设计中的常见误区与最佳实践
即使理解了架构设计的理论,在实际项目中仍可能陷入各种误区。了解这些常见问题及其解决方案,能帮助开发者少走弯路。
9.1 常见架构误区
- 过度设计
- 症状:为尚未存在的需求提前设计复杂架构
- 危害:增加开发成本,降低开发效率
- 解决方案:遵循YAGNI原则(You Aren't Gonna Need It),仅设计当前必需的功能
- 技术驱动架构
- 症状:为使用某项新技术而设计架构,而非基于业务需求
- 危害:架构与业务脱节,增加维护难度
- 解决方案:始终以业务需求为导向,技术仅作为实现手段
- 忽视非功能性需求
- 症状:只关注功能实现,忽视性能、安全性、可扩展性等
- 危害:系统上线后出现性能瓶颈或安全漏洞
- 解决方案:在架构设计阶段明确非功能性需求指标,并进行针对性设计
- 架构师独断专行
- 症状:架构设计由架构师单独决定,不征求团队意见
- 危害:架构难以落地,团队执行力低
- 解决方案:采用协作式设计,鼓励团队成员参与架构讨论
- 架构一成不变
- 症状:架构设计完成后不再调整,不随业务发展演进
- 危害:架构逐渐不适应业务需求,最终被迫重构
- 解决方案:定期回顾架构设计,根据业务变化进行调整
9.2 最佳实践案例
- 分层架构的正确依赖 控制器层不应直接调用DAO层,而应通过Service层间接访问,即使业务逻辑简单:
- Service层解耦 当多个Service之间存在依赖关系时,应通过事件驱动或中介者模式解耦,避免直接依赖:
- 领域模型转换 不同层之间的模型转换应通过专门的转换器完成,避免在业务代码中散落转换逻辑:
- 事务管理 事务应在Service层声明,且明确指定回滚异常类型:
10 主流框架中的规范实践
主流开源框架的代码规范和架构设计为我们提供了良好的参考范例。学习这些框架的设计思想,能帮助我们更好地理解和应用工程规范。
10.1 Spring框架的分层实践
Spring框架的分层设计清晰,各模块职责明确:
- Spring MVC:处理Web层请求,对应Controller层
- Spring Core:提供依赖注入等核心功能
- Spring Transaction:处理事务管理,对应Service层事务控制
- Spring Data:简化数据访问,对应DAO层
Spring的命名规范具有很强的参考价值:
- 接口命名:
XxxService
、XxxRepository
- 实现类命名:
XxxServiceImpl
、XxxTemplate
- 注解命名:
@Service
、@Repository
、@Transactional
10.2 MyBatis-Plus的DAO层规范
MyBatis-Plus的ServiceImpl定义了一套标准的数据操作方法,遵循一致的命名规范(tip: 我十分反感IService的存在):
这种规范的命名使开发者能快速理解方法功能,降低学习成本。
总结
规范是地基,架构是楼房写代码就像盖楼:
- 规范是地基,决定了代码的稳定性(地基不稳,楼越高越危险);
- 架构是设计图,决定了系统的扩展性(设计不合理,加层就塌);
- 领域模型是承重墙,承载业务逻辑(设计不好,改一点就裂)。
从规范到架构的演进,本质是“从约束到自由”的过程:初期靠规范约束保证一致性,后期靠架构设计支撑业务增长。阿里、美团等团队的实践证明,这套体系能让新人3天上手,线上故障下降,重构从“大手术”变为“日常小优化”。
优秀的工程规范不是一朝一夕形成的,而是通过团队成员的共同努力和持续改进逐步完善的。只有将规范内化为团队的开发习惯,才能真正发挥其价值,构建出高质量、可维护的系统。
最后,写给读文章的你:
“代码是写给人看的,只是恰好机器能运行。”—— Honesty
当你写出的代码既符合规范,又体现架构思想时,维护它的人(包括未来的你)会真心感谢现在的你。
参考资料
- 《阿里巴巴Java开发手册(嵩山版)》
- 《Clean Code》- Robert C. Martin
- 《Clean Architecture》- Robert C. Martin
- 《大型网站技术架构:核心原理与案例分析》- 李智慧
- 《架构即未来》- Marty Cagan
- 《从0到1构建电商平台》- 京东技术白皮书
- Spring 官方文档
- Mybatis 官方稳定
- MyBatis-Plus 官方文档
- 阿里巴巴开源项目代码规范
- 作者:Honesty
- 链接:https://blog.hehouhui.cn/archives/23f0c7d0-9e17-80e5-bba8-e086042821b4
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章