天天看點

産品代碼都給你看了,可别再說不會DDD系列(三):戰略設計

作者:碼如雲

這是一個講解DDD落地的文章系列,它以一個真實的并已成功上線的軟體項目為例,系統性地講解DDD在落地實施過程中的典型實踐,以及在面臨實際業務場景時的各種取舍。

本系列包含以下文章:

  1. DDD入門
  2. DDD概念大白話
  3. 戰略設計(本文)
  4. 代碼工程結構
  5. 請求處理流程
  6. 聚合根與資源庫
  7. 實體與值對象
  8. 應用服務與領域服務
  9. 領域事件
  10. CQRS

案例項目介紹

既然DDD是“領域”驅動,那麼我們便不能抛開業務講技術,為此讓我們先從業務上了解一下貫穿本文章系列的案例項目 —— 碼如雲(不是馬雲,也不是碼雲)。如你已經在本系列的其他文章中了解過該案例,可跳過。

碼如雲是一個針對二維碼場景應用的SaaS軟體平台,采用“一物一碼”的業務模式,可以為每一件“物品”生成一個二維碼,并以該二維碼為入口展開對“物品”的相關操作,典型的應用場景包括固定資産管理、裝置巡檢以及物品标簽等。

在使用碼如雲時,首先需要建立一個應用,一個應用(App)包含了多個頁面(Page)(也可稱為表單),一個頁面又可以包含多個控件(Control)(比如單選框控件)。應用建立好後,可在應用下建立多個執行個體(QR)用于表示被管理的對象(比如機器裝置)。每個執行個體均對應一個二維碼,手機掃碼便可對執行個體進行相應操作,比如檢視執行個體相關資訊或者填寫頁面表單等,對表單的一次填寫稱為送出(Submission);更多概念請參考碼如雲術語。

在技術上,碼如雲是一個無代碼平台,包含了表單引擎、審批流程和資料報表等多個功能子產品。碼如雲全程采用DDD完成開發,其後端技術棧主要有Java、Spring Boot和MongoDB等。

戰略設計

在上一篇DDD概念大白話中,我們提出了一個觀點:DDD的戰略設計隻在解決一個問題,即軟體的子產品化劃分的問題。在本文中,我們将對此做出詳細的解釋。

不過,首先讓我們來看看DDD的戰略設計原本包含哪些内容。戰略設計中包含領域、子域、通用語言和限界上下文等概念。領域(Domain)表示一個行業中所發生的一切業務;子域(Subdomain)則表示領域中細分之後的子業務,是比領域更小的概念,子域又可細分為核心子域(Core Domain)、支撐子域(Supporting Domain)和通用子域(Generic Domain);通用語言(Ubiquitous Language)表示在領域中所有人員都使用一套相同的語言進行溝通交流;限界上下文(Bounded Context)則表示由通用語言所形成的上下文邊界。讀到這裡,你是不是感覺好像什麼都說了,又感覺什麼都沒說?

事實上不難看出,無論是從領域到子域,還是從通用語言到限界上下文,其中都展現了一種“分”的思想,這種思想也正是整個計算機科學中的一種基本思想——分治法(devide and conquer)。作為頂層設計的DDD戰略設計來講,這種“分”的思想的落地不正是我們在軟體架構圖中所看到的那些方塊麼?不正是軟體的子產品化劃分麼?

有了以上認識,再讓我們來重新審視一下戰略設計中的各種概念。軟體中有些子產品是業務的核心,對應着DDD中的“核心域”的概念,比如電商系統中的訂單子產品;有些子產品對核心子產品起支撐作用,對應DDD中“支撐域”,比如電商系統中的積分子產品;而有些子產品是通用性質的,對應着DDD中的“通用域”,比如登入管理子產品。限界上下文可以看做是子域落地後的概念,是以通常與子域存在一一對應的關系,也即一個限界上下文表示一個子產品。限界上下文可以這麼了解:在DDD中,允許在不同的子產品中存在相同名稱的對象,但是它們在各自的上下文中所表示的含義是不同的,這也是“限界”一詞的由來。舉個例子,在電商系統中,存在交易子產品和物流子產品,它們都包含“訂單(Order)”對象,但是交易子產品中的訂單和物流子產品中的訂單所承載的業務含義是不一樣的,在交易子產品中我們更專注訂單的價格、數量和折扣等,而在物流子產品中我們則更關注訂單的重量、體積和物流狀态等。

産品代碼都給你看了,可别再說不會DDD系列(三):戰略設計

說DDD的戰略設計隻是子產品化劃分并不是要貶低戰略設計的意思,事實上恰恰相反,戰略設計很重要。DDD的開山鼻祖Eric Evans曾經說,如果讓他重新撰寫《領域驅動設計》那本書,他會将原書中的很大部分全部撕掉,然後用于撰寫與限界上下文相關的内容,從此也可見戰略設計的重要性。但是,我們希望做的是讓讀者認清其中的本質,畢竟DDD本身是一種實踐性很強的學問,我們對DDD的認識不應該停留在對概念的咬文爵字上,而是真正能夠産出高品質的軟體。

事實上,軟體的子產品化劃分是一個非常古老的概念,它伴随着軟體的誕生而誕生,其萌芽至少可以追溯到世界上第一台通用電子計算機ENIAC的發明者之一約翰·皮斯普·埃克特(J. Presper Eckert)在一篇研究穿孔紙帶的論文中所提到的“Decomposition(分解)”。

産品代碼都給你看了,可别再說不會DDD系列(三):戰略設計

後來,軟體的子產品化經道格拉斯·麥克羅伊(Douglas McIlroy)和布萊德·考克斯(Brad Cox,Objective-C發明人)等人得到了進一步發展。如果我們再将眼光放開闊一些,你會發現子產品化的思想存在于各個行業中,比如船舶、橋梁、建築以及航空等領域。

