Clean Code 系列之 DDD 分層參數轉換
先看一段簡單的代碼:
package com.zhuxingsheng.adapter
@PostMapping("/login")
public LoginResponse login(LoginRequest loginRequest) {
return loginService.login(loginReqeust);
}
從代碼中,可以明顯看出這是一段處理登陸請求的方法。在大多數項目中,這種代碼很常見。
它有什麼壞味道呢?
分層穿透了,LoginRequest 類本應該屬於入口層,結果穿透到了 service 層。
細細追究,需要明確的問題:
1、LoginRequest 到底屬於哪一層,是 resource 層,還是 service 層?
2、沒有達到 DDD 防腐層的意義,resource 是隔離外部與核心業務的,但卻變成了透傳。
歸屬哪一層
在《再議 DDD 分層》[1] 中,也討論過。
當前系統是以 REST 方式對外提供服務,如果後面需要以 RPC 方式對外提供服務,顯然 LoginRequest 可能不再適用。
從圖中可以看出 REST 方式是 Controller,而如果是 thrift 方式是 TService。controller 的 LoginRequest 參數,會在 TService 中失效。在實現層面,LoginRequest 本質上就是個 DTO,傳輸數據。而且不再像過去原始 servlet,傳輸數據時會有很多原生 API 類型,現在的框架都進化了,request 對象中只有業務屬性。
從這個角度講,request 對象是在 resource 層,並且是與各個實現框架綁定的。
另外,resource 層還需要處理 request 參數的檢驗與轉換。如果直接透傳到 service 層,不僅加重了 service 的職責,而且對於 service 層,我們更推薦使用 ADT 方式,讓代碼更有業務語義,不使用單純的技術基礎類型。
總結一下整體結構就是這樣:
DDD 防腐層
DDD 中有限界上下文,而且限界上下文之間需要高度自治、隔離變化,防腐層因運而生。
而這兒的 LoginRequest 就是兩個限界上下文的通信數據,而核心業務層是有對應的業務對象承接數據。
這樣有常見的兩個問題
1、代碼重複
《DDD 實戰指南》[2] 中提出,我們引入 CQRS 架構中的概念,業務層有對應 cmd 和 query 對象。
如 LoginRequest 到 LoginCmd 轉換,但兩個類的內容都一樣
package com.zhuxingsheng.adapter.pl
public class LoginCmd {
private String username;
private String password;
}
如果是這樣,那我們參數校驗邏輯是不是得寫到 service 裏面,不然校驗邏輯也要重複了。
當測試代碼時,controller 的測試與 service 的測試是一致的,use case 是相同的。
怎麼應對,還是上文提到的 ADT 方式,對於 service 層,不再提倡使用技術層面的基本類型,如 username 屬性包裝成 Username 類,而校驗邏輯可以封裝在 Username 中
package com.zhuxingsheng.adapter.pl
public class Username {
private String username;
public Username(String username) {
if (username == null) {
throw new ValidationException("username不能爲空");
} else if (isValid(username)) {
throw new ValidationException("username格式錯誤");
}
this.username = username;
}
}
這樣對於 service 層的方法業務語義更加顯現化。
public void login(Username username,Password password) {
}
對於 login 方法的測試,use case 數量相對基礎類型也變少了。
對於複雜對象的轉換,可以使用 mapstruct,既方便,性能也高效。
2、代碼複用
比如創建文章,編輯文章,兩者入參差不多,只是創建時沒有 id, 而編輯時有 id,從代碼複用角度,不想類的膨脹,DTO 只創建一個。會出現一個 dto 會有很多很多的屬性。
但從業務語義角度,兩個業務行爲就不應該共用同一個對象。需要有 CreateArticleCmd 和 EditArticleCmd
而對於 request dto 的數量,從友好 API 角度,應該要有兩個 DTO,但如果是複雜的查詢操作,query dto 屬性數量比 command dto 更多些。
總結
clean code,這篇主要闡述了分層架構中傳輸對象與業務對象職責不清問題。
應對策略:
大到一個微服務,小到一個變量,SRP 原則無處不在。
DTO 屬性要明確簡單,業務對象要語義清晰顯現化。
References
[1]
《再議 DDD 分層》: https://www.zhuxingsheng.com/blog/further-discussion-on-ddd-layering.html
[2]
《DDD 實戰指南》: https://www.zhuxingsheng.com/blog/ddd-tactical-practice-guide.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/wfFrf38CPRhDJnDf4we9qg