DDD防腐层设计 

本文主旨

  • 防腐层核心思想。
  • 防腐层设计思路。
  • 门面和适配器实现防腐层。

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

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

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

java防腐层代码实现 防腐层设计模式_封装

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

防腐层实现

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

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

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

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

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

优点:

  1. 减少系统相互依赖;
  2. 提高灵活性;
  3. 提高了安全性。

缺点:

  1. 不符合开闭原则。

应用场景:

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

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

java防腐层代码实现 防腐层设计模式_java防腐层代码实现_02

适配器模式 Adapter Pattern

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

适配器种类:

  • 类适配器:需要继承被适配器类实现目标接口。
  • 对象适配器:不继承,new一个对象实例。
  • 接口适配器:有些适配方法不需要全部实现,可创建抽象类实现接口中全部方法。

优点:

  1. 可以让任何两个没有关联的类一起运行;
  2. 提高了类的复用;
  3. 增加了类的透明度;
  4. 灵活性好。

缺点:

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

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

java防腐层代码实现 防腐层设计模式_DDD_03

防腐层简单案例

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

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

java防腐层代码实现 防腐层设计模式_java防腐层代码实现_04

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

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

此时拆分有两种方式:

  1. 根据通知的业务属性独立为单独的服务,作为通用域存在。
  2. 不拆分服务,只拆分package,把通知相关的逻辑限制在通知的package内,假如后面需要独立服务部署,是可以更快的分离出去。

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

java防腐层代码实现 防腐层设计模式_DDD_05

通知传输对象

@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);
        ...... 处理后续逻辑
    }
}