DDD 防腐層設計
本文主旨
-
防腐層核心思想。
-
防腐層設計思路。
-
門面和適配器實現防腐層。
防腐層(Anti-Corruption Layer)思想:通過引入一個間接的層,就可以有效隔離限界上下文之間的耦合。防腐層往往屬於下游限界上下文, 用以隔絕上游限界上下文可能發生的變化。
即使上游發生了變化,影響的也僅僅是防腐層中的單一變化點,只要防腐層的接口不變,下游限界上下文的其他實現就不會受到影響。
缺點是代碼會重複,但解耦徹底。
**防腐層設計:**比如用戶訂單微服務本地增加一個訂單支付 Service 的 Feign 接口,這樣用戶訂單 Service 就像本地調用一樣調用支付 Service,再通過這個 feign 接口實現遠程調用,這樣的設計叫做防腐層設計。
防腐層實現
防腐層用於隔離變化,代碼落地方面可結合門面模式 + 適配器模式來實現。
門面模式可簡單理解爲將多個接口進行封裝,對調用層提供更精簡的調用。
適配器模式可簡單理解爲將外部系統提供的不兼容接口,轉換爲內部合適的接口。
門面模式(外觀模式) Facade Pattern
隱藏系統的複雜性,並向客戶端提供一個可以訪問系統的接口。
優點:
-
減少系統相互依賴;
-
提高靈活性;
-
提高了安全性。
缺點:
- 不符合開閉原則。
應用場景:
-
Java 的三層開發模式;
-
Tomcat RequestFacade 類就使用了外觀模式。RequestFacade 是對 Request 類封裝,屏蔽內部屬性和方法,避免暴露。
舉例:定義了 3 個接口,客戶端正常調用實現的話,需要依賴三個實現類,調用其方法。用外觀模式後,定義外觀類 Facade,其內部實例化了三個實現類的對象。客戶端直接調用 Facade 類來完成調用即可。
適配器模式 Adapter Pattern
主要是爲了在不改變原有接口的基礎上,適配新的接口。使原本接口不兼容的類可以一起工作。
適配器種類:
-
類適配器:需要繼承被適配器類實現目標接口。
-
對象適配器:不繼承,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);
...... 處理後續邏輯
}
}
總結
防腐層主要用於上下文映射(不同領域之間的協作)的解決方案。
其目的還是爲了隔離上游服務的可變性,降低影響範圍,以更小的可能變更代碼。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/yxUV7qH5Bd-vNeWsHsyDlw