前言
最近在看领域驱动模型DDD相关的东西,由于没有实际的项目支撑,所以大都是停留在一些理论层面。但是我发现这里面的一些设计思想还是非常有实用价值的,可以直接应用于你目前的项目中,今天我就来谈谈防腐层的妙用。
一个简单的例子
大家在做项目中是否有过这样的经历,你的项目中需要调用一个外部服务接口,而这个外部服务接口需要在你的项目中的不同地方被多次使用,比如在公司项目中就出现调用下面获取用户详细信息的外部的接口多达10几次。
SessionUser getUserDetail(String username)SessionUser getUserDetail(String username)
一旦这个外部接口发生变化,那么是不是意味着我就要修改这几十处的地方,简直头大。
那我们是不是可以对外部接口做一层适配封装,隔离这种可能地、不可控的变化。因此在我们的manager层中添加了一个UserManager的类,如下所示:
@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public UserDTO getUserDetail(String username) {
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
UserDTO user = convertUser(sessionUser);
return user;
}
}@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public UserDTO getUserDetail(String username) {
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
UserDTO user = convertUser(sessionUser);
return user;
}
}
我们让系统中的业务层从原来直接调用remoteUserApi.getUserDetail(String username)改为调用UserManager#getUserDetail(),这样哪怕有一天外部接口的返回内容、方法名发生变化,我们也只需要修改一下这一个地方,而无需修改上层调用的十几处地方。
另外,我们还可以再这一层加入更多的功能,比如参数校验,日志打印等等,如下代码所示:
@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public List<UserDTO> getUserDetail(String username) {
// 参数校验
if(StrUtils.isBlank(username)) {
throw new UserException("用户名不能为空");
}
long t1 = System.currentTimeMillis();
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
long t2 = System.currentTimeMillis();
// 打印日志,方便甩锅
if(t2 - t1 > 3000L) {
log.warn("调用外部接口耗时过长,cost:[{}]ms", t2 - t1);
}
UserDTO user = convertUser(sessionUser);
return user;
}
}@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public List<UserDTO> getUserDetail(String username) {
// 参数校验
if(StrUtils.isBlank(username)) {
throw new UserException("用户名不能为空");
}
long t1 = System.currentTimeMillis();
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
long t2 = System.currentTimeMillis();
// 打印日志,方便甩锅
if(t2 - t1 > 3000L) {
log.warn("调用外部接口耗时过长,cost:[{}]ms", t2 - t1);
}
UserDTO user = convertUser(sessionUser);
return user;
}
}
我们可以加上额外的参数验证,打印调用外部接口的耗时,有理由“甩锅”。
防腐层介绍
通过上面一个简单的例子,你是不是对防腐层有了一个初步的认识。通俗的说,我们认为外部系统、接口中间件等都是腐烂的,不可控的,我们需要添加一层去做隔离和防腐,被叫做防腐层。
大多数应用程序依赖于其他系统的某些数据或功能。例如,旧版应用程序迁移到新式系统时,可能仍需要现有的旧的资源。新功能必须能够调用旧系统。逐步迁移尤其如此,随着时间推移,较大型应用程序的不同功能迁移到新式系统中。
这些旧系统通常会出现质量问题,如复杂的数据架构或过时的 API。旧系统使用的功能和技术可能与新式系统中的功能和技术有很大差异。若要与旧系统进行互操作,新应用程序可能需要支持过时的基础结构、协议、数据模型、API、或其他不会引入新式应用程序的功能。不仅仅是旧系统,不受开发团队控制的任何外部系统(第三方系统)都可能出现类似的问题,因此引入防腐层去做隔离解决。
图片
如上图所示,子系统 A 通过防腐层调用子系统 B。子系统 A 与防腐层之间的通信始终使用子系统 A 的数据模型和体系结构。防腐层向子系统 B 发出的调用符合该B子系统的数据模型或方法。防腐层包含在两个系统之间转换所必需的所有逻辑。该层可作为应用程序内的组件或作为独立服务实现。
总结
说了那么多,这是不是和设计模式中的适配器模式很像,实际上防腐层也叫适配层。当然写防腐层也是有代价的。最大的代价就是有「额外的开发成本」。所以如果你的上下游比较少,且比较稳定,其实是可以不用防腐层的。但是在大型团队,付出这些额外的开发成本是有价值的,因为大型团队的上下游关系非常复杂,他们可能不是在一个团队,也有可能经常进行迭代升级,通过我自己的经验来看,接口变化是经常会发生的。