1. 程式人生 > >如何初識領域驅動設計

如何初識領域驅動設計

服務 nal data 相關 希望 演變 代碼實現 log tran

? 研究領域驅動設計(後面簡稱DDD)有半年之多,初識DDD是因為了解何為充血模式,何為貧血模式,進而順蔓摸瓜觸及DDD,初次了解有種相見恨晚的感覺,為什麽到現在才了解到有DDD這麽個東西,之後,一個伴隨我成長的疑惑,在我成長過程中不斷致力於去解決,終於在DDD幫助下雲撥霧散。
? 我的疑惑,第一點就是對於我們采用了不知道多久的開發模式,即Controller->Service->Dao,Service層扮演者上帝類,所有的邏輯都往裏塞,這種模式造成Service層的無限膨脹真的是對的嗎,很顯然答案是不對的,但事實上很多人對此還是無動於衷,但是究竟要如何改進?以前在大學的時候,自己一直在給自己找外包項目做,曾經對自己放下豪言壯語,多復雜的系統,只要一次性過不要求對其進行維護以及加和改需求,我都能做出來,因為這種開發模式,確實是將CURD表現淋漓盡致,一個資深工程師的代碼實現結果,一個菜鳥也同樣做到(這裏說的是結果,不是過程,然而實際是過程也可能一樣,這就很難受了)。第二點,其實也是延續著第一點而來,隨著經驗的豐富,開始領悟到面向對象和軟件工程,那麽問題來了,在這種傳統的開發模式下,面向對象究竟體現在哪裏,我們學到的設計模式,究竟在哪裏展開實踐

,而實際上我們往往是掛羊頭賣狗肉,羊頭是面向對象,狗肉是面向過程。
? 下面我將一點點的引導大家進入DDD的思維模式中,將我們習慣的開發方式面向過程開發與DDD所倡導的面向模型開發進行對比,逐步進入面向對象。註意,本篇僅僅告訴大家何為DDD,以及DDD的好處和缺點,具體如何實踐將在後面展開探討。DDD提出了一個所謂的領域建模,其實本質是面向對象開發原則中的類的單一職責,我們的第一個問題是如何處置承擔過多的職責的Service上帝類。一個字很好解決,就是拆,筆者曾經嘗試按著類的單一職責的指導,將一個依賴註入過多其他類的一個Service類,可能有十幾二十個,讀者你可以檢查一下自己的項目,這就是壞代碼,這帶來的好處其實只有功能點封裝,並不能解決不讓Service類臃腫的問題,他還是會慢慢臃腫起來的,而本質仍是面向過程開發,還有一個問題是,就算是這種做法解決Service類臃腫的問題,他無法解決一個很關鍵的東西,即這裏面的代碼,沒有體現出業務性。什麽叫做沒有體現出業務性?比如說激活用戶,我們一般會怎麽開發?

public class UserService{
    public void updateAvailableById(long id,boolean available){
        userDao.updateAvailableById(id,available);
    }
}

? 這是很典型的面向過程開發的代碼,在我們的項目中處處可見,喪失了面向對象中對象的概念,而現如今我們在開發的時候,反而被因為使用了數據庫而束縛了我們的思維,我們在接收到需求的第一時刻,在分析設計階段,往往是以數據表為核心進行分析設計, 也就是根據需求首先得到數據表名和字段,然後培訓程序員學會SQL語句如何操作這些數據表,那麽程序員為實現數據表的前後順序操作,必然會將代碼寫成過程式的風格,以數據表為核心進行分析設計,代碼很難避免不演變成過程式的代碼,因為我們的重點放在了操作某張表,以及相關的某個字段


? 這裏就是所謂的貧血模型了,UserDao是關於用戶表的SQL語句的集合,在項目中的表現就是寫了一堆SQL語句,UserService則是作為Transaction Script的入口以及夾雜著其他雜七雜八的Service類。而這一整個過程中,user對象都沒出現過。
而領域驅動倡導的做法如下。

@Data
public class User{
    private Long id;
    private Boolean available;
    public void activate(){
           this.setAvailable(true);
    }
}
//領域驅動服務
@Service
public class UserOperationService{
      @Autowired
      private UserRepository userRepository;
      public void activate(User user){
          user.activate();
          userRepository.save(user);
      }
}

