1. 程式人生 > >你或許以為你不需要領域驅動設計

你或許以為你不需要領域驅動設計

 

 

 

作者:鄒溪源,長沙資深網際網路從業者,架構師社群合夥人!


 一

猶記得剛剛參加工作時,是地圖廠商四維圖新集團旗下的一家子公司,主要從事規劃測繪相關軟體研發的公司。當時我的專案是為勘測設計院提供相對應的應用軟體,對地理資訊和規劃相關的圖紙資訊領域的認知,幾乎已經專業水平。事實上,規劃設計大概和軟體設計類似,有規劃的設計、或無規劃的設計,造成的結果幾乎是天壤之別。

 

我們或許很容易就能設想到一個毫無規劃設計的城市,縱橫交錯的路網、雜亂無章式的建築佈局、各種凌亂的棚戶區設計,恰好象徵著軟體設計的無序性,也恰好體現了軟體企業在經費不足、組織缺乏管理、開發者能力不足、軟體隨時隨地想改就改時的行業現狀,只能說這樣的軟體是最能符合當時實際勞動生產力水平的產品。

 

   

圖一:巴西棚戶區如圖一所示,巴西棚戶區,層層疊疊、風格迥異、密密麻麻,如果作為一個外人貿然來到這樣的地方,大概很容易迷失期間、更不用說充斥在棚戶區的各類毒品和黑社會。雜亂無章的建築和街區,就像程式碼中錯綜複雜的呼叫鏈;而藉助貧民區搞事的黑社會就像是程式碼中的異味或者bug,表面上看起來如此平靜、與世無爭、但是你永遠也不知道啥時候會來一冷槍。

 

不要以為離我們很遠,我們其實輕易就能寫出這樣的軟體工程專案。不一定是“大泥球”系統,也有可能只是一些看似簡單的業務系統,但內部程式碼邏輯,可能會複雜到令人窒息的程度。也許那個時候有個別開發者也許會試圖靠自己的能力來改變局面,但是往往也會礙於屎山太大,難以下嚥。

 

大概只有最頂級的規劃設計師、耗費足夠多的資源,才能將這樣的軟體系統進行整改。然而,即便如此,如果以後沒有持續維護的手段、更好的設計、僅靠老程式設計師或個別架構師、盲目相信將單體服務拆分成微服務,幾乎不太可能實現軟體未來的可持續發展。

 

 一個良好的軟體產品的一生、或許其實是一家企業一生的真實寫照。

 

在特定組織架構下,缺乏技術基因的組織有時候期待技術變革,卻會開啟新的泥坑。而那些渴望靠技術改變一切的技術專家,雖然擁有某些大廠微服務式架構、以及架構改造的經驗,他們也試圖通過自己的努力,為企業業務騰飛助力。而在他們過去的經驗中,往往相信組織遇到的問題,用微服務一定能解決問題。然後大肆擴招,一年內從幾個人的規模、擴招到數百人的規模,將原來的系統從單體服務、改良成為微服務。但是靠單槍匹馬根本無力拯救大勢,沒有更好的業務拆分策略,就只能按照資料庫的表名關係實現了最簡單的拆分。架構改造並非每次都會百試百靈,有時甚至連原來的需求都包不住,畢竟只能看到使用者介面層外觀上的表面邏輯,而隱藏在業務中的那數十萬行程式碼,哪怕包含了企業最有價值的經驗財富,也由於程式碼過於混亂,最終拋棄在原始碼管理器中,堪稱化神奇為腐朽。

 

老系統改造也好、新系統開發也好,毫無疑問,我們最容易相信的其實是老程式設計師經驗,而程式設計師們掌控系統的方式,就是靠資料庫建模來驅動軟體開發的古老模式,而且幾乎都是面向過程式的程式碼,這些程式碼的流程幾乎一模一樣,只需簡單的按照步驟,一步步套模式,輕易就能學會。

 

