l

2020年2月26日 星期三

【還少一本書】Clean Agile

Feb. 26 15:00~16:54


Clean Agile

Clean Agile: Back to Basics》是Robert C. Martin(Uncle Bob)最新著作,這本書算是他老人家對於敏捷開發的歷史回顧,以及他對於敏捷開發應該長成什麼樣子的個人意見

***

第1章

第1章Introduction to Agile談到談到敏捷的歷史,從1880年代的Scientific Management(科學管理)演變到Waterfall,再到1980年代末期~1990年代早期的敏捷改革時期,Uncle Bob提到當時多個輕量級軟體開發方法的興起,到2001年17位輕量級流程愛好者在美國猶他州雪鳥滑雪聖地的聚會,誕生了敏捷方法與敏捷宣言

第1章最後提到Ron Jeffries所畫的The Circle of Life這張圖,它表達了XP的12個實務做法,《Clean Agile: Back to Basics》這本書的核心基本上是圍繞著The Circle of Life,由外而內分成三章解釋這張圖。

讀完《Clean Agile: Back to Basics》之後,Teddy覺得Uncle Bob所謂的Clean Agile底子裡其實就是XP,這很可能是因為Uncle Bob一開始接觸的敏捷方法就是XP,而且他是個TDD堅定信仰者的關係。

▲The Circle of Life,節錄自《Clean Agile: Back to Basics

***

第2章

第2章The Reason for Agile,對Uncle Bob來說,理由很簡單:「讓自己以及軟體開發這個行業變得更專業」。如果你夠專業,你就有勇氣可以拒絕長官不合理的要求,你敢說No。你軟體會變軟,你會持續改善與學習,且無懼改變。你的軟體品質會很好,QA應該找不到任何bug。客戶與開發人員各遵其職,相互合作。基本上算是一種軟體開發的大同世界XD。

***

第3~5章

接下來的三章分別介紹The Circle of Life的三個圈圈:

  • 第3章Business Practices:介紹Planning Game、Small Releases、Acceptance Tests、Whole Team這幾個實務做法。本章一開始簡短的介紹Story和Story Point,以及軟體估算的幾種方法。然後提到Velocity、小規模釋出、驗收測試以及敏捷團隊的組成。接觸過XP或Scrum的朋友讀起來應該沒什麼困難。
  • 第4章Team Practices:這一章提到Metaphor、Continuous Integration、Collective Ownership、Sustainable Pace,除了Metaphor以外其餘三個實務做法都很容易理解。Metaphor在XP剛提出的時候其實很抽象,不好解釋。但後來有了Domain-Driven  Design(領域驅動設計;DDD)之後,Metaphor就有了一個完美的解釋方式,就是DDD裡面的Ubiquitous Language(通用語言)。
  • 第5章Technical Practices:這一章提到Simple Design、Pairing、Test Driven Development、Refactoring,算是軟體開發人員比較熟悉的內容。

***

第6章

第6章Becoming Agile,這一章原本是最吸引Teddy的章節。如何變得敏捷?Uncle Bob又回頭偷Kent Beck的四個XP價值觀:

  • Courage
  • Communication
  • Feedback
  • Simplicity

這章還有三點Teddy覺得很有趣的部份

  • Transformation:敏捷轉型是這幾年很熱門的話題,作者認為˙(大型)組織的敏捷轉型很多都是失敗收場,因為「中間管理層」所存在的目的與敏捷精神相違背,因此他建議「產生新的組織來實施敏捷而非將現有組織轉型」。
  • Coaching:近幾年敏捷圈很流行的Coaching(敏捷教練),Uncle Bob的看法異於常人,他覺得敏捷團隊不需要,或是只有偶爾需要聘請教練。
  • Agile in the Large:Uncle Bob認為敏捷就是為了解決中小型軟體開發團隊所誕生出來的方法,所以根本沒有所謂「大規模敏捷」的問題。因為大規模團隊合作的問題在5000年前已經被解決了。

***

第7章

第7章Craftsmanship,本章不是由Uncle Bob執筆,而是「外包」給寫了《The Software Craftsman: Professionalism, Pragmatism, Pride》的作者Sandro Mancuso,寫得出乎Teddy意料之外的好。

Software Craftsmanship Manifesto(軟體工藝宣言)在2009年提出,Teddy之前一直覺得既然已經有敏捷宣言了,為什麼要畫蛇添足?是要另立山頭佔領地盤嗎?

