MVC架构
- Controller/Service 主要用来做业务逻辑实现,Controller对应接口需求,Service对应业务逻辑
- Mapper/Dao 用于操作不同数据库的表数据,例如增删改查,sql执行等,常见的ORM框架会对这一部分进行深度封装,适配各种数据库,提供基础方法等
- Entity/PO 用于映射数据表对象化,通过javax等Table,Id等注解,形成表结构一对一
依赖耦合
在service层业务逻辑的方法中,引入并直接使用了orm提供的dao/mapper等,实际在业务逻辑和数据库操作中形成了一层技术耦合。
public class AuthInfoService { @Autowired protected OpenAuthInfoDao openAuthInfoDao; public Boolean checkCompanyAuthInfo(TokenParam tokenParam) { Map<Property<OpenAuthInfoPO, ?>, String> paramMap = new HashMap<>(2); if (Objects.nonNull(tokenParam)) { // 业务逻辑 - 校验Token有效性 // 数据库操作 - 拼装查询条件 paramMap.put(OpenAuthInfoPO::getAppId, tokenParam.getAppId()); paramMap.put(OpenAuthInfoPO::getAppKey, tokenParam.getAppKey()); LambdaQueryWrapper<OpenAuthInfoPO> authWrapper = new LambdaQueryWrapper<OpenAuthInfo>().select(OpenAuthInfoPO::getId).allEq(paramMap); // 数据库操作 - 直接调用Dao方法 OpenAuthInfoPO openAuthInfo = openAuthInfoDao.selectOne(authWrapper); return Objects.nonNull(openAuthInfo); } else { // 业务逻辑 - Token无效 return false; } } }
PO跨界
基于数据编程的时候,将包含基础原子属性的po对象,使用在mvc各层,常见的有:
- 在controller层,拼装多个po对象,返回目标结果值;
- 在没有性能压力等需求的前提下,设计宽表+15个以上属性的po对象,用于多场景响应,存在多余字段返回,数据泄漏等;
- 接口增加字段,po反向增加属性,导致业务属性和表字段混合,影响可读性及稳定性;
@TableName("open_auth_info") public class OpenAuthInfoPO { // 数据库字段 @TableId("ID") private Long id; /** 公司companyID */ @TableField("APP_ID") private String appId; /** 公司名称 */ @TableField("APP_NAME") private String appName; /** 生成唯一key */ @TableField("APP_KEY") private String appKey; /** 生成签名key */ @TableField("SIGN_KEY") private String signKey; /** 生成URL */ @TableField("APP_URL") private String appUrl; /** 1:启用,2:禁用 */ @TableField("APP_STATUS") private Boolean appStatus; @TableField("APP_REMARK") private String appRemark; // 接口字段 /** 接口查询参数反显 */ private LocalDateTime createDateStart; /** 接口查询参数反显 */ private LocalDateTime createDateEnd; }
多源扩展
组合数据源或者数据来源不稳定时,很多orm不支持mapper内扩展,会下意识的将切源/取数等判断逻辑,放到service层代码中,增加了业务逻辑的实现复杂度
public Boolean checkCompanyAuthInfo(TokenParam tokenParam) { Map<Property<OpenAuthInfoPO, ?>, String> paramMap = new HashMap<>(2); if (Objects.nonNull(tokenParam)) { // 业务逻辑 - 校验Token有效性 // 多种数据源切换 if(OAuth.isEnable){ // 如果使用OAuth,远程调用OAuth Server获取数据 return Objects.nonNull(openAuthInfo); }else{ // 否则使用本地数据库 // 数据库操作 - 拼装查询条件 paramMap.put(OpenAuthInfoPO::getAppId, tokenParam.getAppId()); paramMap.put(OpenAuthInfoPO::getAppKey, tokenParam.getAppKey()); LambdaQueryWrapper<OpenAuthInfoPO> authWrapper = new LambdaQueryWrapper<OpenAuthInfo>().select(OpenAuthInfoPO::getId).allEq(paramMap); // 数据库操作 - 直接调用Dao方法 OpenAuthInfoPO openAuthInfo = openAuthInfoDao.selectOne(authWrapper); return Objects.nonNull(openAuthInfo); } } else { // 业务逻辑 - Token无效 return false; } }
测试难度
数据库升级,orm升级,国产化替代等,经常会导致方法/关键字/驱动等变化,导致mapper层调用方法等变化,因为业务逻辑的直接使用,导致回归测试范围变大
// 自己悟吧
引入仓储模式
技术本质
- 多了一层接口和实现
好处
留出了足够的空间,可以解决上述问题
- 业务逻辑和数据逻辑结耦,并反转依赖,仓储层实现依赖业务逻辑需求
- 技术脱离,实现层基于不同orm做纯技术实现,保证入参出参,不影响现有业务逻辑代码
- 测试MOCK,回归范围收窄
- 表聚合,表切割灵活
实现
// 自己动手吧
本文由 Ivan Dong 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jun 21, 2023 at 07:30 am