通过抽象和封装,将重复出现的业务规则沉淀为通用能力,降低代码复杂度,提高系统的可维护性和扩展性。
这其实贯穿了很多架构设计:
- 自定义注解
- AOP 切面
- 统一异常处理
- 统一日志
- 参数校验框架
- 权限框架
- 自动化配置
我给你整理一篇偏架构思想的博客。
为什么要做业务抽象?从重复代码到可复用能力
在软件开发过程中,一个系统从简单到复杂,往往会经历这样的过程:
最开始:
一个接口
一个功能
几行代码解决
随着业务增长:
10个接口
50个接口
100个接口
很多相同的逻辑开始出现。
例如:
- 手机号格式校验
- 用户权限判断
- 登录状态检查
- 操作日志记录
- 参数合法性验证
如果每个地方都重复实现,项目会逐渐变得难以维护。
因此,在架构设计中,一个非常重要的思想就是:
把重复的业务规则抽离出来,形成统一能力。
一、什么是业务抽象?
业务抽象简单来说:
就是发现系统中重复出现的规则,将它提取出来。
例如:
用户注册:
手机号校验
密码校验
验证码校验
用户登录:
手机号校验
密码校验
验证码校验
修改手机号:
手机号校验
验证码校验
可以发现:
手机号校验这个规则,在多个业务中重复出现。
如果每个地方自己写:
if(phone.length()!=11){
}
那么代码会越来越散。
所以我们应该抽象:
手机号校验规则
↓
PhoneNumberValidator
↓
统一提供手机号校验能力
业务代码只需要使用即可。
二、为什么不能一直复制代码?
很多项目初期都会这样:
注册接口:
// 手机号校验
if(phone == null){
}
登录接口:
// 手机号校验
if(phone == null){
}
修改资料:
// 手机号校验
if(phone == null){
}
刚开始没有问题。
但是随着项目发展,会产生几个问题。
1. 修改成本高
假设手机号规则变化:
以前:
11位数字
后来:
支持更多号码规则
没有抽象之前:
需要修改:
注册接口
登录接口
修改资料接口
订单接口
...
容易遗漏。
抽象之后:
只需要修改:
PhoneNumberValidator
所有地方自动生效。
2. 容易产生规则不一致
例如:
注册:
手机号必须11位
登录:
手机号只判断非空
同一个业务规则:
不同地方出现不同实现。
这会产生隐藏 Bug。
3. 业务代码越来越臃肿
理想业务代码:
createUser();
表达:
创建用户。
但是现实:
// 校验手机号
// 校验参数
// 判断权限
// 记录日志
// 处理异常
// 核心业务
createUser();
大量非核心代码混在一起。
三、架构设计中的核心思想:关注点分离
优秀系统都会遵循:
让业务代码关注业务,让通用能力负责通用问题。
例如:
用户注册:
业务关注:
创建用户
保存数据
发送欢迎消息
而:
参数校验:
交给:
Validation模块
日志:
交给:
Log模块
权限:
交给:
Permission模块
最终形成:
业务代码
|
--------------------------------
参数校验
日志管理
权限控制
异常处理
缓存管理
--------------------------------
基础能力层
四、自定义注解就是一种业务抽象
以手机号校验为例。
没有注解:
PhoneUtils.check(phone);
业务代码需要主动调用。
有注解:
@PhoneNumber
private String phone;
代码表达:
这个字段需要手机号校验。
具体如何校验:
交给框架处理。
这就是:
声明式编程。
五、类似思想在 Spring 中随处可见
Spring 本身大量使用这种思想。
事务管理
以前:
开启事务
执行SQL
提交事务
现在:
@Transactional
一句注解完成。
权限控制
以前:
if(user.hasPermission){
}
现在:
@PreAuthorize()
统一处理。
日志记录
以前:
每个方法:
logger.info()
现在:
@OperationLog
自动记录。
这些本质都是:
把重复逻辑从业务代码中剥离。
六、架构中的“能力沉淀”
一个成熟项目通常会经历:
第一阶段:功能开发
目标:
先跑起来。
代码可能:
业务代码
+
各种判断
+
各种工具
第二阶段:发现重复
开始出现:
很多地方都有相同逻辑
例如:
- 参数校验
- 权限判断
- 日志记录
第三阶段:能力抽象
形成:
common模块
validator
exception
log
security
第四阶段:平台化
最终:
业务开发人员只需要:
@PhoneNumber
@LoginRequired
@Permission
即可使用系统能力。
七、什么时候应该抽象?
并不是所有代码都应该马上抽象。
正确原则:
1. 出现重复时抽象
第一次出现:
可以直接写。
第二次出现:
观察。
第三次出现:
考虑抽象。
2. 业务规则稳定时抽象
如果规则一直变化:
不要过早设计。
3. 抽象应该降低复杂度
好的抽象:
复杂逻辑隐藏
简单接口暴露
坏的抽象:
增加更多复杂概念
八、总结
软件架构的核心目标之一:
降低变化带来的影响。
通过抽象和封装:
我们可以把:
- 重复代码
- 重复规则
- 通用流程
沉淀成:
- 注解
- 组件
- 框架
- 基础服务
最终让业务代码更加:
- 简洁
- 统一
- 易维护
- 易扩展
所以:
自定义注解只是技术表现,背后的架构思想是“业务能力抽象与复用”。
这也是为什么优秀的后端系统,会不断从业务代码中提炼公共能力,最终形成自己的技术体系。