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

主要是爲了在不改變原有接口的基礎上,適配新的接口。使原本接口不兼容的類可以一起工作。

適配器種類:

優點:

  1. 可以讓任何兩個沒有關聯的類一起運行;

  2. 提高了類的複用;

  3. 增加了類的透明度;

  4. 靈活性好。

缺點:

  1. 過多使用適配器,會讓系統內部變的複雜。比如明明調用的 A 接口,但內部被適配成了 B 接口的實現。

應用場景:springmvc 中 DispatcherServlet 類的 doDispatch 方法用到了適配器模式。通過 request 獲取 handler,通過 handler 獲取適配器類。

防腐層簡單案例

在某個業務場景中,會有很多的命令觸發相關事件,這些事件會被作爲任務去執行。執行後會調用通知 service 來完成通知(短信通知、企業微信通知、H5 端通知、公衆號通知等等)。

項目初期所有的業務邏輯都在一個服務內,此時 TaskService 直接引用 NoticeService 即可完成通知服務的調用。

隨着需求的不斷迭代,後期項目越來越複雜,單應用內包含的子域越來越多,每個子域也會有更多的職責。

像通知服務不僅僅內部服務會調用,也會提供第三方服務調用。爲了解耦合,此時把通知作爲單獨的服務拆分出去,把通知相關的業務邏輯限定在通知子域內。

此時拆分有兩種方式:

  1. 根據通知的業務屬性獨立爲單獨的服務,作爲通用域存在。

  2. 不拆分服務,只拆分 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);
        ...... 處理後續邏輯
    }
}

總結

防腐層主要用於上下文映射(不同領域之間的協作)的解決方案。

其目的還是爲了隔離上游服務的可變性,降低影響範圍,以更小的可能變更代碼。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/yxUV7qH5Bd-vNeWsHsyDlw