廣義來看它可視為一種對於敏捷宣言的補充說明,身為一位軟體工程師,Software Craftsmanship Manifesto 的內容原本就是自己一直以來所重視與實踐的方向,落實敏捷不就是這樣嗎?!

還真的不是,敏捷經過這麼多年的演化,即使只將敏捷限定在軟體開發組織中,很多時候還是偏向A Name Without Quality,變成一種口號與行銷工具。所以這章重回敏捷軟體開發的初心,軟體工藝,也算是呼應了Uncle Bob在第二章所提到的專業

***

結論

這本書提到很多敏捷這個詞被提出前後的歷史故事,以及重新詮釋XP的12的實務做法與4個價值。雖然書中對於Clean Agile的看法可能與現今主流趨勢相差甚遠,但這應該是作者有意為之的結果。從正面來看,可以讚賞作者不忘初心,方得始終。從負面來可,可以批評他食古不化,沒有與時俱進。

看完《Clean Agile: Back to Basics》,Teddy立馬買了《The Software Craftsman: Professionalism, Pragmatism, Pride》,且興起把當年沒看懂的XP系列書籍拿出來再看一次的衝動。

▲年輕時購買的XP系列叢書,現在回頭讀應該比較看得懂吧XD。

***

友藏內心獨白:是初心不是粗心。

2020年2月20日 星期四

領域事件幫你切割使用案例

Feb. 20 13:45~14:36


售後服務

今天上午有一位剛上過【領域驅動設計與簡潔架構入門實作班】的學員B問Teddy一個關於如何切割使用案例(Use Case)的問題。為了不洩漏學員B的工作內容,以下的文章中Teddy將學員B的應用領域轉成開發看板系統,將他提出的問題改寫,再跟鄉民們介紹。

***

問題敘述

使用者想要列印看板系統中的卡片(Card),使用者可以選擇列印全部的卡片,或是只列印挑選過的若干卡片。開發團隊想到兩種可能的實作方式:

  • 方法一:實作一個PrintCardsUseCase使用案例,它接受兩個參數—boardId和filters。如果filters是空的,就列印該看板的全部的卡片;如果filters有資料,則先依據filters條件過濾不需要的卡片,再列印符合條件的卡片
  • 方法二:方法一需要在使用案例中加上一個if判斷,感覺不太好。因此衍生另一種想法,把使用案例拆成兩個—PrintAllCardsUseCase和PrintCardsByFilterUseCase,前者只需要boardId這個參數,後者則需要boardId與filters這兩個參數。

寶傑,你怎麼看?!

***

用領域事件思考

學員B遇到的問題很常見,Teddy年輕的時候學習物件導向分析與設計(OOAD)也遇過同樣的問題。學了領域驅動設計和Event Storming之後,這個問題的解決方式就變得很簡單。回到事件風暴(Event Storming)來思考:

  • 先寫出領域事件(Domain Event)


  • 幫每個領域事件加上Command


  • 幫Command寫上Input

分析到這裡就很清楚了(先不用管Aggregate與Policy),使用者的確需要兩個使用案例,但不是原本方法二的PrintAllCardsUseCase和PrintCardsByFilterUseCase這種切割方式,而是:

  • FilterCardUseCase
  • PrintCardUseCase

使用Event Storming,依據時間軸來思考領域事件,再寫出相對應的Command,系統的行為就會慢慢浮現出來。

***

友藏內心獨白:距離上市集資也就跨進一大步了。

2020年2月11日 星期二

領域專家X

Feb. 11 17:33~19:11

▲立志成為喵星人的領域專家


前言

無論是行為驅動開發(Behavior-Driven Development;BDD)實例化規格(Specification By Example;SBE)或是領域驅動設計(Domain-Driven Design;DDD),共同存在一個非常重要的假設:「正確的需求可以藉由領域專家與開發團隊密切的迭代溝通所產生」。

在這個假設之下,領域專家彷彿「神一般的存在」,他的腦袋中似乎對所有問題都有答案,對於團隊的提問,他不會說:「都可以,由團隊決定」。

可能是領域專家知道的太多,他無法將所有事情主動告訴開發團隊,需要藉由團隊成員與他的互動,將完整的需求慢慢誘導出來。

這其實並不是什麼新觀念,傳統的軟體開發方法也都持相同看法,只不過BDD/SBE與DDD,特別是BDD/SBE這一系列的方法,特別強調此觀點。

