DDD防腐层设计
防腐层核心思想。
防腐层设计思路。
门面和适配器实现防腐层。
防腐层(Anti-Corruption Layer)思想:通过引入一个间接的层,就可以有效隔离限界上下文之间的耦合。防腐层往往属于下游限界上下文, 用以隔绝上游限界上下文可能发生的变化。
即使上游发生了变化,影响的也仅仅是防腐层中的单一变化点,只要防腐层的接口不变,下游限界上下文的其他实现就不会受到影响。
缺点是代码会重复,但解耦彻底。
防腐层设计:比如用户订单微服务本地增加一个订单支付Service的Feign接口,这样用户订单Service就像本地调用一样调用支付Service,再通过这个feign接口实现远程调用,这样的设计叫做防腐层设计。
防腐层用于隔离变化,代码落地方面可结合门面模式 + 适配器模式来实现。
门面模式可简单理解为将多个接口进行封装,对调用层提供更精简的调用。
适配器模式可简单理解为将外部系统提供的不兼容接口,转换为内部合适的接口。
隐藏系统的复杂性,并向客户端提供一个可以访问系统的接口。
优点:
减少系统相互依赖;
提高灵活性;
提高了安全性。
缺点:
不符合开闭原则。
应用场景:
Java的三层开发模式;
Tomcat RequestFacade类就使用了外观模式。RequestFacade是对Request类封装,屏蔽内部属性和方法,避免暴露。
举例:定义了3个接口,客户端正常调用实现的话,需要依赖三个实现类,调用其方法。用外观模式后,定义外观类Facade,其内部实例化了三个实现类的对象。客户端直接调用Facade类来完成调用即可。
主要是为了在不改变原有接口的基础上,适配新的接口。使原本接口不兼容的类可以一起工作。
适配器种类:
类适配器:需要继承被适配器类实现目标接口。
对象适配器:不继承,new一个对象实例。
接口适配器:有些适配方法不需要全部实现,可创建抽象类实现接口中全部方法。
优点:
可以让任何两个没有关联的类一起运行;
提高了类的复用;
增加了类的透明度;
灵活性好。
缺点:
过多使用适配器,会让系统内部变的复杂。比如明明调用的A接口,但内部被适配成了B接口的实现。
应用场景:springmvc中DispatcherServlet类的doDispatch方法用到了适配器模式。通过request获取handler,通过handler获取适配器类。
在某个业务场景中,会有很多的命令触发相关事件,这些事件会被作为任务去执行。执行后会调用通知service来完成通知(短信通知、企业微信通知、H5端通知、公众号通知等等)。
项目初期所有的业务逻辑都在一个服务内,此时TaskService直接引用NoticeService即可完成通知服务的调用。
随着需求的不断迭代,后期项目越来越复杂,单应用内包含的子域越来越多,每个子域也会有更多的职责。
像通知服务不仅仅内部服务会调用,也会提供第三方服务调用。为了解耦合,此时把通知作为单独的服务拆分出去,把通知相关的业务逻辑限定在通知子域内。
此时拆分有两种方式:
根据通知的业务属性独立为单独的服务,作为通用域存在。
不拆分服务,只拆分package,把通知相关的逻辑限制在通知的package内,假如后面需要独立服务部署,是可以更快的分离出去。
拆分后为了不影响原来的TaskService调用逻辑,采用防腐层的思想,用门面模式封装调用第三方的规则,用适配器模式完成不同的消息通知方案。
通知传输对象
@Data
public class NoticeDTO {
/**
* 姓名
*/
private String name;
/**
* 手机号
*/
private String mobile;
/**
* 消息内容
*/
private String content;
/**
* 消息类型
*/
private Integer type;
......
}
任务传输对象
@Data
public class TaskDTO {
/**
* 姓名
*/
private String name;
/**
* 手机号
*/
private String mobile;
/**
* 消息内容
*/
private String msg;
/**
* 消息类型
*/
private Integer type;
.....
}
定义通知门面类完成DTO对象的转换,封装Http调用的代码。
通知门面类
public class NoticeFacade {
public Object weChatNotice(TaskDTO taskDTO) {
NoticeDTO noticeDTO = this.convert(taskDTO);
Object obj = this.send(noticeDTO);
//补全taskDTO属性
return taskDTO;
}
public Object send(NoticeDTO noticeDTO) {
//构建http请求体
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.baidu.com/"))
.timeout(Duration.ofSeconds(20))
.header("Content-Type", "application/json")
//.POST(HttpRequest.BodyPublishers.noBody())
.GET()
//.POST(HttpRequest.BodyPublishers.ofFile(Paths.get("file.json")))
.build();
//构建http客户端
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
//.proxy(ProxySelector.of(new InetSocketAddress("https://www.baidu.com", 80)))
//.authenticator(Authenticator.getDefault())
.build();
//同步调用
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
.....
Object obj = response.body();
return obj;
}
//传输实体转换
private NoticeDTO convert(TaskDTO taskDTO) {
NoticeDTO noticeDTO = new NoticeDTO();
noticeDTO.setContent(taskDTO.getMsg());
......
return noticeDTO;
}
}
通知适配器省略相关逻辑,为适应扩展需求可参考spring 源码中适配器用法。此处定义了微信通知的调用。
通知对象适配器
public class NoticeAdapter {
public NoticeFacade noticeFacade;
public TaskDTO toPush(TaskDTO taskDTO) {
Object obj = noticeFacade.weChatNotice(taskDTO);
......
return taskDTO;
}
}
任务Service像以前一样调用通知方法,只不过引用的适配器对象,由适配器完成后面的实现。
此时由NoticeAdapter + NoticeFacade 完成通知逻辑的防腐。后面可变的修改隔离在防腐层代码中。
任务Service
public class TaskService {
private NoticeAdapter noticeAdapter;
public void run(TaskDTO taskDTO) {
this.noticeAdapter.toPush(taskDTO);
...... 处理后续逻辑
}
}
防腐层主要用于上下文映射(不同领域之间的协作)的解决方案。
其目的还是为了隔离上游服务的可变性,降低影响范围,以更小的可能变更代码。
可关注【阿飞技术】公众号