慶祝 Eric Evan 提出領域驅動設計15週年。

在這篇文章中,我想分享兩件事情。第一件事,是 Eric Evans 為軟體開發做出的卓越貢獻。第二件事,是想提出一些會引起探究和加強的問題。但在此之前,提供一些上下文是有幫助的。

說點歷史

曾幾何時,電腦還是激動人心的新興事物。在《誠如所思》(As We May Think)一文中,Vannevar Bush 構想出一種叫做Memex的機器,這種類似超文本設備的機器能夠增強研究和思考。 Douglas C. Engelbart寫了《增強人類智力》(Augmenting Human Intellect)。 Alan Kay 提出了供孩子使用的個人電腦 Dynabook 的想法。喬布斯(年輕時)有一個「思維的自行車」的願景[1]

今天,電腦變得無處不在,神祕莫測。之前所有的期盼變成什麼樣了?理想中的烏託邦,是怎麼就變幻成了以 Facebook 為代表的充斥著廣告的反烏託邦(就如同 Frederic Pohl 和 C.M. Kornbluth 在1955年前後出版的科幻小說《太空商人》中所描繪的那樣)?

軟體也曾是有價值的新事物。用數周時間構思、實現和交付5000行以內的 COBOL 程序,能幫助公司消減幾十萬美元的成本,或者為公司創造幾十萬美元的收益。漸漸地,規模更大的軟體系統,創造了價值數百萬美元的戰略優勢。由於軟體自動完成了工作中最無聊和平凡的部分,工作人員得以將人類固有的優勢發揮到最大程度,從而改善了工作。

今天,軟體無所不在,且成本高昂。規模巨大、錯綜複雜和令人費解的軟體,抑制了商業的敏捷和創新。每年,數以萬億的美元花在了軟體這種商品上。但是,這份花銷的90%都用來維護遺留代碼了。人們越來越覺得工作是在降低他們的人格,因為他們已被簡化為」機器零件「,不能自由思考,無法做出獨立於計算機之外的行為。

討論反烏託邦的未來是如何取代烏託邦的所有原因,已經超出了本文的範圍。但討論軟體和軟體開發是如何走下神壇的,則更容易處理一些。

讓我們從世界上第一臺商業計算機 LEO I 開始。

1951年,英國領先的餐飲和食品製造公司 J. Lyons and Company 構建了 LEO(Lyons Electronic Office)系統。同一個團隊構造了計算機硬體,編寫了系統軟體,還編寫了一系列應用程序,包括工資、訂單錄入、庫存控制、生產排期和管理報告等。這臺計算機,甚至還被用來配置定製的混合茶——這是一個初步的「專家系統」。

在建設 LEO 的過程中,做出了一大批沒有經過驗證的假設。這些假設給軟體開發帶來了很多問題,其中的一些問題直到今天依然困擾著我們:

  • 黑盒編程。編程是完全按照計算機工作的方式,即機器工作的方式定義的,將一組精確定義的輸入,錄入計算機這個黑盒,然後得到一組精確定義的輸出。程序員必須預想黑盒中所有的狀態和狀態變遷,也就是說確保輸入和輸出的正確關聯關係。幾十年後, Fred Brooks 在《沒有銀彈》一文裏認為,這種在智力上跟蹤所有狀態和變遷的挑戰,正是軟體開發的本質問題。
  • 一體化的單體系統。考慮到只有一臺計算機可用,所以單體系統是難以避免的。
  • 集中的分層控制。其縮影就是統一的「程序結構圖」(Program Structure Chart),其中會有一個單獨的主控模塊,來管理下屬的輸入、轉換和輸出模塊。

其中最過分的(雖然也可以理解),是與領域有關的假設。領域被認為在本質上與計算機沒有什麼區別。也就是說,公文處理領域,被認為與計算機一樣,都是確定性很強的機械繫統。對於一個純粹的公文處理領域來說,這個假設還是合理的。在接下來的15到20年的「數據處理」時代,幾乎沒有軟體開發工作挑戰這個假設。

幾乎沒有人對領域進行深入思考,來揭示這些真實世界裡的子集(例如業務或業務流程)。直到70年代初,當數據處理系統時代讓位於管理信息系統時代,人們才開始關注領域。

在70年代初,「結構化分析和設計」(SAD,Structured Analysis and Design)的理念,主導了軟體開發的實踐。SAD倡導:

  • 第1步:對領域建模
  • 第2步:確定需要哪些變化來增強或者矯正領域中的問題
  • 第3步:通過頭腦風暴,找到讓變化產生的多種方法
  • 第4步:分析出哪個是最佳解決方案
  • 第5步:對選擇出的解決方案進行建模——包括軟體的模塊化
  • 第6步:實現模型
  • 第7步:交付,並對實現的解決方案以及它對領域的影響進行評估

但人們很少實際執行從第1到第4步。部分原因是由於,業務管理者沒能看到其中的價值。因此這4步被看作是「浪費時間」,而不被鼓勵,甚至被禁止。

而「軟體工程」,是讓領域在軟體開發中消亡的最重要因素。1968年,人們定義了這個新專業和新學科。就如同結構工程被應用於數學和物理一樣,軟體工程被應用於計算機科學。

專業的軟體工程師,需要知道計算機的各個方面。而編程只不過是一門應用科學,僅僅是「演算法加數據結構」(Dijkstra對編程的著名定義)。

專業的軟體工程師,從一組「完整且明確」的需求開始著手工作,然後構建一個可以證明能滿足這些需求的軟體。軟體工程師運用的所有工具和技術,都聚焦於促進程序員理解計算機內部是如何工作的,以及在計算機的上下文中,如何構建軟體模塊,並將其關聯起來。

1970年中期,領域消亡了。

蟄伏

然而,領域仍然活躍在主流之外。

1985年,Peter Naur闡釋了領域對於軟體開發而言至關重要的原因和機制。Naur駁斥了當時盛行的「軟體工程產品模型」,他堅持認為開發者需要投入到軟體開發理論的構建過程中,而這個理論是指「真實世界中的事務及軟體該如何處理和支持它」(黑體字體是本文作者強調的)。

人們在20世紀80,90年代,急於使用面向對象的編程方法。然而在這期間,「對象思維」的關鍵點卻被遺忘。在面向對象編程裏,對象僅僅是動態的數據結構,就是一種將程序代碼進行模塊化的方法。然而,「對象思維」的關鍵點,卻是以「職責」為標準,理解領域,將領域模塊化,創建通用實體和語言,從而將領域模塊映射到軟體模塊上。

與Kent Beck和極限編程風格一致的用戶故事,則是另外一種將「領域」帶回到開發過程中的嘗試。「駐場客戶」和「探索、迭代、開發」的實踐,將「領域」帶到軟體開發核心的位置,並提供能支持上述Peter Naur的軟體開發理論構建的實踐和原則。

「超大規模系統」和「複雜適應性系統」所帶來的挑戰,在過去10年中佔據了軟體開發的中心位置。隨著我們對這些系統理解的深入,人們越來越相信,自然系統,即「領域」,與計算機加軟體的這種簡單確定性系統相比,在本質上是完全不同的。所以,當年構建LEO系統所基於的「領域與計算機一樣具有確定性和簡單性」這個假設,就是個錯誤。

這些領域重要性的倡導者,似乎被主流開發者所忽視。他們雖然做出了嘗試,但是下面這些工作方式阻礙了領域進入主流——只有業務分析師纔可以與用戶對話,而程序員卻只能與系統架構師對話。

當時主流的看法是,對於軟體工程而言,領域當然是無關緊要的。用戶提供規格和需求,開發者編程來滿足需求。這就完事兒了。

破曉

翻開Eric Evans的《領域驅動設計》,我的第一印象是驚喜。竟然有人真的在關注領域,並且意識到了蟄伏中的「領域」。

書中Martin Fowler的推薦和Eric的敘述清晰地闡釋,健壯的領域模型對任何軟體項目的成功都至關重要。他們甚至斷定,在軟體開發領域已經存在一個完備的社區,這個社區理解領域知識和領域模型的關鍵價值。

那麼該如何構思和構建模型呢?

在書中,Eric繼續向我們描述了大量原則、實踐、技術和工具,以實現這一目標。相信本文的讀者已經熟悉原書的內容以及DDD的後續發展,因此不再贅述。

DDD的神奇之處並不在於模式或實踐,而是在於它不動聲色地戳穿了軟體工程的基石其實是一個謊言——開發者不需要理解什麼業務領域,而只需要知道一些需求,然後用代碼來滿足。

在《領域驅動設計》正在撰寫的時期,軟體開發組織(特別是大型的)的主流趨勢,是建立和加強溝通「筒倉」。其中,用戶或領域專家只能與業務分析師溝通,業務分析師只能與架構師溝通,架構師只能與系統分析師和資料庫管理員溝通,系統分析師和資料庫管理員只能與設計師溝通,而設計師只能與測試人員溝通。

程序員,只能從設計師那裡單向接收規格說明書,只能從測試人員那裡單向接收缺陷報告。

在2004年之前,管理層顯然並不允許開展敏捷軟體開發中的一個不可或缺的重要實踐——領域專家、用戶或駐場客戶,使用自己的用語來編寫的用戶故事。而開發團隊與領域專家(含「用戶」)這兩者是隔離的。兩者之間只能通過「產品負責人」間接溝通,並且經過了過濾。

Eric不僅挑戰了關於領域的主流觀點,而且準確說明瞭為什麼理解領域,對於軟體開發的實施成功至關重要。

他的第三個貢獻,是填補了迭代式、增量式和敏捷軟體開發方式中存在的一些「設計空白」。例如,極限編程為構建和使用領域模型提供了明確的準則,即只有代碼(如Eric所述)才能作為唯一的實現模型。Kent Beck和極限編程的早期實踐者們,都已經是設計專家,擁有深厚的隱性知識,來將用戶故事轉換為代碼。但他們從未以任何方式闡明這種「魔法」的本質,來讓那些不太熟練的開發人員能夠效仿。

Eric提供了一些知識「中間件」以解決這個問題。

DDD和Eric的貢獻(無論是原著和撰寫的論文,還是他對論壇和會議的積極參與)的重要性,都不容低估。

讚賞他的工作之餘,我也在思考一些問題。在阿姆斯特丹的DDD Europe大會上,我有機會和Eric及其他DDD專家探討了一些問題。我相信並希望,探索這類問題會讓DDD的進一步強化碩果累累。

建模

Eric(在書中的第一節前幾頁中)和Martin Fowler(在書中的序言部分),闡述了領域模型和實施模型,以及保持這兩種模型一致的必要性。這是一個重要的觀察結論。

之前領域在軟體開發中的三次受挫(第一次關於對象,第二次關於敏捷),恰好說明瞭這個觀察有多重要。

對於採用職責驅動對象設計的項目,領域模型就是一組CRC(Class、Responsibility、 Collaborator,即類、職責、協作者)卡片[2]。創建一組卡片時,會花費大量精力。這些卡片可以用於口頭說明一系列的對象之間交互的故事,它們要達到的是同一個目標。要小心翼翼地確保,每張卡片僅列出適合於相關對象的那些職責,且職責會以最佳的方式被分配到一組對象中。

然而之後呢?該如何為一張3乘5英寸的卡片上的一組自然世界的領域職責進行編程呢?例如,一張Person卡片上的「年齡」對應的代碼:(SystemClock today – self DOB) asInteger。在自己的工作中,我發明並使用了「對象立方體」。它擴展了CRC卡片,包括了所需的知識、協議和事件部分。所有這些元素都是從領域的角度創建的。將這些元素添加到領域模型中,可以幫助程序員直接編寫代碼來實現對象。

當Kent Beck(和Ward Cunningham)為人們帶來極限編程時,領域模型就是用戶故事,或者更準確地說是產品待辦列表中故事的集合。而代碼則是實現模型。他們從未解釋過用戶故事是如何過渡到代碼的。當管理層既不允許客戶駐場,又不允許領域專家來編寫用戶故事,那麼糟糕的結果就可想而知。

從典型的軟體工程的視角來看,領域模型是一組廣泛的「需求」。這些實現模型(如UML倡導的那些),是通過與領域的實際模型的脆弱(如果有的話)連接而生成的。軟體編寫的目的就是為了「滿足需求」。然而,大量的外包和離岸軟體,即使能表明(甚至證明)自身已「滿足需求」,但仍然無法投入使用。這就說明以「滿足需求」為目的是多麼的失敗。(這並不是要挑外包或離岸交付的刺兒,但相比遠在他鄉開發的軟體,在公司內部開發的項目,會有很多優勢,繞過障礙,以尋求對領域更充分和更具語義性的理解。)

對於這個問題,Eric在書中或明或暗地給出了答案——通過讓開發人員及開發團隊與領域專家合作,一起創建領域模型。

這種方法,讓人聯想到兩個有趣且相互關聯的問題。

首先,領域模型真的是一個「領域模型」嗎?或者,換句話說,如果沒有開發人員的影響和指導,領域專家會創建出相同的,或者在大體上相同的領域模型嗎?

我讀過很多商業書籍。其中大多數都會提出一些模型,來促進對業務領域的理解。但除了PERT圖表之外,這些模型看起來一點都不像標準UML模型。

