【DDD】領域驅動設計之規約模式
工具人發現一個很有意思的現象,在傳統企業中,由於成果導向不同,所以採購師和工具人對產品功能的審美是截然相反的。
採購師以高顏值爲價值取向,內在腐朽不重要,無論是加拿大火腿腸還是墨西哥雞肉卷,只要能評獎,就是好產品。
而工具人則以內在美爲審美標準,沒有優秀的內部設計,再光鮮的麪皮,也難逃照妖鏡的聚光。
所以,工具人推薦使用 DDD 的緣由在於可以管理業務的複雜度,避免在業務規則愈發複雜的狀況下代碼以及架構發生腐化,變的難以維護,最終退化成下一個 WuYF。
大多數業務系統的複雜度體現在多個層面,比如:繁瑣的流程,繁複的校驗規則,數據的多樣性等。
DDD 對於不一樣層面的複雜度提供了不一樣的應對模式,今天我們聊瞭解下規約模式( Specification 模式)是如何解決業務規則的複雜性的。
常見 html
**常見的業務規則 **
作爲一個 CRUD 工具人,日常工作經常會遇到如下場景:
-
校驗業務對象的某些狀態是否合法,例如理財服務中,常常需要校驗理財產品是否處於申購或認購期,認購額度是否充足,風險等級是否匹配等。
-
從業務對象的集合中篩選出符合條件的結果集,例如行情服務中,從所有的股票標的中找出融資融券標的。
-
檢查一個新建立的業務對象是否符合某些業務條件,例如卡券系統中,給用戶派發一種優惠券,它對應的客戶與產品都應該是系統的合法用戶和在售產品。
以往我們常常會在 Domain service 中編寫一些簡單方法和校驗邏輯,但是由於它們被分散在各處,當業務越來越複雜、不同的場景這些規則需要多種不同的排列組合關係時,傳統的模式就會變得難以管理。
就像剛纔我們提到的理財業務,其校驗規則會比較複雜,例如理財購買,你可能需要驗證用戶賬號是否可用、理財產品的剩餘額度是否充足、產品是否處於認購期,與用戶的風險等級是否匹配等等…… 僅僅是通過一連串的 if 判斷,那就真的太不利於維護了,並且 if 嵌套的多了代碼難於理解,不好說明白具體意圖,而簡單短小的方法,若是任其分散在不同的 Domain Service 中,後續的開發過程當中就這些業務知識就很容易被丟失。因此需要將隱式業務規則轉換成顯示概念,這也是 DDD 的要求。
規約模式提煉隱式規則
而規約模式經常在 DDD 中使用,用來將業務規則(通常是隱式業務規則)封裝成獨立的邏輯單元,從而將隱式業務規則提煉爲顯示概念,並達到代碼複用的目的。
DDD 中認爲這些規則都是純粹的 “動詞”,所以需要單獨的創建模型,而這些模型都應該是簡單的值對象。
首先,定義規約接口:
public interface ISpecification<T> {
boolean isSatisfied(T entity);
}
以風險等級規約爲例:
public class FinanceRiskSpecification implements ISpecification<FinanceProduct> {
Customer customer;
public FinanceRiskSpecification(Customer customer) {
this.customer = customer;
}
@Override
public boolean isSatisfied(FinanceProduct financeProduct) {
return financeProduct.getRiskLevel().compareTo(this.customer.getRiskLevel()) > 0;
}
}
同樣,我們可以實現認購額度規約等:
public class FinanceQuotaSpecification implements ISpecification<FinanceProduct> {
double purchaseQuota;
public FinanceRiskSpecification(double purchaseQuota) {
this.purchaseQuota = purchaseQuota;
}
@Override
public boolean isSatisfied(FinanceProduct financeProduct) {
return financeProduct.getRestQuota() > purchaseQuota;
}
}
理財購買是一種組合規約的場景,多個規約需要同時滿足,所以我們可以對規約模式進行擴展:
public class AndSpecification<T> implements ISpecification<T> {
private ISpecification<T> left;
private ISpecification<T> right;
public AndSpecification(ISpecification<T> left, ISpecification<T> right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfied(T entity) {
return this.left.isSatisfied(entity)
&& this.right.isSatisfied(entity);
}
}
組合使用多個規約:
ISpecification<FinanceProduct> compositSpecification =
new AndSpecification<FinanceProduct>(new FinanceRiskSpecification<FinanceProduct>(XXX),
new FinanceQuotaSpecification<FinanceProduct>(1000.00));
規約模式過濾查詢
從業務對象的集合中篩選出符合條件的結果集,也是工具人日常工作中的常見場景。傳統模式中,當數據量很大的情況下,我們常常將邏輯下沉至 DAO,直接在 SQL 中完成。如查詢所有的融資融券股票:
// 以下代碼爲工具人 YY
select * from t_security
where t.margin_trade = '1' //融資融券標識
and security_type = 'EQUIT' //證券類型爲股票
and list_status = '1'; // 標的狀態爲上市
其實,這部分的邏輯原本應該是屬於領域層的,如今卻泄漏到了數據層,形成的後果就是維護的難度大大提高,不少業務系統到後期都是在和大段大段的 SQL 作鬥爭,而應該編寫邏輯的 Service 層,Domain 層都成了擺設,退化成了純粹的數據對象,又回到了傳統模式的老路。而 規約 模式能夠提供一種不錯的解決思路。我們可以爲每個規約增加一個 queryWrapper 的方法,返回一個類似查詢包裝對象(這裏和使用的 ORM 框架有點耦合, 工具人這裏舉例的是 baomidou)。
public interface ISpecification<T> {
boolean isSatisfied(T entity);
QueryWrapper<T> queryWrapper(QueryWrapper<T> tQueryWrapper);
}
構建查詢股票型基金產品的規約:
public class FinanceInvestStyleSpecification implements ISpecification<FinanceProduct> {
String investStyle;
QueryWrapper<FinanceProduct> tQueryWrapper;
public FinanceRiskSpecification(String investStyle,QueryWrapper<FinanceProduct> tQueryWrapper) {
this.investStyle = investStyle;
this.tQueryWrapper = tQueryWrapper;
}
....
@Override
public QueryWrapper<FinanceProduct> queryWrapper() {
return tQueryWrapper.eq("invest_style", investStyle);
}
}
在理財產品倉庫,我們則可以用 findBySpecifications,統一抽象該類查詢
public List<FinanceProduct> queryProductBySpec(ISpecification<FinanceProduct> spec)
最後,如果颱風天氣下,上班的工具人,請一定注意安全。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/jA9Os_XPLkLubrGF-C7V5A