***

落實BDD/SBE的困難

讀過BDD/SBE書籍的鄉民一定會發現,許多書本都會以一個例子來示範如何學習這些方法,例如停車費計算、搭乘巴士或捷運的車資計算、包裹運費計算。相對於一般的商業軟體,這些例子除了功能較少以外(系統較小),還有一個共同特色,就是有著非常明確的商業邏輯

因為有著非常明確的商業邏輯,領域專家的角色就可以被簡化,也容易透過領域專家與開發人員之間的對話來釐清「規格」,進一步採取「舉例子」的方式來描述這些規格。作為學習目的,這樣的設計非常合理,並無不妥。

落實BDD/SBE的困難之一在於一個完整的系統除了「具備明確商業邏輯」的部分以外,也有許多功能的商業邏輯不是哪麼明確。又或者是這些邏輯不屬於核心商業邏輯(core business logic), 而是應用程式邏輯 (application logic)。換句話說,領域專家也許對於核心商業邏輯比較清楚,但不一定對於應用程式邏輯有特別的意見。這時候通常需要由開發團隊來主導,再與領域專家確認。這個部分的系統實作,在BDD/SBE的書籍討論的比較少,因為這原本就不是這系列方法所著重的焦點。

舉個例子,以看板系統(Kanban System)為例,它本身有一些相對明確的核心商業邏輯,例如看板核心三原則:

  • 視覺化
  • 限制WIP(work in progress/process)
  • 管理流

根據這三個原則可以舉出好幾的實例,這些都是領域專家比較熟悉的核心商業邏輯:

  • 工作流視覺化實例,包含垂直工作流、水平工作流(swim lane)。
  • 卡片(Card)視覺化:包含標準卡片、固定交期卡片、加急卡片、Bug修正卡片、技術工作卡片。
  • 工作流加上WIP限制之後,卡片數量符合與違反WIP限制的例子。
  • 阻礙(Blocker)的表達方式。
  • Lead Time、Cycle Time的定義與量測方法。

真正實作看板系統的時候,有一些不屬於核心商業邏輯的應用程式邏輯會跑出來,例如:

  • 一個使用者可以擁有多少個看板?
  • 不同的使用者是否可以共用看板?
  • 使用者要如何設計自己的看板?從頭開始設計,還是可以套用現成的模板(Template)?
  • 一個看板只能表達一種工作流程,還是可以同時表達多個工作流程,以便於讓多個團隊共用看板?
  • 已完成的卡片(當累積很多之後)要如何「收納」才不會阻礙使用者使用看板?
  • 如何表達看板的開始與結束,以便於計算lead time?

以上總總問題,雖然也屬於開發看板系統需要釐清的邏輯,但並不屬於核心商業邏輯,因為不同的看板系統,可以有不同的決定。例如,路人甲所開發的Simple Kanban系統決定一個看板只能有一個工作流程,而路人乙所開發的myKanban系統則是支援一個看板可以有多個工作流程。

***

落實BDD/SBE的困難

相較於BDD/SBE,DDD因為要探討領域模型(domain model)、通用語言(ubiquitous language)、bounded context、context map以及aggregate、entity、value object、repository、service、factory等DDD設計模式,因此需要一個相對比較完整的系統開發作為例子,例如《Implementing Domain-Driven Design》就以開發敏捷專案管理系統當作例子。

落實DDD的困難之一在於如何找到一個「合適且完整的系統當作例子?例子的問題領域很重要,因為學習者通常是軟體工程師,如果問題領域過於專業,例如健保系統、保險理賠系統、進銷存系統,軟體工程師很難在學習過程中弄清楚這些系統的核心商業邏輯與應用程式邏輯。如果例子太小,又失去套用DDD的意義。

為了解決缺少領域專家的問題,《Implementing Domain-Driven Design》作者選用了敏捷專案管理系統作為例子。一方面這個系統的大小適中,而問題領域又是開發人員比較容易理解的敏捷方法,因此開發人員可以自己腦補,同時扮演領域專家的角色。

***

真實世界的困難

剛剛從學習的角度來討論在學習BDD/SBE與DDD的時候,缺少真正領域專家所造成的問題。在真實世界中,這個問題是否就消失了?

雖然開發團隊應該會有專案經理、系統分析師、產品經理或Product Owner等角色來協助釐清系統需求,但實際上這些人很可能都不是真正的領域專家。就算團隊有領域專家的協助,但如前面所提到的,領域專家可以協助釐清核心商業邏輯,但對於應用程式邏輯的幫助相對較小。

此外,敏捷開發採用迭代與增量的方式,在開發過程需要持續與領域專家討論於釐清問題,但並不是所有領域專家都願意這樣子跟開發團隊配合。有些領域專家甚至認為:「這是開發團隊的工作,我為什麼要花自己的時間幫他們做事?」

所以,開發團隊不能無腦地一味將釐清商業邏輯的責任推給領域專家,而領域專家也應該多聽聽開發團隊的意見,不要把團隊提出的問題當成對自己專業知識的質疑,這是一種釐清問題的溝通過程。

***

友藏內心獨白:沒有的東西要去哪裡生?!

2020年1月1日 星期三

領域驅動設計學習筆記(7):Aggregate (下)

January 01 21:52~23:28

▲圖1:Clean Kanban領域模型(部分)


問題

領域驅動設計(Domain-Driven Design;DDD)的Aggregate物件之間透過ID(字串或GUID)參考,而不像傳統物件導向設計(OOAD)直接透過記憶體參考(memory reference)來存取依賴物件。對於「從小」熟練物件導向分析與設計(OOAD)的Teddy而言,剛開始覺得這樣做好像不太方便啊。只記錄Aggregate ID,還要透過Repository來取得該Aggregate物件,這樣不是有點麻煩嗎?

***

記憶體參考

圖1是Teddy所開發的看板系統的部分領域模型:

  • Board:代表一個看板。
  • Workflow:代表一個工作流程,一個看板可以有多個工作流程。
  • Stage:代表垂直的工作階段。
  • Swimlane:代表水平的工作階段。

如果領域模型直接透過記憶體參考,也不套用Aggregate,寫程式的時候就可以:

  • board.getWorkflows()傳回看板的全部工作流程物件,或用board.getworkflowsById()得到某個工作流程物件。
  • workflow.getBoard()得到這個看板所屬的board物件。

這樣一來透過物件的reference可以輕鬆自在存取整個領域模型,甚至可以:

stage.getworkflow().getBoard().getUser()……一直不斷的get下去。當然程式寫成這樣就產生壞味道,但是無限制的記憶體參考很容易「引誘犯罪」,讓開發人員寫出這種程式。

***

Aggregate形成邊界

圖2:套用Aggregate形成物件的邊界

如圖2所示,套用DDD的Aggregate設計模式,原本領域物件變成兩個Aggregate:Board與Workflow,得到如圖3所示的Board與Workflow類別。

圖3:Board與Workflow類別。

套用Aggregate有幾個好處:

  • Board與Workflow變成獨立的Aggregate,因此可以各自更新,不必放在同一個交易(transaction)裡面。如此一來可以提高並行處理的彈性。
  • 一個Aggregate對應一個Repository,物件儲存問題變得簡單很多。
  • Aggregate內部物件的存取必須透過Aggregate Root,因此可以讓Aggregate Root來確保對於Aggregate的操作不會違反invariant,也就是保持程式狀態的正確性。

當然有好處也有缺點:

  • 最大的缺點(也是優點)可能就是Aggregate之間透過領域事件(domain event)達成狀態同步,由原本同步的方式變成非同步方式,程式開發模式跟傳統不同,習慣需要改變一下,重新學習。
  • 無法像傳統一樣,只要透過物件參考就可以得到整個領域物件(或大部分領域物件)的資料。例如,如果使用者介面要顯示看板所有的工作流程,從Board物件無法直接得到Workflow物件,因為Board只記錄Workflow的ID,必須再透過WorkflowRepository才能得到Workflow物件。

***

View Model

▲Clean Architecture,圖片來源在此

將領域模型與使用者介面模型(View Model)切開,原本就是DDD的重點。但是領域模型最終還是要展示給使用者看,此時只要多設計一層View Model,將領域模型轉成使用者介面方便操作的View Model就可以了。從Clean Architecture的角度來看,讓Interface Adapter層(第三層)來負責做轉換。

▲設計BoardDto給UI使用。

***

結論

釐清邊界,單一責任,分層負責,系統的結構才會清楚明白,軟體也才能變軟。

***


友藏內心獨白:把一件事情做到最好就很厲害了。

2019年12月31日 星期二

2019年終心得:突破