? 首先第一點是類富含行為(即充血模式),這才是真正倡導的面向對象開發,業務系統所關註的點是業務功能而不是想著這個功能我們應該如何建表,如何寫SQL語句,一直以來我們的思維都因為使用了數據庫而被綁架了,一上來就開始建表寫代碼,代碼寫的非常冗余,完全是過程式的思考方式,最後導致系統非常難以維護。業務功能只需要明白是激活功能,而不是把user表中available設為true,數據庫只是基礎設施,我們不應該關註以及知會對應的SQL語句是怎麽樣的,所以DDD強調的是Repository而不是DAO,Repository翻譯為倉庫資源,所需即所得,不用去關註其背後,而DAO我們不得不去關心SQL語句如何寫。可以前往閱讀這篇文章進入更深入的思考對象和數據庫的天然阻抗,如果看完之後還不是很明白的話可以在留言區留下疑惑。
? 我們不應將原屬於領域模型的行為方法等劃放在服務中實現,對象不但有屬性還有行為將數據和行為封裝在一起,並與現實世界的業務對象相映射。回歸到我們最初剛學習面向對象教我們的那樣,萬物皆是類,而不是只有一個上帝類,我們需要多創建有合適邏輯的類,各類具備明確的職責劃分,使得邏輯分散到合適對象中。這樣的對象就是“充血模型”,而這個過程我們也稱之為領域建模,這才是面向對象的真諦啊!
? 說到這裏,大家可能對以上的示例存在著一點疑惑,1.“怎麽這麽簡單的功能,用DDD的方式下卻整的這麽復雜?”,2.“你說的不就是要使用ORM框架hibernate嗎”。3.“但是一切說到底不都是需要對數據的增刪查改嗎”
? 第一點,DDD為什麽稱之為復雜軟件的設計之道?因為系統夠復雜,才能全面展現DDD的功效,業務邏輯站在至高點使得任何業務邏輯的修改擴展都能做到兵來將擋水來土掩,極大的降低了剛接觸該系統的同事上手入門的門檻,以及更好的避免存在隱藏BUG(你總會因為不小心改了一行代碼,而導致出現了業務異常)。簡單的系統功能,只有簡單的增刪查改,所有的業務邏輯實際只有簡單的增加和查詢,根本無法提煉出來領域模型,若直接運用DDD的全部理論,實則浪費時間,殺雞用牛刀。但是沒有亙古不變的代碼,重構是一直伴隨著整個系統的演變,因此建議是即便是簡單系統,最好一開始就搭好DDD的樣子,後面我會繼續出幾篇文章,總結我對於DDD理論的實踐。
? 對於第二點,建議大家前往閱讀此篇文章 領域驅動設計(DDD)的實踐經驗分享之ORM的思考,還是那句話,如果看完還不是很明白的話,請到留言區進行留言。其實目的就是為了避免進行數據建模, 數據模型,數據庫技術綁架了面向對象開發的思路,無形中導致開發人員率先輸出表設計,而忽略原本的模型設計,進行形成思維習慣,“你這樣表不好設計啊”。而Hibernate其實是根據DDD中的JPA理論指導下設計,就是為了讓開發人員更加專註於業務邏輯實現,進行領域建模。
? 還有一個問題是一直以來很多人都錯誤的使用了像hibernate這種全ORM框架,建議大家前往閱讀此片文章如何對 JPA 或者 MyBatis 進行技術選型
? 第三點,初步認識DDD並想展開實踐的,確實會存在這個疑惑,DDD難在一點的是,他需要你思維的轉變,你需要開始不去關註數據庫,不去關心SQL語句, 從數據建模轉向領域建模,而這個過程又會將你面向對象過車功能的思維短板暴露出來,很抱歉,長期以來你並沒有用面向對象的思維來開發。
? Alistair Cockburn 提出了六邊形架構,旨在解決了傳統的分層架構所帶來的問題。倡導的是屏蔽技術細節,強調業務邏輯,最終目的是實現業務邏輯可重用,組織為一個可重用的自封閉的業務模型,即拷貝不走樣。仔細想想在貧血模式下,最大的不足就是業務邏輯不能重用,業務邏輯沒有組織為一個可重用的自封閉的業務模型。
技術分享圖片
? 初步認識領域驅動設計的第一篇文章就講到這裏,後續會繼續分享領域驅動設計的概念和實踐。走過路過不要錯過,您的點贊是給予我寫作的最大動力。
? 寫在最後,本人後續關於領域驅動設計的文章後陸續發表在掘金上,希望大家能夠多多支持

如何初識領域驅動設計