與之相關的問題是,以開發人員的視角構建出來的領域模型是否實用,Eric聲稱,這關乎開發團隊能否取得持續成功。這種模型對於領域專家是否同樣有價值,讓他們增進對領域及其運作和演化的理解?或者,正如我所認為的那樣,當領域專家不與軟體團隊合作時,他們會使用其他對他們來說更熟悉的模型嗎?

這又引出了另一個問題:任何正式或半正式的模型,都可以「抓住」自然領域的真正複雜性嗎(不考慮內部系統領域,如1951年的LEO系統那樣的設備驅動程序和純粹的公文、數字運算以及早期的工資單和訂單錄入)?

自然領域通常是複雜的,高度動態的,模糊的和自相矛盾的。實現系統可能是紛亂且繁雜的,但並不一定複雜。實現系統必然是明確、精確和可量化的。那麼,任何半正式的建模過程,僅僅基於開發人員的視角和世界觀,是否就足夠了呢?

這些問題都不是對Eric在書中所提出的那種模型的質疑,也不是想要抹殺他對軟體開發所做出的巨大貢獻。

相反,這些問題應該促使我們思考——是否可以通過使用用戶故事和可視化的手段(像符號化的西藏曼荼羅圖騰、隱喻和一般系統論的簡單系統模型),從軟體開發的視角來加強領域建模?

語言

如前一節所述,DDD堅持在領域模型和實現模型之間保持一致性。源代碼難道不是實現模型嗎?也許是終極實現模型。正如Eric指出,在大多數敏捷方法中,源碼就是唯一的實現模型。

Eric還提出了一個重要觀點——團隊應該共享同一種語言。這意味著領域專家和開發人員要使用同一種語言,即擁有不同專業術語的測試人員和DBA之間,Java程序員和JavaScript程序員之間,框架專家和Maven工具使用者之間,都應該以某種重要的方式,統一起來,共同使用嵌入在領域模型和實現模型中的共同語言。

「共同語言」不僅涵蓋可視化模型,是否也要包括源代碼本身?Eric在書中並沒有提出這個問題。該書出版以後,我也不知道這個問題是否在討論區、論壇和博客中被討論過。

我個人對這個問題的答案是肯定的!

職業生涯早期,我使用COBOL語言編寫應用程序(用彙編語言編寫系統級程序)。那時,我們的領域專家——銀行家們常常坐在我們身旁參與「代碼評審」。在上線之前,我們的領域專家——銀行審計師們也會閱讀和審查代碼來檢測或防止欺詐。COBOL語言很容易做到這一點。

幾十年後,我改用Smalltalk寫代碼,而領域專家們同樣可以閱讀和評論代碼,有時甚至可以改進代碼。雖然領域專家很少接觸類庫中的大型組件,也從未接觸過C語言所實現的少量核心代碼,但是他們確實要瀏覽對象的實現代碼,以及對象交互所使用的代碼(一般存放在工作空間中),以確保應用程序實現了目標。

下面這樣的例子不勝枚舉。一位領域專家(大多時候作為一個旁觀者),當他看到「不理解」這樣的錯誤信息時,就會揮舞著手中的CRC卡片告訴程序員:「等等,我們在發送這個消息前,應該要說清楚協作關係。如果沒有協作關係,就沒有合適的對象來接收這個消息。」

Smalltalk被明確設計為能在人類和計算機之間共享的語言——這讓領域專家們可以表達出他們的需求,而不必考慮計算機內部是如何滿足這些需求的。在成為Simula I和編程語言之前,Simula也有著相同的目標,那就是讓領域專家「討論」程序「要做什麼」和「為什麼這麼做」,而不必關心「怎麼做」。

我這樣說,並不是建議採用任何形式的「最終用戶編程」。因為要想成功地開發出軟體,很多「怎麼做」的知識是必不可少的。但是,我強烈建議將源碼作為實現模型的一部分。而且編程語言的很大部分,應該由領域專家和開發人員之間所共享的語言來組成。

COBOL和Smalltalk(以及更偏科學領域的FORTRAN)這樣的編程語言,顯然可以做到這一點,但我可以直截了當地斷言,C、C++、LISP這樣的編程語言,或任何函數編程語言,都做不到這一點。Java也未必能做得到。

軟體的核心

「軟體的核心,是能為用戶解決領域相關問題的能力。而軟體其他的一些特性,儘管也許是必須的,但也是要用來支持這個基本目的。」——Eric Evans。

這個簡短陳述的中心,是傳統主流的軟體工程所面臨的另一個考驗。這是Eric挑戰普遍觀唸的另一個例子。

