• DDD防腐层设计


    DDD防腐层设计 

    本文主旨

    • 防腐层核心思想。

    • 防腐层设计思路。

    • 门面和适配器实现防腐层。

    防腐层(Anti-Corruption Layer)思想:通过引入一个间接的层,就可以有效隔离限界上下文之间的耦合。防腐层往往属于下游限界上下文, 用以隔绝上游限界上下文可能发生的变化。

    即使上游发生了变化,影响的也仅仅是防腐层中的单一变化点,只要防腐层的接口不变,下游限界上下文的其他实现就不会受到影响。

    缺点是代码会重复,但解耦彻底。

    防腐层设计:比如用户订单微服务本地增加一个订单支付Service的Feign接口,这样用户订单Service就像本地调用一样调用支付Service,再通过这个feign接口实现远程调用,这样的设计叫做防腐层设计。

    防腐层实现

    防腐层用于隔离变化,代码落地方面可结合门面模式 + 适配器模式来实现。

     

    门面模式可简单理解为将多个接口进行封装,对调用层提供更精简的调用。

     

    适配器模式可简单理解为将外部系统提供的不兼容接口,转换为内部合适的接口。

    门面模式(外观模式) Facade Pattern

    隐藏系统的复杂性,并向客户端提供一个可以访问系统的接口。

    优点:

    1. 减少系统相互依赖;

    2. 提高灵活性;

    3. 提高了安全性。

    缺点:

    1. 不符合开闭原则。

    应用场景:

    1. Java的三层开发模式;

    2. Tomcat RequestFacade类就使用了外观模式。RequestFacade是对Request类封装,屏蔽内部属性和方法,避免暴露。

    举例:定义了3个接口,客户端正常调用实现的话,需要依赖三个实现类,调用其方法。用外观模式后,定义外观类Facade,其内部实例化了三个实现类的对象。客户端直接调用Facade类来完成调用即可。

    适配器模式 Adapter Pattern

    主要是为了在不改变原有接口的基础上,适配新的接口。使原本接口不兼容的类可以一起工作。

    适配器种类:

    • 类适配器:需要继承被适配器类实现目标接口。

    • 对象适配器:不继承,new一个对象实例。

    • 接口适配器:有些适配方法不需要全部实现,可创建抽象类实现接口中全部方法。

    优点:

    1. 可以让任何两个没有关联的类一起运行;

    2. 提高了类的复用;

    3. 增加了类的透明度;

    4. 灵活性好。

    缺点:

    1. 过多使用适配器,会让系统内部变的复杂。比如明明调用的A接口,但内部被适配成了B接口的实现。

    应用场景:springmvc中DispatcherServlet类的doDispatch方法用到了适配器模式。通过request获取handler,通过handler获取适配器类。

    防腐层简单案例

    在某个业务场景中,会有很多的命令触发相关事件,这些事件会被作为任务去执行。执行后会调用通知service来完成通知(短信通知、企业微信通知、H5端通知、公众号通知等等)。

    项目初期所有的业务逻辑都在一个服务内,此时TaskService直接引用NoticeService即可完成通知服务的调用。

    随着需求的不断迭代,后期项目越来越复杂,单应用内包含的子域越来越多,每个子域也会有更多的职责。

    像通知服务不仅仅内部服务会调用,也会提供第三方服务调用。为了解耦合,此时把通知作为单独的服务拆分出去,把通知相关的业务逻辑限定在通知子域内。

    此时拆分有两种方式:

    1. 根据通知的业务属性独立为单独的服务,作为通用域存在。

    2. 不拆分服务,只拆分package,把通知相关的逻辑限制在通知的package内,假如后面需要独立服务部署,是可以更快的分离出去。

    拆分后为了不影响原来的TaskService调用逻辑,采用防腐层的思想,用门面模式封装调用第三方的规则,用适配器模式完成不同的消息通知方案。

    通知传输对象

    1. @Data
    2. public class NoticeDTO {
    3.     /**
    4.      * 姓名
    5.      */
    6.     private String name;
    7.     /**
    8.      * 手机号
    9.      */
    10.     private String mobile;
    11.     /**
    12.      * 消息内容
    13.      */
    14.     private String content;
    15.     /**
    16.      * 消息类型
    17.      */
    18.     private Integer type;
    19.     
    20.     ......
    21. }

    任务传输对象

    1. @Data
    2. public class TaskDTO {
    3.     /**
    4.      * 姓名
    5.      */
    6.     private String name;
    7.     /**
    8.      * 手机号
    9.      */
    10.     private String mobile;
    11.     /**
    12.      * 消息内容
    13.      */
    14.     private String msg;
    15.     /**
    16.      * 消息类型
    17.      */
    18.     private Integer type;
    19.     
    20.     .....
    21. }

    定义通知门面类完成DTO对象的转换,封装Http调用的代码。

    通知门面类

    1. public class NoticeFacade {
    2.     public Object weChatNotice(TaskDTO taskDTO) {
    3.         NoticeDTO noticeDTO = this.convert(taskDTO);
    4.         Object obj = this.send(noticeDTO);
    5.         //补全taskDTO属性
    6.         return taskDTO;
    7.     }
    8.     public Object send(NoticeDTO noticeDTO) {
    9.         //构建http请求体
    10.         HttpRequest request = HttpRequest.newBuilder()
    11.                 .uri(URI.create("https://www.baidu.com/"))
    12.                 .timeout(Duration.ofSeconds(20))
    13.                 .header("Content-Type""application/json")
    14.                 //.POST(HttpRequest.BodyPublishers.noBody())
    15.                 .GET()
    16.                 //.POST(HttpRequest.BodyPublishers.ofFile(Paths.get("file.json")))
    17.                 .build();
    18.         //构建http客户端
    19.         HttpClient client = HttpClient.newBuilder()
    20.                 .version(HttpClient.Version.HTTP_1_1)
    21.                 .followRedirects(HttpClient.Redirect.NORMAL)
    22.                 .connectTimeout(Duration.ofSeconds(20))
    23.                 //.proxy(ProxySelector.of(new InetSocketAddress("https://www.baidu.com"80)))
    24.                 //.authenticator(Authenticator.getDefault())
    25.                 .build();
    26.         //同步调用
    27.         HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
    28.         .....
    29.         Object obj = response.body();
    30.         return obj;
    31.     }
    32.     //传输实体转换
    33.     private NoticeDTO convert(TaskDTO taskDTO) {
    34.         NoticeDTO noticeDTO = new NoticeDTO();
    35.         noticeDTO.setContent(taskDTO.getMsg());
    36.         ......
    37.         return noticeDTO;
    38.     }
    39. }

    通知适配器省略相关逻辑,为适应扩展需求可参考spring 源码中适配器用法。此处定义了微信通知的调用。

    通知对象适配器

    1. public class NoticeAdapter {
    2.     public NoticeFacade noticeFacade;
    3.     public TaskDTO toPush(TaskDTO taskDTO) {
    4.         Object obj = noticeFacade.weChatNotice(taskDTO);
    5.         ......
    6.         return taskDTO;
    7.     }
    8. }

    任务Service像以前一样调用通知方法,只不过引用的适配器对象,由适配器完成后面的实现。

    此时由NoticeAdapter + NoticeFacade 完成通知逻辑的防腐。后面可变的修改隔离在防腐层代码中。

    任务Service

    1. public class TaskService {
    2.     private NoticeAdapter noticeAdapter;
    3.     public void run(TaskDTO taskDTO) {
    4.         this.noticeAdapter.toPush(taskDTO);
    5.         ...... 处理后续逻辑
    6.     }
    7. }

    总结

    防腐层主要用于上下文映射(不同领域之间的协作)的解决方案。

    其目的还是为了隔离上游服务的可变性,降低影响范围,以更小的可能变更代码。

    可关注【阿飞技术】公众号

     

  • 相关阅读:
    canvas 轮询http接口让小车实时运动
    matlab图像的增强
    网络游戏协议:基于Protobuf的序列化与反序列化
    KingbaseESV8R6 snapshot too old的配置和测试
    原版畅销36万册!世界级网工打造TCP/IP圣经级教材,第5版终现身
    分享从零开始学习网络设备配置--任务4.1 IPv6地址的基本配置
    提高篇(三):交互设计与用户输入:打造更具互动性的Processing作品
    医疗信息管理系统(HIS)——>业务介绍
    JavaScript基本语法、函数
    Machine learning week 7(Andrew Ng)
  • 原文地址:https://blog.csdn.net/u011385940/article/details/126977014