DDD 的哲學意味(上)

最近有小夥伴在 Thoughtworks DDD 社區中提起了有關哲學的話題,這在我這個哲學民科(下文簡稱 “哲民”)的心中激起了陣陣漣漪。

據小道消息,Eric Evans 認爲 DDD 不是一種方法學,而是一種軟件開發的思想和哲學。言下之意,“方法學” 把 DDD 給說小了。好吧,那咱就順着艾老師的意思,看看 DDD 和哲學能碰出什麼火花來。

由於出自哲民之手,本文穿鑿附會有餘,科學嚴謹不足,僅供大家茶餘飯後消愁解悶。歡迎提出寶貴意見,如有任何不同觀點,以您的爲準 。

好吧,讓我們從頭開始。

引子:從本體論到認識論

最早,無論中外的哲學家,首先思考的問題是世界的本原(希臘文:ἀρχή)是什麼。德謨克利特認爲是 “原子”,老子認爲是“道”,基督教認爲是上帝,唯物主義認爲是“物質” 等等。研究這些問題的學問,後世稱爲“本體論”(Ontology)。

在爭論世界本原的過程中,人們不禁會問這樣的問題:我們怎麼知道我們認爲的 “本原” 就是正確的?推而廣之,我們怎麼知道我們所知道的東西就是正確的?還可以進一步追問:世界是可知的嗎?人類認知的邊界是什麼?知識是如何獲得的?知識是先天的還是後天的? 等等。研究這些問題的哲學分支就是 “認識論”(Epistemology)。認識論是關於知識的學問,是對“思考” 進行思考。

對 “思考” 的思考,引發了邏輯學的創立。最早系統地研究邏輯學的是亞里士多德老師。爲了研究邏輯,首先就要搞清“概念”,爲了搞清概念,亞老師討論了“範疇論”(見《範疇篇》)。

“模型驅動設計” 與亞里士多德的 “範疇論”

爲避免混淆,先說明一下,“範疇論” 有兩種不同的含義:一種是數學上的(Category theory),一種是哲學上的(Categoris)。本文說的是後者。

什麼是 “範疇” 呢?我嘗試給出一個通俗的(也可能是庸俗的)解釋。亞老師的邏輯學是從對語言的研究展開的。老先生嘗試對人們語言中的所有概念進行歸類。比如把人歸爲動物,動物歸爲生物等等,一級一級往上歸。最後歸到不能再歸了,發現歸成了十大類(Categoris)。Categories 本來可以翻譯爲 “類”。不過當年中國的翻譯家借用《尚書》中“洪範九疇” 的說法,譯成了“範疇”。這當然顯得更有學問嘍。

亞里士多德的十個範疇包括:

(上面的括號中注了希臘文和英文,維基百科抄的啦。後面的例子有些是亞老師給的,有些是我加的)

DDD 領域建模中的概念可以和亞老師的十範疇建立起對應關係,如下表:

Ru6ACm

對上表還有幾點說明:

 從現在的觀點來看,亞老師的十範疇來源於經驗,本身並不嚴密。上表中對應關係的細節也可再行斟酌。關鍵是總體來看,這種對應關係到底僅僅是牽強附會,還是有必然的內在聯繫呢?答案是後者。

DDD 的核心是建立領域模型。艾老師的原書(以下簡稱《DDD》)將以領域模型爲核心的開發方法稱爲 “模型驅動設計”(《DDD》第 4 至第 7 章)。

領域模型是現實世界的抽象。建立領域模型的過程就是認識現實世界中的概念(領域概念或者說業務概念),進而將認識的結果用一定的方式可視化的過程。從哲學的角度,就是認識論的問題。由於是對領域概念的認識,因此 Martin Fowler 在《分析模式》中將領域模型稱爲 “概念模型”。對概念的歸類,形成了 “範疇”。

面向對象設計和編程的典型特徵是:封裝、繼承和多態。上升到 “方法學” 層面後,“繼承”(inheritance)擴展爲“泛化”(generalization)。繼承是泛化的特例。

當人們認識一個實體的時候(例如一匹馬、一個人),只會關心實體的外在表現,也就是屬性和行爲,而不會關心實體內部的結構。也就是說我們一般不會爲了認識一個新朋友,而把這位仁兄切開看看他內臟的構造。因此實體的內部信息是隱藏起來的,這就是 “封裝”。

在特定場景下,客戶可以分成兩類:個人客戶和團體客戶。分類關係就是 “泛化”。其中“客戶” 是父類型(Super Type),“個人客戶”和 “團體客戶” 是子類型(Sub Type)。不同子類型的同名行爲(例如更改聯繫方式)可能具有不同的邏輯和數據,這就是“多態”。這裏按照《分析模式》的習慣,在分析(概念模型)層面使用術語“類型(Type)”;在設計層面使用術語“類(Class)”。類是類型的實現。

類型可以看做實例的集合。關聯(association)是實例之間的關係;而泛化是集合之間的關係。父類型是全集,子類型是子集。子集又可以有交集,從而形成《分析模式》中所說的 “多重分類”。分析層面的多重分類無法直接用主流編程語言的“繼承” 來實現,而要在設計上做些特殊處理,這是後話了。

分類或者說範疇(Categories)是人理解概念的基本方法,因而也是面向對象方法學的基本方法。DDD 正是建立在面向對象方法學之上的。

總之,開發軟件時,先基於領域知識建立領域模型,再根據領域模型編寫代碼和設計數據庫。這種 “模型驅動設計” 有其認識論的根源,符合人的認知規律。

我們真的是在對 “客觀世界” 建模嗎?

上文說到爲了開發軟件,我們首先要對客觀世界進行建模。

對於我們這些從小受到辯證唯物主義教育的小夥伴來說,這沒什麼問題。但有些哲學家可未必同意。

首先,佛教哲學認爲根本就不存在 “客觀世界”,一切都是空幻不實的。印度教也認爲我們不過是生活在梵天的一場夢中。

好吧,就算存在客觀世界,我們能夠認識它嗎?

柏拉圖認爲不能。在他的世界觀中,真實的世界是 “理念” 的世界,我們所能認識的世界只是理念世界的摹本,而真實的世界是我們無法認識的。類似的,康德將獨立於人的意識而存在的事物稱爲“物自體”,雖然存在,但無法認識。

而唯物主義者認爲能。認識的方法是通過 “實踐 – 理論 - 再實踐” 的無止境的循環。關於這一點,對中國人來說最親民的闡述應該在毛老師的《實踐論》中吧。

電影《黑客帝國》對上述問題進行了形象的解釋。如果人真的生活在 “母體” 中,根本無法判斷我們所在的世界是否真實。我們怎麼知道我們現在不是生活在 “母體” 中呢?我覺得甚囂塵上的 “元宇宙” 是黑客帝國的低配版,所以這東西總讓人感到一絲不安。

好在這些哲學思辨並不會對程序員和領域專家們的生活造成太大影響。我們只需理解《分析模式》中所說的,我們實際上不是對客觀世界本身建模,而是對我們所認識到的東西,也就是說客觀世界在我們心靈中的映像建模(大意,沒查書)。至於我們所認識到的東西和客觀世界(如果有的話)的關係就留給哲學家們討論吧。

不過上述論斷對建模還是有一個細微但重要的影響。例如對一個事件的記錄,常常要記錄兩個時間:一個是錄入計算機的 “登記時間”;另一個是實際的“發生時間”。登記時間是不變的,而發生時間可能改變,這就是因爲從哲學上說,我們並不知道當前所認識的“發生時間” 是否就是客觀上真實的時間。因此總有機會由於對這一時間的新的認識而改變。很多系統就是由於沒有將這兩個時間區分清楚,從而造成一些微妙的問題,例如統計數據的時間口徑不一致,造成數據“對不上”。

值對象與第二信號系統

在 DDD 中有幾個模式總是不太好講,其中之一是 “值對象(Value Objects)”。

值對象的例子倒是好舉。數值(整數、實數等)、數量(包含數值與單位兩個屬性,例如長度)、姓名、字符串、日期、地址、實體的狀態、實體在某時刻的快照等,常常都建模成值對象。

捎帶說一句,由於歷史的原因,有些人會將值對象(VO)與數據傳輸對象(DTO)相混淆。兩者其實是完全不同的概念。

除了舉例說明的方式,從理論上說,值對象和實體的確切區別到底在哪呢?按照《DDD》第 5.3 節的說法,從概念層面,大約有以下幾點:

就這?我不知道大家讀到《DDD》中這些論述的時候是什麼感覺,反正我覺得是 “你不說我還明白,你越說我越糊塗”。爲什麼呀?爲什麼實體和值對象有這些區別呀?直到我從哲學層面進行思考。

實體是我們能夠感受到的客觀存在的外部事物。比如說一個蘋果,人看到,是一個蘋果。狗看到,也是一個蘋果,雖然狗不知道這個東西叫 “蘋果”。從這個角度來說,對實體(至少是自然界中的實體)的直覺認識能力,是人和動物共有的。

