對結合BDD進行DDD開發的一點思考和整理


引言

二十年前的我,還在學校里抱着一台DIY機(德州486+大眾主板+16M內存+3.5inch軟驅+昆騰320M硬盤,當時全校最快主機沒有之一),揣着一本《Undocumented DOS》,沉迷在Pascal、C以及匯編混合編程的世界里無法自拔。二十年后,軟件開發已遠不是當初幾張簡單流程圖可比,軟件開發的方向由簡至繁,各式的開發工具更是層出不窮,不僅讓新出道的人們深感亂花漸欲迷人眼,也讓我等備感跋涉之不易。所幸愛好不外有二,讀書實中其一。也唯有不斷學習,才不至被拍死在沙灘之上。

學習BDD,實屬偶然。在學習ES+CQRS的過程中,我認識到必須要轉變觀念,把傳統單一結構的領域模型一分為二,將其中反映系統狀態發生變化的部分封裝在寫模型里,而將查詢或呈現的部分封裝在讀模型里,分別設計、分別實現,再以領域事件為紐帶,實現整個業務的最終一致性。因此,發掘領域事件、理解最終一致性成為個中的關鍵。因此,我開始尋求發掘領域事件的方法學支撐。

在搜索引擎幫助下,我很快找到了一些社區和Blog上關於發現和挖掘領域事件的文章,而它們的觀點最終都歸結為了一點:講好故事。講好了故事,才能清楚我們究竟要什么,才能幫助我們划清邊界,才能發現邊界間的聯結關鍵。於是,很自然地,BDD進入了我的視野,然后是Specification by Example,繼而是Impact Map。它們都可以幫助我們把故事講好。

這一篇,先總結一下這段時間的學習成果。之后再爭取學以致用,用一個具體的項目進行DDD+BDD的綜合實踐。

回首過往

既然是要在原有開發方法里摻入新的東西,那就說明現有方法必定有其缺陷,需要加以完善和改進。所以,先說說我們在此之前是怎么做DDD的。

  • 剛開始,我們會有一個很原始、很初級的需求說明,大致說明項目的背景和主要需求。而這樣的需求不外有二,或是改進現有業務流程,或發現新的價值增長點。接下來,是若干次的見面會,通過討論和溝通,從客戶處得到的用戶故事越來越詳盡,業務流程因此得到逐步細化,一些關鍵的概念也得以逐步澄清。於是,用文本描述的角色、用例以及領域概念成為這一階段的主要產品。
  • 然后,大家開始討論如何為整個系統划定核心域和各子域,並嘗試用UML類圖和順序圖建立系統的模型、切分BC。最后,我們開始嘗試用TDD實現系統原型,並借由一些簡單的測試腳本,通過命令行窗口模擬系統的行為,幫助客戶更好地進行反饋。在此之后,由獲取反饋-改進模型-重構實現構成的每個迭代小節,成為我們的主要工作,也使得系統逐漸豐滿和完整。
  • 當應用層接口和系統模型相對穩定后,我們才開始着手UI和持久層的設計實現。在MVP、MVVM模式和ORM工具的幫助下,這個階段通常沒有太大的難度。隨后,是以應用層接口為單位的集成測試和性能分析,期間還會穿插若干次的重構和單元測試,通常這是最惱人的一步——眼看勝利在即,仍只能望梅止渴。接下來,是完整系統的模擬運行,一幫人整天想着法子折騰它,並記下每一次的錯誤和異常,然后又進入新一輪的重構和測試。
  • 最后,大家伙再加把勁,編制用戶手冊、完成代碼和資料存檔、再完成生產環境的部署,就算萬事大吉了。

在整個開發流程里,如果說建模、實現、測試等等,都還在我的控制之中,那么在與客戶交流時,我不知道有多少人象我一樣,時常感到引導話題或者討論方向時那種深深的無力。客戶總會認為你是專家,他說的你都能理解、都能實現,無論其表述是如何的天馬行空。而這種信馬由韁式的討論,也對我划分子域、切分BC帶來了很大的困擾。可能有的人會說,你為什么不每次都擬定一個討論的主題和大致的提綱呢?而我能說的是,嗯,是的,我准備了,可是客戶思維的發散性和跳躍性永遠會給你帶來意外的“驚喜”。另一方面,是伴隨系統模塊逐漸增多后迅速膨脹的各類測試,以及繁瑣的UI測試,給我們的維護與迭代帶來的巨大心理和工作壓力。

所以,我希望有一種方法學的指引,幫助我們更加專注於每次討論的主題,幫助我們更好地發現和切分BC。在張逸的《如何識別Bounded Context》一文中,我找到了方向。在文中,他倡導以領域中的Who-What-Why-When-Where-How為媒,以Actor為驅動,不斷堆砌出系統的關鍵用例,再以對用例的分類划定問題的邊界,最終由此催生不同的BC切分。

這更加深了我對“講好故事、划好邊界”的認識,並由此引導我迅速地轉入了對BDD、Specification by Example的學習。

初識BDD

關於BDD,我在前一篇《行為驅動開發BDD概要》中已經做了一份《BDD in Action》的書摘,並按圖索驥嘗試了.NET平台下的Specflow+NUnit組合。由於有TDD的基礎,所以這個過程的后半實現部分並沒有想象中的困難,反而是在前半分析部分——理清Specification並寫出Feature文件,由於找不准Why-Who-How-What,而讓我犯了不少迷糊。

起初,我的想法很簡單,只要能搭出這個Why-Who-How-What的框架,剩下的工作將只是往里填充內容而已。所以我在使用Impact Map這個腦圖工具時,一開始就拘泥於BDD in Action作者在第三章的闡述,只能教條式地從該書第72頁列舉的4點主題去尋找Why:

  • Increasing revenue 增加收入
  • Reducing costs 減少支出
  • Protecting revenue 保護收益
  • Avoiding future cost 避免將來的開銷

於是,我得到了一張象下面這樣的圖。這顯然與我們的目標相距甚遠,因為這樣的Business Goal並不夠明確具體,不是完全可量化的,也無法直接作為驗收標准。但這並不是說這樣的做法是錯誤的,只能說是仍不夠精確的。因為它對於我們理解用戶需求還是有一定幫助的。

1st IM

而且在這個過程中,如果說找出Why和Who還不算難事,那么要挖出How與What則讓我大費周張。在書中,作者對How的部分引入了Capability的概念,並建議參照Liz Keogh的觀點,以”to be able to”的形式來描述。而對What的部分,作者則引入了Feature的概念,強調這里描述的應該是系統能以何種方式幫助特定角色實現相應的Capability。

於是當我按圖索驥時,卻再一次陷入了教條主義的泥沼,搞不清什么應該放在How、什么又該放在What里。而致使我陷入被動的另一個因素,則是我發覺中英文在表達How與What時語義上存在的混淆。比如”它會怎樣改變?(How should it change?)“與”它會發生什么樣的改變?(What should be the change in it?)”,你能分得清這兩者有什么真正的區別嗎?

盡管我反復閱讀BDD in Action第3章和第4章,但我那可憐的英語閱讀能力並未能幫助我順利擺脫How與What的糾結。

柳暗花明

好在網絡是強大的、牆外的風光總有美好的。在email、twitter以及group的輪番轟炸下,一切開始出現轉機。首先是Julian May向我推薦了Gojko Adzic所著的《Specification By Example》一書,然后是作者Gojko、網友ChrisMatts等人的回復,為我指明了學習的方向。

在此有點題外話,一是BDD in Action著書於Specification by Example之后,其第3、4章的內容已經擷取了后者的精華,卻不知為何沒有使用Gojko所提倡Impact和Deliverable的概念。二是Specification by Example已有中譯本《實例化需求:團隊如何交付正確的軟件》,由人民郵電出版社出版,我的英文書摘亦將另行整理,以作對照。

Specifications by Example為我呈現了一個全新的視角:以Specification、Automation Test以及Living Documentation等文檔為中心的交付模型(Documentation-centric model),以及一個由下圖所示的交付模式。

SbE process

隨着閱讀的不斷深入,Why-Who-How-What被Goal-Actor-Impact-Deliverable所代替,其含義也越發地清晰起來:

  • Why – Goal - Why do you want this product? 為什么會需要這款產品?
  • Who – Actor - Who can help or obstruct us? 誰會幫助或妨礙我們?
  • How – Impact - How will they be impacted? 這會對他們產生什么樣的影響?
  • What – Deliverable - What will the feature do to support the desired impact? 這個功能將如何產生期望中的影響?

之前那種依賴於語言本身對How和What進行的討論,此時便顯得如此拙劣。對此,Gojko有這樣一段精彩的闡述:

But now I advise people not to think about that, instead to think about impacts and deliverables. Deliverables are stuff you do in your zone of control, impacts are how those things influence actors in your sphere of influence. You can call these questions "what/how" or "how/what" or not think about the questions at all, depending on what the group in the room understands better.

它回答了之前一直困擾我的問題。即What是在開發團隊控制范圍內的,能對Actor產生影響的那些事物。這些是我們努力就一定能做到的,比如系統能提供的具體功能。而How則是我們期望的、能對Actor產生的影響。這種影響相對開發團隊而言,是間接和被動的。對此,Gojko用一個手機App進行了形象的比喻:

Impacts - this is your sphere of influence - things you don't control directly, but things you think you can influence. Deliverables - this is your zone of control. The big question to differentiate between the two is: is there an assumption here, or are we guaranteed to achieve it if we decide to do it. Eg if we decide to build an iPhone App, we will. But if we decide to get people to buy the app on the appstore more, regardless of all our efforts that might not happen. The first one is in our zone of control, the second is in the sphere of influence.

期間我對Business Goal也重新進行了認識。BDD in Action曾用Specific(特定的)、Measurable(可量化的)、Achievable(可實現的)、Relevant(相關的)、Time-bound(有期限的)對其進行說明,而發掘Goal的工具真的是如此簡單——不停地問Why……然后,我又重繪了之前的圖:

2nd IM

再補上MelvinPerezCx對Impact Map與各種產出件之間關系的一張好圖:

psb

繼續前進

解決了Impact Map的問題,下一步就是思考如何將DDD的戰略思考與BDD的戰術實現結合起來,引導整個開發的方向。這一切當然地需要用實踐來解決,在前進中才能解決遇到的問題,所以暫且只得到以下的一個思路。

DDD+BDD的開發流程:

  1. 項目伊始,使用Impact Map勾勒出系統的功能輪廓。
  2. 找出Key Example,編寫相應的Specification及Feature文本。
  3. 對領域進行划分,將Specification划入不同子域。
  4. 統一Specification中的概念,建立特定子域內的通用語言。
  5. 結合Specification的描述和子域的划分,定義最初的BC結構。
  6. 選擇核心BC,利用SpecFlow+NUnit等工具,用代碼實現Specification描述的Feature。
  7. 通過捕捉Feature描述中的Given-When-Then,得到領域事件列表。
  8. 逐個BC重復第6、7兩步,得到領域模型的原型。
  9. 不斷審視和完善Specification,中間可能夾雜着BC的調整,使領域模型不斷演進和豐滿。
  10. 利用SpecLog等工具生成領域模型的Living Documentation。
  11. 通過自動化測試,審視業務流程是否達到預期。
  12. 從領域模型由內而外,編寫應用接口層、UI層和基礎設施層的Specification。
  13. 使用BDD工具,實現這些外圍Specification的自動化測試和文檔生成。
  14. 經過若干次迭代,所有核心和外圍Specification都得到完美實現。
  15. 系統通過集成測試和試運行,順利交付並完成歸檔。

對這個流程中,有幾點需要說明:

  • 第3、第5步,對子域的划分和BC的切分是難點,因為兩者並不一定是一一對應的關系。只是因為Specification的出現,可以讓我們用更具體的示例來進行這種划分,而不是憑空地臆想。
  • 第6、第7步,在實現Specification的自動化測試時,應高度關注各種領域事件及其觸發的結果,還要從saga的角度去思考這些事件之間的協調配合。這在CQRS Journey一書中有相應的例子可作參考。
  • 第6、第7、第8步,在編碼的同時配合不斷完善的UML類圖和順序圖,應該能更好地反映系統全貌,或者幫助我們發現遺漏。
  • 第12、第13步,UI的自動化測試一直是難點,也是我的弱項。盡管Specification by Example中有對UI自動化測試的專門闡述,但仍需要實踐才能真正變成自己的東西。
  • 在任何時候,專注核心域都是要放在第一位的。包括Specification的編寫和Example的選擇,都要以此為原則,才能避免陷入實現細節的泥沼。

寫在最后

這是我近期學習的一些收獲,不僅思考很不成熟,亟待實踐的檢驗,而且文中也難免有個人認識上的謬誤之處,還請各位看官批評指正。另一方面,由於BDD是一種戰術意義的Development方法學,需要時間去掌握相關的一些工具,所以我會嘗試用一個相對簡單的項目練手,屆時再來補充完善此篇所述之內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM