戲說領域驅動設計(十一)——糾偏


  今兒寫這個題目膽子有點大,不過還是得冒險整一篇(我怕您看完了罵我),一是出於經驗分享,另外則是為了后面我們講案例的時候做好鋪墊。好的代碼需要注意的事項其實挺多的,您真讓我一骨腦兒都列出來可能也差點意思,所以遵照我們常態化歪樓的習慣,我是想到哪寫到哪兒。  

  我沒事兒的時候就喜歡看別人寫的文章,也喜歡看書,收獲還是挺多的,不過歲數大了忘性也大,記不住。可有一個事情我記得倍兒清楚:咱們搞技術的尤其是后端開發都知道一類對象叫視圖模型(View Object簡稱VO),一說VO,大多數文章給的概念就是“后端傳向前端,用於承載需要在頁面上顯示的信息的實體或對象(反方向是前端傳向后端用於存儲的信息)”。概念雖然簡單,可是和我的想法就不一樣,誰叫咱這人個色呢,您聽我給您嘮嘮。

  我所認為的視圖相對來說要廣義一點:前面您所認為的自然是沒問題;還有一種(咱們以Java為例啊)我來舉例說明:訂單的服務需要調用客戶服務來查詢客戶信息“CustomInfo”,這里面的“CustomInfo”就是視圖模型。您肯定暴起反對:搞笑呢吧?“CustomInfo”是“DTO(數據傳輸對象)”。這種說法當然沒錯了,但DTO這個說法太廣義面且太模糊了。常用的數據實體也算是一種DTO,他承載了需要存儲和從數據庫查詢返回的數據;前端傳向后端的也是DTO,承載了用戶的輸入。所以您平常老是用DTO這個說法其實真的不夠准確。那為什么說服務間相互傳的數據是VO呢?比如您去相親,對方給您的印象(注意:印象不僅是你主動獲取的,還包括對方傳給你的,就跟兩個函數一樣你調用我我調用你)比如長相、談吐等這些是對方想讓您知道的(別反對,女人畫起妝來你就不知道她到底長什么樣)關於其自身屬性的部分信息,當然還有一部分是您從對方身上獲得的信息,所謂的“印象”是兩種信息的集成,其實就是信息視圖。所以您調用下游服務時對方返回給你的就是這個下游服務的視圖也就是下游服務想要展示給你的內容。而且,不僅僅是服務間有視圖,包與包之前也只能通過視圖了解彼此。

  上一段我提到的“服務間和包間只能通過視圖了解彼此”,引申的含義是說:服務間和包間只能通過視圖傳遞信息,這種限制不論是3層還是4層構架都適用。DTO這種稱呼相對模糊,一般我在開發的時候也不會這么叫。實際上,您是喜歡叫VO還是DTO也不耽誤什么事兒,可我們在這里給出了一個重要的約束也就是包或系統間的數據訪問限制。這東西特別容易出亂子,尤其是想把一個服務做二次拆分的時候,如果當前系統無訪問約束那拆的風險就會相當的高。

  除了視圖模型外,后端服務開發還會用到另外兩類:數據模型和領域模型,如下圖所示。您別看統共就三種,想用好了沒那么容易。至少在我經歷的不少項目中很多人都是亂用的,要不是沒有訪問限制要不就是模型冗余。

重點!

服務間和包間相互調用時,傳入和傳出的信息(簡單字段除外)只能通過視圖模型進行承載,不得將數據模型和領域模型作為傳輸信息的載體,包括在消息中也不可以內嵌領域與數據模型,以避免內部信息泄露。

  對於模型的濫用,讓我來厚着臉皮做一下糾偏,分別說一個每個對象的作用和主要的使用限制。看完了后您會覺得:“這也太誇張了,寫個代碼有那么麻煩嗎?”,答:有!好東西一般不會是多快好省出來的。

1、數據模型

  定義:描述數據庫設計並承載數據在持久層到應用層間之間的傳輸。限制:1)每一張表一個實體;2)級聯查詢的結果一般是多個表的集成,也需要建立對應的實體;3)不可以傳到包、服務之外;4)不要包含任何業務邏輯;5)DAO的輸入輸出只能是簡單字段或數據模型;6)僅能使用基本類型如Integer、String等。有一個小技巧:如果數據模型和視圖模型的字段一樣,賦值時可以使用一些工具如“BeanUtils”實現兩個對象間的字段復制。