Dec. 31 08:45~10:02


停了三年沒寫年度心得報告,前幾年的心得分別是〈2012年終心得:堅持〉、〈2013年終心得:淡定〉、〈2014年終心得:專注〉與〈2015年終心得:學習〉,今年的心得可以用「突破」兩個字來概括。

2019年重大突破事件包括:

***

業績

今年泰迪軟體的營業額是成立以來最高的一年,說「高」也沒有到達真正「發大財」讓Teddy可以財務自由提早退休的地步,但的確是比往年都要來得好。

Teddy 猜想可能是敏捷在台灣近幾年比較流行的關係,市場變大,機會也變多了。泰迪軟體從2012年7月成立至今,始終沒有改變初衷,持續耕耘於敏捷開發的各個主題上,因此累積了許多老客戶。很多新的業務機會都是靠老客戶介紹,真的非常感謝。因為除了寫寫部落格,在Facebook上發發廢文以外,Teddy也沒什麼行銷能力去拉新客戶,只能被動地靠口碑行銷了。

***

同事

泰迪軟體只有兩位員工,Teddy與Erica。Teddy在〈2015年終心得:學習〉提過,Erica到泰迪軟體之後Teddy一直抱持著「訓練博士生」與「培養合作夥伴」的態度來與Erica互動。2019年Erica已經能夠獨立作業,在敏捷教練、引導、敏捷需求管理方面也有不錯的成績。

今年公司業績創新高有很大一部分也是因為Erica獨立接案的關係,畢竟Teddy一個人一年能夠賺的錢也就那麼多(少 Orz),公司有「兩顆CPU」當然是要「並行處理」才能夠擴大營收XD。

***

Teddy

精實開發有一句話:「沒有最好,只有更好」,今年Teddy也有一些長進。研究許久的Clean Architecture(簡潔架構)Domain-Driven Design(領域驅動設計),今年結合了這兩者,發現無論是在架構上、problem modeling以及coding,軟體開發與維護變得比較容易也更系統化。

Teddy一直相信敏捷開發是一種軟體從業人員追尋「The Timeless Way of Software Development」(軟體開發的永恆之道)的一種嘗試。永恆之道,沒有固定的形式,但任何認真於此道的人只要一看到就會知道:「啊,對了,就是這個樣子。」

這就是Quality Without A Name。

***

另外一個突破就是為了明年三月在台灣舉辦的AsianPLoP 2020暖身,Teddy在年底辦了一場【模式寫作工作坊】,幾位參加工作坊的朋友也開始嘗試撰寫模式(Pattern)。撰寫模式是將自己的專業知識淬鍊之後以精簡的語言與固定格式表達與分享的一種過程,這是一條辛苦的路,Teddy很高興有朋友願意一起嘗試,Teddy也很願意花時間幫忙這些朋友們修訂他們撰寫的模式。

2020年1月還有【模式寫作工作坊第二梯次】,有興趣的朋友可以參考。

***

最後還有一個突破就是Teddy今年收編了一隻浪浪,叫做Pascal。說是收編,其實是Teddy被收編

Pascal是Teddy餵養三年多的浪浪,他一開始跑到Teddy家的窗戶外偷吃Teddy準備給另一隻浪浪的飼料,一直持續三年多。有一天他突然消失一個禮拜,Teddy以為他出意外已經往生了,沒想到一個禮拜後他又突然出現,但此時他的左眼破裂,整隻貓也瘦到皮包骨。

看到餵了這麼久的浪浪受重傷實在於心不忍,於是Teddy買了捕貓籠,將他抓起來送醫治療。治好後恢復健康,但剩下一顆眼睛怕他無法適應街頭的生活,於是就將他收編了,取名Pascal。

▲Pascal本貓

Teddy家裡原本已經有Eiffel與Ada兩隻母貓,不過當初都是向「流浪動物花園協會」領養而來,約六3~6個月左右的幼貓,而且已經有人幫忙「整理過」了,比較親人。不像Pascal是成年公貓,而且又是浪浪,第一次收編還是有很多需要學習的地方,也算是有苦有樂。這就是人生啊。

▲Eiffel(虎斑)與Ada(橘白)

***

感恩

最後要感謝2019年直接或間接「金援」泰迪軟體的所有朋友們。沒有金錢的幫助,泰迪軟體無法經營下去,當然也就沒有現在的Teddy。

對於一個不拉幫結派、不搞花俏行銷、不造神,相信把自己的專長持續做到最好就有一線生機的笨蛋,還可以在這片土地上生存下去,而且活得快樂、活的有尊嚴。除了感恩,還是感恩。

***

友藏內心獨白:最後重複使用了2015年的結語。

2019年12月27日 星期五

領域事件的發送與接收(下)

Dec. 27 06:40~09:02

▲圖1:兩個Aggregate透過領域事件同步狀態


前情提要

上一集談到Teddy開發的Clean Kanban系統範例其中的兩個Aggregate:Board與Workflow,如圖2所示。


▲圖2:Board與Workflow aggregates。

Workflow的addStage方法會發出StageCreated領域事件,先將領域事件儲存在身上,等待儲存Workflow之後再一起發送領域事件。

今天要介紹如何接收並且處理領域事件。

***

誰來接收領域事件

Board與Workflow被設計成兩個Aggregate,一個Board包含零到多個Workflow,Board依靠CommittedWorkflow Value Object來存儲它與Workflow關係。

如圖1所示,新增Workflow會產生Workflow Created領域事件,這個事件驅動Commit Workflow這個Command,通知Board將剛剛新增的的Workflow加入自己身上(透過新增一筆CommittedWorkflow的方式來紀錄)。

有兩種可能的方式來接收WorkflowCrated領域事件:

  • Board:既然Board對於WorkflowCrated領域事件感興趣,可以讓它直接接收這個領域事件。但是為了處理領域事件,Board可能需要透過Repository讀取其他物件,如此一來會讓Board與Repository產生相依性。依據Clean Architecture,Board屬於最內層的Entity Layer,而Repository屬於第二層Use Case Layer,因此Teddy不考慮讓Board直接去接收領域事件。
  • Event Handler:透過位於Use Case Layer的event handler來接收領域事件,接下來將採用此方式接收與處理領域事件。

***

程式範例

▲圖3:Workflow Event Handler程式範例

Event handler程式很容易撰寫,上一集提到Teddy使用EventBus元件來發送與接收領域事件,EventBus採用annotation的方式來標註event handler,如圖3所示,只要在處理領域事件的method身上貼上@Subscribe,然後再跟EventBus註冊即可。

註冊event handler的方式如圖4所示,呼叫EventBus的register方法即可。

▲圖4:註冊event handler

***

▲圖5:測試案例

圖5為測試案例,在setUp方法中記得先註冊event handler,然後驗證產生Workflow會在Board物件上將新增一筆CommittedWorkflow資料。測試案例通過代表event handler產生作用。

***

不是新東西

把Aggregate當成狀態機(State Machine),透過Command產生狀態改變(state transition)並發出事件,讓對於狀態改變有興趣的物件(也就是event handler)可以依據這些事件做出反應,並不是什麼新的技術,也不是領域驅動設計(DDD)或是Event Storming所獨創的開發方法。這種程式開發方式在「古早」時代就有了

從實作面來看,只要套StateObserver這兩個設計模式就可以做到。所以,不要被名詞給嚇到,要關注這些名詞背後所要解決的問題是什麼

DDD加上Event Storming在這裡的價值在於,前者提出Aggregate模式,讓開發者可以組織一個合適的物件群組邊界(a cluster of objects)。不同的Aggregate之間不再透過直接記憶體物件參考(object reference via memory address)而是透過事件(領域事件)來同步狀態。只有Aggregate內的狀態保存需要符合ACID(atomicity、consistency、isolation、durability),Aggregate之間的狀態放寬到最終一致性(eventual consistency)即可。

放寬了consistency model之後,除了可以提升程式的並行性,對於資料庫設計也簡化許多。不需要再像傳統系統一樣,使用一個大型關聯式資料庫來確保所有資料的一致性。系統可以切割成不同的模組、服務、微服務,或是用DDD的說法,Bounded Context,簡化了每個Bounded Context的設計、開發、佈署與維運等工作。

每一個Bounded Context之內的不同Aggregate透過Domain Event來同步狀態,不同的Bounded Context則是透過跨Bounded Context的Domain Event來同步狀態(也就是說Domain Event可以區分為Bounded Context之內與之外這兩種)。

既然系統演變成事件驅動的模式,傳統物件導向分析設計以名詞(先找概念)為主的塑模方式,就變得比較不那麼直覺。Event Storming,嚴格說起來,它的基本元素在傳統物件導向分析設計裡面幾乎都有,但是Teddy覺得Event Storming有幾點很不一樣的地方:

  • 採用以事件(領域事件)為主的塑模方式,不但在分析階段可以優先關注商業流程,在開發階段也可以自然配合事件驅動的系統架構。透過一個「無限大的牆面」,以迭代與增量的方式讓開發團隊與領域專家一起合作、學習、交換知識、產生共同語言。
  • 把傳統物件導向分析設計所需繪製的很多圖表,用一些很簡單的圖示來取代。請參考圖6,而這個圖中的元素不但可以用在分析商業流程(Big Picture Event Storming),還可以用在設計(Design Level Event Storming),簡單、好記、好用,又有足夠的表達力。


▲圖6:Alberto Brandolini 的「picture that explains everything」,節錄自《Event Storming》。

***

友藏內心獨白:不要被名詞嚇到。

2019年12月26日 星期四

領域事件的發送與接收(上)

Dec. 26 17:30~18:47

圖1:看板系統的部分領域模型


背景介紹

為了明年泰迪軟體新修訂課程【領域驅動設計與簡潔架構入門實作班】,Teddy最近三個禮拜都在修改課程專案的程式範例。簡單的說,Teddy想藉由開發一個看板系統,來達到學習領域驅動設計(Domain-Driven Design;DDD)、簡潔架構(Clean Architecture)以及實例化規格(Specification by Example;SBE)。

這一系列談用DDD實作系統時如何發出與處理領域事件(domain event),這一集先談Aggregate Root如何發送領域事件,下一集再談如何接收。

圖1是Teddy設計的CleanKanban系統的部分領域模型,包含了兩個Aggregate:

  • Board:代表一個看板,一個Board可以包含多個Workflow。這個aggregate由Board entity以及 CommittedWorkflow value object所組成,Board為Aggregate Root。
  • Workflow:代表工作流程,其中包含了Stage(垂直的工作步驟)以及Swimlane(水平的工作步驟),Workflow、Stage、Swimlane都是entity,Workflow為Aggregate Root。


圖2:新增Stage的時候Workflow會發出 Stage Created領域事件。

如圖2所示,如果使用者要新增一個Stage(垂直的工作階段),由於Stage屬於Workflow這個aggregate,所以Add Stage的要求會交由aggregate root,也就是Workflow來處理。

接下來看程式碼,如何送出領域事件。

***

Aggregate Root送出領域事件

有三種常見的方式可以送出領域事件:

  • 使用Publisher Subscriber設計模式,領域事件發生時立刻傳送出去。
  • 使用回傳值,呼叫產生領域事件的方法(method or function)之後傳回領域事件。
  • 將領域事件先存在Aggregate Root身上,等適當時機再發送。

Teddy試過第一種和第三種方式,覺得第三種方式比較好。接下來介紹第三種方式。

圖3:AggregateRoot抽象類別程式碼

步驟一:首先宣告一個AggregateRoot抽象類別,如圖3所示。在它上身有一個List<DomainEvent> domainEvents資料成員,用來儲存準備對外發送的領域事件。


圖4:Workflow類別的addStage方法


步驟二:接下來看Workflow的addStage方法,它負責產生一個新的Stage,並將該Stage加在它身上。完成之後Workflow產生一個StageCreated領域事件,並呼叫addDomainEvent方法把這個領域物件先存起來。


圖五:由使用案例層發出領域事件

步驟三:由於Teddy套用Clean Architecture,在實作Add Stage Command的時候直接把它變成一個Use Case類別,如圖五所示的CreateStageUseCase

CreateStageUseCase的execute方法,首先透過repository找出要在哪個Workflow新增Stage,然後呼叫該Workflow的addStage方法,接著把Workflow透過repository存起來。最後,呼叫eventBus的postAll方法將Workflow身上的領域事件全部送出。因為已經將Workflow狀態儲存起來,此時發出領域事件是很合適的時機。


圖六:DomainEventBus

至於傳送領域事件的方式,Teddy目前使採用Google開發的一個開源軟體元件叫做EventBus,然後稍微加工一下符合自己的需求, 請參考圖六。

這種寫法違反了Clean Architecture的相依性原則,需要自行定義一個介面,將對於EventBus的相依性反轉過來。這個就留給鄉民當作練習XD。

***

友藏內心獨白:領域事件先存起來稍後再發送比較好。