值對象則不然。我們從 5 個蘋果、5 只山羊、5 棵樹中,抽象出 “5” 這個概念,用於描述數量。“5”是整數,整數是值對象的類型,“5”是值對象的實例。

我們發明了字母,並約定用字符串 “apple” 來指稱 “蘋果” 這個概念。字符串是值對象的類型,“apple”是這一類型的實例。

從唯物主義的認識論來說,值對象是人類爲了認識和描述事物的屬性,在頭腦中經過抽象思維創造出來的概念。這些概念在自然界中是不存在的。只有智慧生物纔有這種抽象能力。狗的頭腦中不會有 “5”、“apple” 的概念。

無獨有偶,諾貝爾獎得主巴浦洛夫提出了 “第一信號系統” 與“第二信號系統”學說。該學說認爲,大腦皮質最基本的活動是信號活動。根據條件刺激的不同區分爲兩大類:一類是現實的具體的刺激,如聲、光、電、味等刺激,稱爲第一信號;另一類是抽象的刺激,即語言文字,稱爲第二信號。對第一信號發生反應的皮質機能系統,叫第一信號系統,是動物和人共有的。對第二信號發生反應的皮質機能系統,叫第二信號系統,是人類所特有的,是在嬰兒個體發育過程中逐漸形成的。通過第二信號系統的活動,能夠對現實進行概括,出現了抽象思維,並形成概念、進行推理,不斷擴大認識能力。人對世界的認識是通過第一信號系統和第二信號系統的共同作用實現的。

聯繫到 DDD,大體上可以說,對 “實體” 的認識主要通過第一信號系統,而對值對象的認識則通過第二信號系統。這是科學對哲學的佐證。

值對象的本質決定了它在概念上具有不變性和唯一性。

關於不變性,舉例來說,“5”這個值對象如果 “變” 成了 “6”,那就已經是另一個值對象了,“5” 本身是不會改變的。再詳細點說:一個小朋友 5 歲。“小朋友”是實體,“年齡”是這個實體的一個屬性。屬性的類型是整數,屬性的值是 “5”。“整數” 是值對象的類型,“5”是該類型的一個實例。如果小朋友的年齡變成了 6 歲。那麼 “6” 就是另外一個值對象了。這裏發生變化的是 “年齡” 這個屬性(從而實體也發生了變化),而不是 “5” 這個值對象。這就是值對象在概念上的不變性。這裏的不變並不是由於任何外在的約束,而是值對象的本質決定的。“值對象的變化”本身就是一個沒有意義的概念。

《DDD》一書中提到,有些情況下值對象也是會改變的。這種說法其實是混淆了值對象的概念層面(或者說分析層面)和實現層面(或者說設計層面)。正確的說法應該是,在概念層面,值對象總是不變的。但在實現層面,有時考慮到性能的原因,可以將對象的內部實現爲可變的,但對象的外在表現(接口)仍然是不變的。

另一方面,《分析模式》中指出,值對象在概念上總是唯一的。也就是說,“5”這個概念只有一個,全世界沒有兩個 “5”。準確的說” 兩個 5”這種說法同樣是沒有任何意義的。用咱們中國的俗話來說:一筆寫不出兩個 “王” 字。作爲姓氏的 “王” 也是值對象。

雖然在概念上,值對象是唯一的,但在實現上,則可以採用唯一或不唯一的方式。

上面提到的實體例子都是自然界中存在的事物。但是企業應用中卻常常要處理另外一類實體:組織、賬戶、合同等等。這些實體在自然界中是不存在的,而是人類社會生活中產生的,有些是有形的,有些是無形的。他們比自然界中的實體要抽象,又比值對象具體。參考《人類簡史》中 “想象的現實” 這一說法,我們可以稱這類實體爲“想象的實體”。按照《人類簡史》的猜測,正是由於智人(也就是我們)具有這種想象的能力,才使我們戰勝了其他同樣具有智慧的人種(例如尼安德特人),繁衍至今。

最後還有一個實踐中的心得。雖然值對象確實是領域概念,但在研發人員和業務人員共同建模的過程中,我們卻很少和業務人員提值對象,這一般也不會影響建模的效果。而如果強調值對象,反而會將業務人員搞糊塗。反之,我們會問業務人員,“這個信息會不會變化”、“需不需要爲這個信息保存快照” 等等,從而間接地識別值對象。

有些東西人們天天在使用,但如果要抽象到理論上去談,一時又不容易理解。而即使不抽象到理論,也不會影響日常使用。比如說人們天天都會說話,說的話基本上都符合語法,但研究語法學的確實少數人。值對象也是同樣的道理。

關於限界上下文、統一語言、模型演進等的一些心得,且聽下回分解。

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