1、檢視使用者介面,定義需要繫結到介面的模型和層級結構。

 2、設計資料庫,不管什麼型別的專案,先根據客戶提供的業務表單、將其轉化成實體關係(ER圖)、然後建立對應的程式碼模型。有可能使用專業軟體設計ER圖,也有可能會使用Navicat軟體設計ER圖。

 3、設計介面,然後把資料拼湊成使用者介面層所需的物件。

 4、程式碼層次結構為傳統的三層架構,嚴格按照使用者介面層、業務邏輯層、資料訪問層進行設計,有時候會引入依賴注入框架,實現不同層次間的解耦。

 

但是有時候程式設計師不會嚴格區分需要編寫的程式碼,究竟是屬於哪個層次應該囊括的內容。於是毫無疑問,如果程式碼是為了實現使用者介面上某些資料繫結操作,程式碼就往使用者介面層寫;或者程式碼是為了實現從資料庫中抽取某些複雜資料、並構造成滿足使用者表現層邏輯的查詢物件,那麼就可以看到資料訪問層程式碼中那些臃腫的SQL語句或查詢方法。

 

正如“羅馬不是一天建成的”,屎山也同樣如此。這樣的寫法在程式碼剛剛編寫之初並沒有問題,只是隨著業務變化、時間的積累、程式設計師的水平、方法重構、新技術新元件的引入,程式碼將成為屎山。

 

這時,高階程式設計師們的價值,就在於他如何能夠在屎山中快速找到bug、並解決問題的能力,這大概是一種不能複用、不可再生的能力,因為永遠有讓人看不懂的垃圾程式碼,而且每家企業都有自己的特點,不同企業間往往不能迴圈利用。我一位朋友經常吐槽,他感覺自己的價值就是守住公司那份擁有8年曆史的古老程式碼,以便其他程式設計師在進行程式碼修改時,不會引發莫名其妙的bug讓系統無法運轉。

 

 在現代軟體工程學的教科書中,都會指出面向物件是解決軟體複雜性的方法,但實際上掌握這種方法的開發者並不多。由於開發者普遍缺乏抽象化思維,所以面向資料庫、面向過程式的程式設計習慣能夠成為業界主流,並非時代的倒退,而僅僅只是在短期效率和長期維護性上,被迫做出的艱難選擇。

 

假設我們設計出的符合三層架構的系統結構圖簡化後,如下圖所示:

 

我們來看看這種資料庫建模的開發流程中的輸出成果:

 

1、會定義兩種物件,分別是是面向UI層的模型(DTO)和資料實體(Entity)。在領域驅動設計中,將這兩種稱為所謂貧血模型,貧血模型,只有賦值器Set和取值器Get,(在Java裡面會使用POJO 這個名詞來定義)。貧血模型是為了作為儲存狀態或傳遞物件而存在,他並非按照實際用例場景對某類具體事務的抽象、也沒有與物件相關的行為。

 

2、定義資料訪問層來實現資料的持久化、或者從持久層實現資料的建立過程。資料訪問層存在的目的是為了構建上述貧血模型物件,這種訪問機制被成為“事務指令碼”。事務指令碼與物件行為割裂,而且容易導致異味產生。

 

3、與使用者行為相關的操作割裂的存放在不同層。有的可能放在使用者介面層、有的可能放在資料訪問層、有的可能放在業務邏輯層,造成了領域知識的丟失。

 

4、使用者介面層使用介面作為外觀或者一種行為、開發者會使用自己獨立的風格習慣來定義這種行為,就容易造成術語和規則不統一,也會為後期產品的維護迭代造成問題。

 

5、現在的軟體設計,往往要求輸出一份高保真的原型圖、也會按照敏捷專案管理的流程對這份原型圖建立持續更新的機制,確保原型圖是需求的具體表達,但是產品語言並非統一語言,也許產品語言具有業務含義,但是由於不能指導開發者進行介面、類、持久層的設計,造成了程式碼與需求的割裂。在張逸老師的《領域驅動戰術實踐》提到他曾經使用dimension和metric兩種不同的物件來定義一個維度物件,為程式碼造成了不必要的麻煩。我也曾經在一個專案,遇到過產品術語未能澄清,導致開發中使用style和theme兩種截然不同的定義來定義與“風格”相關術語,為程式碼引入了不必要的糾結。

 

 領域驅動設計引入了以下概念,但是我們無需在這篇文章中深刻理解這些概念的具體含義,我們只需知道,有這個東西。當我們開始按照領域驅動設計的方法設計一個系統時,按照前人整理的領域驅動的sample,往往就會將概念融匯貫通,達到更好的理解效果。

 

1、統一語言:定義好產品原型,需要建立統一語言。這是一種在內部和外部都能使用的規範化用語,包括UML、適當的圖、一致性的描述、以及專業術語和術語對應的英文描述。

 

2、實體:在領域中可以通過標識進行唯一值定位的物件。

 

3、值物件:在領域中,從其他領域或某個實體中分離出只包含某些特定屬性的物件。由於不具備唯一性特徵,往往無需用於資料持久化。

 

4、聚合、聚合根:將具有相關性的物件聚合在一起,並以聚合根的形式統一對外提供訪問方法和屬性欄位成員。

 

5、限界上下文:領域包含核心領域、子域和通用子域,而限界上下文則是一個具體業務的流程。每個限界上下文獨立於其他限界上下文而存在,獨立演進、功能完備。限界上下文的識別充滿技術含量。

 

6、領域服務:包括倉儲服務和工廠服務,前者負責實現物件與資料庫的操作過程、封裝了一系列資料庫操作的方法;後者則側重於物件的建立過程。個人認為從三層架構演進到領域驅動架構過程中,倉儲服務是最接近於資料訪問層的邏輯,也是讓大部分領域驅動架構最終又迴歸到三層架構的一種通病。從對資料訪問層中抽出物件、行為、資料訪問,是戰術設計的關鍵步驟。

 

領域驅動設計引入了一堆新的架構形式,包括經典的四層架構、EDA(事件驅動架構)、CQRS架構(命令查詢職責分離)。而由於Evans的原書沒有過分討論如何識別領域,後來又有許多大佬在他的基礎上進行了完善,提出了許多方法,包括名詞、形容詞、動詞建模法、事件風暴、四色建模等方法,限於篇幅,且聽下回分解。

 

 

領域驅動設計,或許是解決這些問題的一劑良方,但也或許是開啟了暗黑世界的大門。

 

概念晦澀難懂、程式設計師們不願意開始思維變革、技術上可能存在不預期的坑、都可能讓新方法的實踐陷入一灘爛泥。還有許多人以為自己看懂了領域驅動設計(包括筆者),在往專案中運用時,總是有意無意的會被過程式程式碼的思維定式控制,讓架構回退到三層架構。

 

由於微服務架構的興起,讓複雜系統的開發維護成為大家普遍關心的問題,使得Eric Evans於十五年前提出的這套理論,在今天綻放出了新的光芒。當然領域驅動設計僅僅只是眾多面向物件程式設計的一種實踐,通過領域驅動設計將UML等方法靈活的運用其中,通過打破原有資料庫關係建模給程式碼造成的桎梏,讓開發者能夠真正的實現面向物件程式設計。

 

然而思維模式的轉換並非易事,從過程式程式碼中,抽離出與物件有關的行為,遠比理解這幾個概念要複雜,這需要大量經驗的積累。

毋庸置疑,資料庫建模驅動軟體開發具有速度快、學習成本低的顯著特點,在許多專案中,能在短期內可以給開發者帶來許多便利;而應用領域驅動設計,則可以在更長的維護週期內,給軟體維護帶來實質性好處。

 

兩種不同型別的開發模式,根據企業實際出發進行選擇,還只是開始,但能真正運用好領域驅動設計或者UML、面向物件設計這種軟體工程的美學思維來改造我們的系統,讓系統綻放出更加璀璨的光芒,這才是軟體設計的樂趣所在。

&n