在半個世紀以來實踐、觀察和教授軟體開發的漫漫長路上,我經歷過太多的軟體開發方法、方法論以及理論,它們不斷地強調軟體解決領域問題的重要性,這些方法多到兩隻手都數不過來。

驗收及可用性測試,還有用戶體驗(UX,User Experience)設計,可以被看作是對軟體的核心的間接度量。且此事僅由它們來完成。(我們可以在另外的場合和時間來討論這個問題。)

我完全同意Eric的說法,和這些說法背後隱含的前提:會出現問題。

我們的領域模型是否應該繼續延伸,以將導致問題產生的力場和約束納入其中?如果這些沒有被納入其中,我們如何來區分一個普通的解決方案和一個最優的解決方案?這個問題是否很重要?

更進一步推理:如果不瞭解當前領域中的力場,那麼是否有辦法預測領域系統會因為新的軟體工件的引入,而產生哪些有利和不利的反應?這個問題是否也很重要?

作為一名人類學家,我廣泛研究了「技術和文化變遷」。文化是一個複雜的系統,業務也是如此,我們為之開發軟體的無數領域也是如此。

在這種系統中引入任何變化,都會打破系統的平衡。而系統為了建立新的狀態,會以不可預測的方式產生變化。來看一些文化變遷的例子:陶罐的發明,大大地促進了人類從狩獵採集社會到村落的轉別;而汽車的產生,帶來的是星羅棋佈的城市和性解放。

還有前面討論的歷史,LEO系統完全改變了商業世界,最終導致了大規模數據存儲的崛起,隨之而來的是對隱私的關注以及細分市場。

當然,我們不能也不希望領域模型的範圍太大,大到能夠將要引入軟體的整個自然系統的細枝末節,都描述得一清二楚。但是,我個人的信念是,對精心設計的解決方案如何影響領域中某些元素的理解,必須包含在領域模型之中——對人的影響尤其是重中之重。

軟體可以很好地「為用戶解決與領域有關的問題」,但用戶又要付出什麼樣的代價呢?

用戶會被解決方案完全取代嗎?記得有一段時間,新軟體的開發成本是否合理,是根據軟體實施後可以解僱的工人數量來判斷的。

又或者,用戶會因為軟體而感覺「生不如死」嗎?比如,機場登機口服務員,可能要為250名因航班取而消怒不可遏的乘客重新安排航班。而這源自那個必須使用的「能讓工作更輕鬆愉悅的」軟體所「幫的忙」。

這些問題可以被提煉成一個通用的問題,「倫理、道德以及對人類的尊重,要求領域模型延伸到多大的範圍,使得在其中看不到軟體開發本身的影子?」

挑戰

15年前,Eric將一系列相互關聯的軟體開發問題,以及這些問題的解決方法,在同一個理念下彙集成書。它們都強調將瞭解領域和領域建模放在首位。

這本書與Eric所做的整理工作,應該被視為一項意義非凡的成就,也應該被視為一種挑戰。

積極運用DDD的社區,一定能超越2004年Eric在書中所倡導的思想和實踐的簡單應用,與Eric和其他人一道,並肩前進,去加強,去拓展,在前人為我們所鋪就的堅實基礎上,繼續發展。

作者:David West教授

簡介:戴維·韋斯特於1968年開始了他作為計算機程序員的編程生涯,同年,「軟體工程」成為一門職業。從那時起,戴維教授就開始從事計算機領域的每一次運動和革命,包括:結構化開發方法,資料庫及數據驅動開發方法,面向對象開發方法,敏捷開發方法,設計模式,領域驅動設計和設計思維。作為一名專業開發人員,同時也是知名學者,戴維教授不斷追求職業生涯的突破。如今,他為尋求變革性改變的團隊和組織提供指導,培訓以及諮詢服務。

Prof. David West

譯者:ThoughtWorks諮詢師 王巖

校審:ThoughtWorks諮詢師 黃雨青、覃宇、伍斌

參考

  1. ^【1】即電腦能像自行車那樣,幫助人類在信息的道路上輕鬆前行,就像鷹憑藉一雙翅膀,能在空中自由翱翔一樣——譯者注
  2. ^【2】CRC(Class-Responsibility-Collaborator) 卡建模是一種簡單且有效的面向對象的分析技術。它由三部分組成:類(Class)、職責(Responsibility)和協作(Collaborator)。 http://www.uml.org.cn/RequirementProject/20044641.htm

推薦閱讀:

相關文章