2、視圖模型

  定義:用於系統間、模塊間、包間傳送和展示數據的載體,具體的解釋可參考上面內容。限制:1)僅包含最少的用於傳輸的信息,不要使用一個對象包含所有的字段即萬能對象;2)不可以從數據模型繼承(這個問題尤其普遍),可使用工具實現與數據模型的互轉;3)是包、服務間傳輸信息的唯一載體;4)不得包含業務邏輯。下面給出了一個“數據字典”視圖模型的示例,請參考。

@ApiModel(value = "數據字典信息")
public class DictionaryVO extends VOBase {
    private static final int MAX_VALUE_LENGTH = 32;

    @ApiModelProperty(value = "字典值,長度:32", required = true)
    private String value;
    

    @Override
    public ParameterValidationResult validate() {
        if (this.classId == null) {
            return ParameterValidationResult.failed(OperationMessages.INVALID_CLASS_ID);
        }
        if (StringUtils.isEmpty(this.value) || this.value.length() > MAX_VALUE_LENGTH) {
            return ParameterValidationResult.failed(String.format(OperationMessages.INVALID_VALUE_LENGTH, MAX_VALUE_LENGTH));
        }        
        return super.validate();
    }

    public static DictionaryVO of(DictionaryDataEntity entity) {
        if (entity == null) {
            return null;
        }
        DictionaryVO vo = new DictionaryVO();
        BeanUtils.copyProperties(entity, vo);
        return vo;
    }
}

  上述代碼中,視圖模型繼承於“VOBase”自定義類,此類包含了“validate”方法用於對視圖模型中的信息進行驗證。切記:前端、其它服務和包傳過來的信息永遠是不可信的,通過在視圖模型中增加驗證邏輯可以讓代碼更簡潔。“of”方法用於數據模型和視圖模型的轉換。“ApiModel”引入了“Swagger”用於對字段進行說明。

3、領域模型

   定義:描述業務實體屬性和行為的模型。一般來說是充血模型,后續會細講。限制:1)不得依賴於架構中的其它層、第三方基礎框架如Spring、DAO、HTTPClinet等。這么說吧,除了JDK外其它都不能依賴。就和狗一樣,依賴少表示血統純粹,越純粹越好;2)作用范圍只能在業務模型層中,不得外泄;3)嚴格注意每個模型的訪問限制,能不用public就不用。4)最好自行寫一些包含了公共能力(比如“對象判等”)的領域模型基類來保證代碼的干凈。

  這個里面我覺得有可能爭議最大的是關於領域模型的依賴,有一些比如字符串工具、日期工具這種的第三方類庫其實很普遍,完全的不依賴是否會更加的極端?怎么說呢,我個人在使用ODD開發的時候寫了一套基礎組件,包含了驗證、值類型、實體類型、領域倉庫、Saga等領域模型相關的組件(大部分都是抽象類)和少量的工具類,需要什么拿來即用,我稱之為“通用能力庫”。這個庫本身是自己寫的(注:本系列文章只關注DDD知識的講解,不會推薦任何的、成熟度不夠高的尤其是標榜為DDD的框架),也的確沒用到JDK外的其它第三方組件。而且,領域模型本身專用於業務邏輯處理和計算,像什么字符串格式化啊、日期格式化啊其實就不應該在領域模型里搞。寫通用能力庫畢竟也會占用精力,我的推薦是您在領域模型中盡量少的依賴第三方組件,越少越好。有些書籍中會展示在領域模型中注入DAO來實現嵌套對象的“懶加載”,我個人認為這個是不化不類,只有在需要向性能妥協時才會使用。

總結

  本章講了三個模型,雖然內容不多,可真正使用起來也是有一定的要求的。軟件開發是個細活兒,想做好當然要負出精力了。您完全可以突破上述所提到的限制,也能出東西,可需求總是變化的,總得改代碼,到時候吃虧的還是自己。這沒辦法,坑兒是您自己給自己挖的,除非您寫完了代碼就打算跑路,那也給了后面接手的人罵爹的機會。另外,您可能認為本章和DDD沒關系,我得提前聲明:咱不是寫小說呢,一個字多少多少錢,這些經驗您還真在其它書上找不着。而且,越是細節越能體現開發者的能力,搞技術到后面比的是什么?不再是會什么不會什么了,而是比誰開發的質量高和可維護性高。兩個員工做同樣的需求尤其是功能增強性相關的,一個5分鍾搞定,一個3天,你是老板你用哪個 ?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM