DDD 的哲學意味(中)

“關聯”、《矛盾論》、畢達哥拉斯學派

DDD 的哲學意味(上)說到了 “模型驅動的設計” 以及其中兩個重要的模式 “實體” 和“值對象”,兩者統稱 “領域對象”。在領域建模的過程中,建立領域對象間的“關聯(Association)” 也是非常重要的。《DDD》第 5.1 節對此進行了專門的討論。不過與實體不同,艾老師並沒有把關聯當做一種正式的“模式”。這一點實屬可惜,因爲關聯至少與實體有同樣的重要性。爲什麼這麼說呢?下面還是先扯幾句哲學。

前面提到毛老師的《實踐論》,這裏再說說怹老人家的另一篇傑作《矛盾論》。這篇論文的第一部分 “兩種宇宙觀” 中提到了兩種認識論體系:“形而上學”和“唯物辯證法”。前者是錯誤的、後者是正確的。

形而上學認爲事物的發展是靜態的、外因驅動的、孤立的;唯物辯證法則認爲事物的發展是動態的、內因驅動的、聯繫的(還記得中學政治課背過嗎?)。前兩點我們後面再聊,這裏先討論 “聯繫”。

《矛盾論》中引用列寧的話:“要真正地認識事物,就必須把握住、研究清楚它的一切方面、一切聯繫和‘中介’。我們永遠也不會完全做到這一點,但是,全面性這一要求可以使我們防止犯錯誤和防止僵化。” 這強調了,只有充分了解事物之間的聯繫,才能充分認識事物。

**DDD 中,領域(事物)的概念以實體、值對象、聚合、模塊等方式表達出來。有些夥伴把領域中的主要聚合或實體識別出來後,卻沒有識別它們之間的關聯,就認爲已經完成了領域建模。這樣的模型其實是不完整的。**那麼,如果我們認識到了這一點,並且識別出了關聯之後,還要進一步識別關聯上的哪些信息呢?讓我們先看一下古老的畢達哥拉斯學派。

這個學派在古希臘最早對數學進行了系統的研究。他們發現了無理數,然後把泄露無理數祕密的團隊成員扔進了大海。畢達哥拉斯認爲世界的本原是 “數”。宇宙的和諧來源於數的和諧。而數的和諧體現在十組對立的關係中:一與多、奇與偶、左與右、陰與陽、動與靜、曲與直、明與暗、善與惡、方與長、有限與無限。在這十組關係中,最根本的是 “一與多”。也就是說,只有把握了 “一與多”,才能把握 “數”,進而把握世界。

從畢達哥拉斯的學說中,我們或許可以得到這樣的啓發(嗯 ~ ~ 我承認有點牽強):在領域模型中,對關聯進行建模時首先要考慮的是數量關係。也就是常說的 “一對一”“一對多”“多對多” 等等。如果做得精細些的話,還可以考慮存在性(必選的還是可選的)。不少建模人員在建模時都忽視了理清數量關係,從而限制了對領域知識的深刻理解。

對數量關係的識別看似簡單,但我們發現百分之六七十的初學者在實踐中都會搞錯。只有經過一段時間的練習,才能充分掌握。

**此外,前文提過,除了這裏說的實例之間的關聯以外,還要考慮類型之間的關聯(也就是泛化)。**對泛化關係的掌握,是領域建模技術從初級階段邁向中級階段的門檻,也是向高級建模技術進階的關鍵。要熟練掌握這項技術,除了在項目中實踐,還可參考《分析模式》。在《DDD》的後半部分有一些高階的例子運用了泛化,但在前面講模型驅動設計的章節卻沒有提及泛化,這就忽略了一種建模的重要手段,也算是該書的一個瑕疵吧。

模型的演進、辯證法、進化論

前文說到,唯物辯證法認爲事物的發展是動態的、內因驅動的、聯繫的;並且已經討論了 “聯繫”。這一節首先討論 “動態”。

事物的發展變化是永恆的。因此,我們會不斷強調,與其期望一次性把領域模型和架構建好,不如建立團隊的架構演進能力。在《DDD》第 8 章至第 13 章,集中討論了模型重構的方法。

模型要不斷演進,這個說法多數人聽了都會點頭稱是。又有誰不知道 “唯一不變的就是變化” 呢?然而在這一點上,說到和做到是有距離的。

真想做到模型的演進,不僅需要上述《DDD》中的建模技能,還要紮實地掌握重構、TDD(或者至少是自動化測試)和持續集成,我將之稱爲敏捷工程實踐的 “老三樣”。此外,還要掌握架構演進和數據庫演進的若干種模式,以及建立多維度的指標體系,利用工具進行量化的架構守護等。

然而我們很多人,哪怕接受了 DDD,也是急於學習建模技能(這本身沒有錯),而對模型演進絲毫不感興趣。這樣的 DDD 往往半途而廢。這是因爲,如果不能演進模型(進而演進整個軟件系統),那麼軟件和模型就會逐漸不一致,最後模型變成廢紙一張,一切打回原形。這背後可以迴歸到團隊甚至企業文化的問題。衡量一個企業的文化,關鍵不是看怎麼說,而是看怎麼做。

談完 “動態”,再談 “內因驅動”。

《矛盾論》中強調 “唯物辯證法認爲外因是變化的條件,內因是變化的根據,外因通過內因而起作用”。內因是第一位的,外因是第二位的。而形而上學只看到外因,外因也能使事物發生變化,但只是量的變化。而內因才能導致質的變化。換句話說,事物本身必然包含內在的矛盾,這一矛盾的發展變化,在一定外因的條件下,會導致事物發生質的變化。

聯繫到模型的演進,我們從《DDD》中的相關例子看到,**隨着對領域知識理解的深入,模型的重構往往不是多了幾個實體、少了幾個關聯,而是多出了若干抽象層次,甚至將模型的核心部分打碎重組。**所以我們既要對這一點有充分的心裏準備,知道這是複雜系統的模型演進中必然發生的;也要未雨綢繆,掌握相關的建模和架構演進技術。不過也不用怕,當掌握相關技能後,再複雜的重構,總可以小步快跑,穩健推進。

前面反覆提到了架構演進。其實《DDD》和《演進式架構》是兩本書。兩者的側重點不同,一本側重領域建模,一本側重系統架構演進。不過在實踐中我們常常將兩者結合起來運用。下面聊兩句演進式架構的原理,這超出了《DDD》原書的範圍。

“演進式架構” 強調增量地、多維度地、導向性地架構變化過程。我們主要解釋這三個關鍵詞。

《演進式架構》(Building Evolutionary Architectures)和 “進化論”(Theory of Evolution)中的“演進” 和“進化”其實是同一個英文詞根。有人將 “Theroy of Evolution” 翻譯成“進化論”,有人翻譯成“演化論”。《演進式架構》的譯者討了個巧,各取一個字,就成了“演進”,其實都是一個意思。所以,演進式架構實際上引用了進化論的隱喻。

達爾文的《物種起源》將進化論總結爲遺傳、變異和適者生存。該書第一部分首先講的是 “人工選擇”,例如對狗和金魚的馴化;後面才推廣到自然選擇。將演進式架構和進化論進行比較,可以看到一些有趣的共同點。

將軟件系統類比爲生命體。不影響系統架構的漸進式修改相當於生物的 “遺傳”。對架構的本質性修改相當於生物的“變異”。變異的系統需要經過各種驗證,看看是否符合人的需求,這實際上是一種“人工選擇” 的過程。適應需求的架構得以延續,不適應的則會被淘汰或進一步變異,這就是演進式架構中說的“導向性”,也這就是 “適者生存”。達爾文指出物種的進化總是漸進式的,而不會短時間內發生劇烈變化。這就是演進式架構中的 “增量式” 變化。生物適應環境時,要適應多方面的因素:捕食能力、逃避天敵的能力、適應氣候的能力、抵禦病蟲害的能力、繁殖能力等等。各種因素都適應,才能生存。同樣,系統也要適應多方面的因素:功能的正確性、可擴展性、可維護性、性能、安全等等。這就是演進式架構中說的 “多維度”。

上述類比可以促使我們對演進式架構進行更深入的思考。至於具體技術,可以參閱原書。

限界上下文與人類認識能力的有限性

雖然哲學家可能是人類有史以來最喜歡爭辯的羣體,但有一個觀點,多數哲學家卻是有共識的:人的認識能力是有限的。從孟子的 “吾生也有涯,而知也無涯”,到康德的 “物自體不可知”,再到辯證唯物主義的 “人只能認識相對真理”,都說明了這一點。那麼,這和限界上下文有什麼關係呢?

如果問一位同事限界上下文是什麼,常常聽到這樣的回答:“業務功能的邊界”“業務領域的邊界” 諸如此類。這種說法雖然沒錯,但說的是結果,不是原因;是表象,不是本質。因爲聚合、模塊等也可以說是一種 “業務功能的邊界”。所以上述回答沒有答到點子上。

限界上下文是在《DDD》第 14 章 “保持模型的完整性” 中介紹的。眼尖的同學可能就會問了:爲什麼這一章的名字不叫做 “劃分功能邊界”?這裏的“完整性” 到底指什麼?

讓我們繼續看原文。這一章的開頭幾段所舉的例子,是說有兩個團隊共用了一個 Charge(收費)對象。但兩個團隊所說的 Charge 背後的業務概念其實是有差別的。開始時兩個團隊都沒有意識到這個問題。他們都去改這個類,後來程序崩潰了。然後作者說 “模型最基本的要求是它應該保持內部一致,術語總具有相同的意義,並且不包含互相矛盾的規則……”。所以,作者在本章要解決的實際上是系統的一致性問題。

這一章標題中的 “完整性” 是對單詞 Integrity 的翻譯。Integrity 固然有 “完整性” 的意思,但也包含 “統一”、“健全” 等含義。因此,本章的標題如果翻譯成 “保持模型的統一” 會更準確。翻譯成 “完整性” 則容易造成誤導。

保持系統的一致性,早已被 IT 界的前輩們所重視。例如,Brooks 老師在七十年代寫的《人月神話》中已經做了強調。不過,之前的專家們在潛意識裏總是認爲,對於一個複雜的系統,總有辦法做到團隊理解的全局一致性。《人月神話》中就列舉了若干種方法。

然而《DDD》中指出 “在理想世界中,我們可以得到涵蓋整個企業領域的單一模型。這個模型將是統一的,沒有任何相互矛盾或相互重疊的術語定義” 然而 “大型系統領域模型的完全統一既不可行,也不划算”。

爲什麼 “不可行” 呢?答案就是人類認識能力的有限性。由於大型軟件是團隊協作開發的,因此這裏的認識能力還要擴展到羣體的認識能力。也就是包含了個人的認識能力、羣體的協作能力、以及兩者的相互作用。越是複雜的系統,認知起來越困難。當系統複雜性達到一定程度時,就超過了一個團隊的認知能力,無法保證系統的一致性了。

解決的辦法就是分而治之,將一個 “理想中” 的大模型劃分成幾個小模型,每個小模型不超過團隊的認知能力。因此每個小模型內部就可以保證嚴格的一致性,而與另一個模型內部不需要一致,只需要約定好模型之間的接口就可以了。也就是說,我們在瞭解了人類認識能力的有限性之後,不再追求全局一致性,而是退而求其次,代之以局部的一致性,從而使系統的正確性在總體上得到管理,實現業務目標,這樣就足夠了。

概念的一致性是通過語義上的一致性來表達的,所以作者引用了語言學上 “上下文” 的術語。在日常語言中,人們常常會隨時切換上下文,在特定場景下未必會影響溝通。但在計算機軟件中,一個元素要麼屬於一個上下文,要麼不屬於,這個界限必須清清楚楚。爲了強調這一邊界的重要性,因此稱爲“限界上下文”。

上下文的具體劃分方法,仍然要圍繞領域概念的內聚和耦合關係。因此劃分的結果,當然表現了一個 “領域的邊界” 或者說“功能的邊界”。如上文所述,這是結果而不是原因。

根據敏捷組織的理論,一個開發組的規模最好在 5~9 人之間(這是有心理學研究作爲依據的)。那麼,一個限界上下文的規模,一般就是這樣一個開發組所能把握的規模。

知道了人類認識能力的侷限,就使我們對這個世界秉持謙卑和敬畏的態度。這也有助於我們理解限界上下文所要解決的問題、劃分的方法、及其與團隊的關係。最終使我們能夠正確劃分上下文,管控好複雜系統的一致性。


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/A1M2x8nlZgj8oy9UYgSrSw