小設(shè)計,大作用——談?wù)劮栏瘜拥拿钣?/h1>
前言
最近在看領(lǐng)域驅(qū)動模型DDD相關(guān)的東西,由于沒有實際的項目支撐,所以大都是停留在一些理論層面。但是我發(fā)現(xiàn)這里面的一些設(shè)計思想還是非常有實用價值的,可以直接應(yīng)用于你目前的項目中,今天我就來談?wù)劮栏瘜拥拿钣谩?/p>
一個簡單的例子
大家在做項目中是否有過這樣的經(jīng)歷,你的項目中需要調(diào)用一個外部服務(wù)接口,而這個外部服務(wù)接口需要在你的項目中的不同地方被多次使用,比如在公司項目中就出現(xiàn)調(diào)用下面獲取用戶詳細信息的外部的接口多達10幾次。
SessionUser getUserDetail(String username)SessionUser getUserDetail(String username)
一旦這個外部接口發(fā)生變化,那么是不是意味著我就要修改這幾十處的地方,簡直頭大。
那我們是不是可以對外部接口做一層適配封裝,隔離這種可能地、不可控的變化。因此在我們的manager層中添加了一個UserManager的類,如下所示:
@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public UserDTO getUserDetail(String username) {
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
UserDTO user = convertUser(sessionUser);
return user;
}
}@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public UserDTO getUserDetail(String username) {
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
UserDTO user = convertUser(sessionUser);
return user;
}
}
我們讓系統(tǒng)中的業(yè)務(wù)層從原來直接調(diào)用remoteUserApi.getUserDetail(String username)改為調(diào)用UserManager#getUserDetail(),這樣哪怕有一天外部接口的返回內(nèi)容、方法名發(fā)生變化,我們也只需要修改一下這一個地方,而無需修改上層調(diào)用的十幾處地方。
另外,我們還可以再這一層加入更多的功能,比如參數(shù)校驗,日志打印等等,如下代碼所示:
@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public List<UserDTO> getUserDetail(String username) {
// 參數(shù)校驗
if(StrUtils.isBlank(username)) {
throw new UserException("用戶名不能為空");
}
long t1 = System.currentTimeMillis();
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
long t2 = System.currentTimeMillis();
// 打印日志,方便甩鍋
if(t2 - t1 > 3000L) {
log.warn("調(diào)用外部接口耗時過長,cost:[{}]ms", t2 - t1);
}
UserDTO user = convertUser(sessionUser);
return user;
}
}@Component
public class UserManager() {
@Autowired
private UserApi remoteUserApi;
public List<UserDTO> getUserDetail(String username) {
// 參數(shù)校驗
if(StrUtils.isBlank(username)) {
throw new UserException("用戶名不能為空");
}
long t1 = System.currentTimeMillis();
SessionUser sessionUser = remoteUserApi.getUserDetail(username);
long t2 = System.currentTimeMillis();
// 打印日志,方便甩鍋
if(t2 - t1 > 3000L) {
log.warn("調(diào)用外部接口耗時過長,cost:[{}]ms", t2 - t1);
}
UserDTO user = convertUser(sessionUser);
return user;
}
}
我們可以加上額外的參數(shù)驗證,打印調(diào)用外部接口的耗時,有理由“甩鍋”。
防腐層介紹
通過上面一個簡單的例子,你是不是對防腐層有了一個初步的認識。通俗的說,我們認為外部系統(tǒng)、接口中間件等都是腐爛的,不可控的,我們需要添加一層去做隔離和防腐,被叫做防腐層。
大多數(shù)應(yīng)用程序依賴于其他系統(tǒng)的某些數(shù)據(jù)或功能。例如,舊版應(yīng)用程序遷移到新式系統(tǒng)時,可能仍需要現(xiàn)有的舊的資源。新功能必須能夠調(diào)用舊系統(tǒng)。逐步遷移尤其如此,隨著時間推移,較大型應(yīng)用程序的不同功能遷移到新式系統(tǒng)中。
這些舊系統(tǒng)通常會出現(xiàn)質(zhì)量問題,如復(fù)雜的數(shù)據(jù)架構(gòu)或過時的 API。舊系統(tǒng)使用的功能和技術(shù)可能與新式系統(tǒng)中的功能和技術(shù)有很大差異。若要與舊系統(tǒng)進行互操作,新應(yīng)用程序可能需要支持過時的基礎(chǔ)結(jié)構(gòu)、協(xié)議、數(shù)據(jù)模型、API、或其他不會引入新式應(yīng)用程序的功能。不僅僅是舊系統(tǒng),不受開發(fā)團隊控制的任何外部系統(tǒng)(第三方系統(tǒng))都可能出現(xiàn)類似的問題,因此引入防腐層去做隔離解決。
圖片
如上圖所示,子系統(tǒng) A 通過防腐層調(diào)用子系統(tǒng) B。子系統(tǒng) A 與防腐層之間的通信始終使用子系統(tǒng) A 的數(shù)據(jù)模型和體系結(jié)構(gòu)。防腐層向子系統(tǒng) B 發(fā)出的調(diào)用符合該B子系統(tǒng)的數(shù)據(jù)模型或方法。防腐層包含在兩個系統(tǒng)之間轉(zhuǎn)換所必需的所有邏輯。該層可作為應(yīng)用程序內(nèi)的組件或作為獨立服務(wù)實現(xiàn)。
總結(jié)
說了那么多,這是不是和設(shè)計模式中的適配器模式很像,實際上防腐層也叫適配層。當然寫防腐層也是有代價的。最大的代價就是有「額外的開發(fā)成本」。所以如果你的上下游比較少,且比較穩(wěn)定,其實是可以不用防腐層的。但是在大型團隊,付出這些額外的開發(fā)成本是有價值的,因為大型團隊的上下游關(guān)系非常復(fù)雜,他們可能不是在一個團隊,也有可能經(jīng)常進行迭代升級,通過我自己的經(jīng)驗來看,接口變化是經(jīng)常會發(fā)生的。