産品代碼都給你看了,可别再說不會DDD系列(三):戰略設計

是以,子產品化對于接受了現代工業文明洗禮的我們來說,并不是一個陌生的詞彙。然而,難點并不在于如何定義子產品,而在于如何劃分子產品。在DDD中,這是一個見仁見智衆說紛纭的話題,為此,讓我們從一個小故事展開。

一個2歲的幼兒,從來沒有看到人的頭像簡易畫(下圖中左邊的圖檔),但是當你問他那是什麼的時候,他可能會說“人人”。這是為什麼?

産品代碼都給你看了,可别再說不會DDD系列(三):戰略設計

幼兒能夠辨認出他從來沒有看到過的東西,是因為他擁有兩種能力:經驗和抽象。他雖然沒有看到過人頭像簡易畫,但是他之前一定看到過真實的人,此所謂經驗,也即我們過去所經曆的事情;而他能夠将人像簡易畫和真實的人對等起來,則是因為人類與身俱來的抽象能力。此二者,恰恰是我們劃分軟體子產品所需要的東西,并且人人皆有。是以,你并不需要一套專門的學問來指導你完成DDD的戰略設計,你需要的依然是那些在日常工作生活中我們始終在使用着的技能。

但是,經驗有多有少,抽象有深有淺,導緻不同的人所劃分出來的子產品形态也不一樣。為了做好DDD戰略設計,你需要有充足的經驗以及對業務的深入了解。那麼,經驗到底到底多少算多呢?5年工作經驗夠不夠?10年又夠不夠?這種按照年限來區分經驗多寡的方式是不合适的,一個10年工作經驗的架構師,他可能在這10年内一直在重複性地做着一件事情,而一個3年工作經驗的程式員,卻可能已經經曆過很多項目、技術以及行業。是以,經驗是根據你在自己所處的行業中所耕耘的深度和廣度來計算的,而非時間。

你可能會認為經驗這個東西太不可名狀了,無法提煉出一套有據可循理論架構出來,的确沒有。然而,這正是軟體被稱之為藝術的原因,它讓每個人都有屬于其自己的發揮空間,況且還有大哲學家和大科學家為你背書,你還那麼不自信到要去追求一個咨詢師沒把你教會而你自己也沒學會的所謂的理論架構嗎?坦誠點,自信點,自豪地告訴别人:“我通過自己對業務的深入了解,外加自己的從業經驗和抽象能力,搞定了DDD的戰略設計!”

産品代碼都給你看了,可别再說不會DDD系列(三):戰略設計

讓我們來看個例子吧,搜尋功能是多數應用網站都有的功能,在一些應用中,搜尋可以簡單到隻是做個正則比對的小功能點,此時的搜尋固然稱不上一個子產品,而在另一些應用中,比如大型電商網站,搜尋包含了多條件多方式查詢等衆多内容,其背景的軟體架構和技術棧甚至都是專門設計的,此時的搜尋功能你哪怕找一個畢業生來設計估計結果都是一個獨立子產品。這裡,從功能點到功能子產品的變化過程中,沒有什麼量化工具和理論架構可言,說得直白點,這就是架構師的一個主觀認知而已。但是,這個認知卻是重要的,因為它展現了架構師對于一個問題的抽象能力,以及對于軟體邊界的識别能力。什麼是架構呢,一種解釋是軟體架構是項目中的資深程式員們對某個問題所達成的統一認識而已。

理論架構雖然沒有,但是指導性的原則還是有的,以下原則是程式員們耳熟能詳的程式設計原則,将其用在子產品化劃分上依然成立:

  • 高内聚,低耦合原則
  • 關注點分離原則
  • 單一職責原則

說到DDD,我們可能不得不說一下微服務,因為一般的了解是DDD因為微服務的興起而重新被業界重視。事實上,DDD和微服務的關系被牽強式地放大了,DDD之于微服務,無外乎“DDD的限界上下文可以用于指導微服務的劃分”,然而,在我們把限界上下文了解為子產品後,這種說法也就不值得再成為一個單獨的命題了。DDD的意義在于“DDD之于軟體”,而不是“DDD之于微服務”。

在碼如雲,我們采用了單體架構而非微服務,但這并不影響我們劃分限界上下文(子產品),我們通過Java分包的方式來劃分子產品。

産品代碼都給你看了,可别再說不會DDD系列(三):戰略設計

碼如雲的子產品關系并不複雜,僅包含3個頂層子產品,一個是核心上下文(子產品),其中包含各種核心的業務實體,比如應用和執行個體等,每個業務實體均被模組化為一個聚合根;第二個是背景管理上下文(子產品),用于碼如雲的背景營運,包含客戶關系、投訴管理和訂單管理等;第三個是內建上下文(子產品),用于處理與第三方的API內建。在前文中我們提到,登入功能可以被看做是通用子域而模組化為一個獨立的子產品,但是在碼如雲中我們并未這麼劃分,而是将登入功能消化在了核心上下文中,因為其粒度尚未大到需要獨立為一個子產品的程度。

總結

很多簡單的東西被人為的複雜化了,當資深人士們還在高談闊論微服務和SOA的差別時,Robert C. Martin(Bob大叔)站出來說,這倆就是一個東西。當下的DDD同樣也在遭受着“被複雜化”的境遇,軟體(至少企業級應用軟體)向來的實踐性是非常強的,結果從業者們自己把自己搞不會了,實則不應該呀!在本文中,我們說DDD的戰略設計隻是在解決軟體的子產品化劃分的問題,并不是要貶低戰略設計,而是希望讀者看到戰略設計的本質,進而從DDD中得到實實在在的好處,也推動這個行業可以健康地,不要那麼浮誇式地發展。