小設計,大作用——防腐層的妙用

2023-07-16 21:00:59

前言

最近在學習瞭解領域驅動模型DDD相關的內容,但是由於沒有實際的專案支撐,所以大都是停留在一些理論層面。我發現這裡面的一些設計思想還是非常有實用價值的,可以直接應用於你目前的專案中,今天我就來談談防腐層的妙用。

一個簡單的例子

大家在做專案中是否有過這樣的經歷,你的專案中需要呼叫一個外部服務介面,而這個外部服務介面需要在你的專案中的不同地方被多次使用,比如在公司專案中就出現呼叫下面獲取使用者詳細資訊的外部的介面多達10幾次。

SessionUser getUserDetail(String username)SessionUser getUserDetail(String username)

一旦這個外部介面發生變化,那麼是不是意味著我就要修改這幾十處的地方,簡直頭大。

那我們是不是可以對外部介面做一層適配封裝,隔離這種可能地、不可控的變化。因此在我們的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;
    }
}

我們讓系統中的業務層從原來直接呼叫remoteUserApi.getUserDetail(String username)改為呼叫UserManager#getUserDetail(),這樣哪怕有一天外部介面的返回內容、方法名發生變化,我們也只需要修改一下這一個地方,而無需修改上層呼叫的十幾處地方。

另外,我們還可以再這一層加入更多的功能,比如引數校驗,紀錄檔列印等等,如下程式碼所示:

@Component
public class UserManager() {

    @Autowired
    private UserApi remoteUserApi;

    public List<UserDTO> getUserDetail(String username) {
        // 引數校驗
        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("呼叫外部介面耗時過長,cost:[{}]ms", t2 - t1);
        }
        UserDTO user = convertUser(sessionUser);
        return user;
    }
}@Component
public class UserManager() {

    @Autowired
    private UserApi remoteUserApi;

    public List<UserDTO> getUserDetail(String username) {
        // 引數校驗
        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("呼叫外部介面耗時過長,cost:[{}]ms", t2 - t1);
        }
        UserDTO user = convertUser(sessionUser);
        return user;
    }
}

我們可以加上額外的引數驗證,列印呼叫外部介面的耗時,有理由「甩鍋」。

防腐層介紹

通過上面一個簡單的例子,你是不是對防腐層有了一個初步的認識。通俗的說,我們認為外部系統、介面

中介軟體等都是腐爛的,不可控的,我們需要新增一層去做隔離和防腐,被叫做防腐層。

大多數應用程式依賴於其他系統的某些資料或功能。 例如,舊版應用程式遷移到新式系統時,可能仍需要現有的舊的資源。 新功能必須能夠呼叫舊系統。 逐步遷移尤其如此,隨著時間推移,較大型應用程式的不同功能遷移到新式系統中。

這些舊系統通常會出現質量問題,如複雜的資料架構或過時的 API。 舊系統使用的功能和技術可能與新式系統中的功能和技術有很大差異。 若要與舊系統進行互操作,新應用程式可能需要支援過時的基礎結構、協定、資料模型、API、或其他不會引入新式應用程式的功能。不僅僅是舊系統,不受開發團隊控制的任何外部系統(第三方系統)都可能出現類似的問題,因此引入防腐層去做隔離解決。

如上圖所示,子系統 A 通過防腐層呼叫子系統 B。子系統 A 與防腐層之間的通訊始終使用子系統 A 的資料模型和體系結構。防腐層向子系統 B 發出的呼叫符合該B子系統的資料模型或方法。 防腐層包含在兩個系統之間轉換所必需的所有邏輯。 該層可作為應用程式內的元件或作為獨立服務實現。

總結

說了那麼多,這是不是和設計模式中的介面卡模式很像,實際上防腐層也叫適配層。當然寫防腐層也是有代價的。最大的代價就是有「額外的開發成本」。所以如果你的上下游比較少,且比較穩定,其實是可以不用防腐層的。但是在大型團隊,付出這些額外的開發成本是有價值的,因為大型團隊的上下游關係非常複雜,他們可能不是在一個團隊,也有可能經常進行迭代升級,通過我自己的經驗來看,介面變化是經常會發生的。

歡迎關注個人公眾號【JAVA旭陽】交流學習!