l

2019年7月2日 星期二

領域驅動設計學習筆記(6):Aggregate (中)

July 02 21:45~22:48


在上一集〈領域驅動設計學習筆記(5):Aggregate (上)〉提到為了避免破壞aggregate invariant(聚合不變量或聚合規則),aggregate root在回傳資料的時候,需要考慮:

  1. 完全禁止回傳Aggregate內部的Entity。
  2. 可以回傳Entity,但只回傳immutable Entity或是Entity的deep copy。
  3. 設計immutable interface,讓Entity實作immutable interface並透過immutable interface回傳給客戶端。

今天討論這幾種做法的優缺點,並展示一個透過自動產生immutable proxy的工具來解決這問題的範例。

***

禁止回傳Aggregate內部的Entity

  • 優點:這個方法因為禁止aggregate root回傳內部entity,所以客戶端不可能透過aggregate內部entity來破壞aggregate invariant。
  • 缺點:為了提供資料給客戶端,aggregate root可能需要將內部entity轉成DTO(data transfer object)再往外傳。這種做法有點類似clean architecture中,use case層定義雙向介面將entity往外傳遞。從架構的角度來看可以切得很乾淨,但是需要花費額外的功夫包一層DTO。

***

回傳immutable entity或是entity的deep copy

  • 優點:用相同的domain model(直接回傳aggregate內部的entity)來處理程式邏輯,而且因為回傳的entity是不可修改的物件,或是原本物件的複製版本,所以也不可能透過這個entity破壞aggregate invariant。
  • 缺點:無論是實作immutable entity或是deep copy(GoF的Prototype設計模式),都需要花費額外的設計、實作與測試的功夫。特別是deep copy,不小心沒有實作好變成shallow copy就GG了。

***

設計immutable interface

Immutable interface是一種設計模式,請參考c2。簡單講,immutable interface是一個只有getter沒有setter的介面。設計好immutable interface之後,讓你的entity實作此介面。如果aggregate root要回傳entity,只能傳回entity所實作的immutable interface,如此一來客戶端就不可能修改到aggregate的資料。

Immutable interface的缺點和回傳DTO類似,domain model裡面除了原本的entity,又多了另一種代表「不可修改」的immutable interface。

***

折衷作法

如果程式語言可以支援自動回傳immutable object,這個問題就不是問題。Teddy不知道有沒有哪個程式語言有這種功能,目前只能從上述三種做法中挑一種來實做。

Teddy想用方法二。方法二有兩種實作,考慮到回傳entity的deep copy比較不好實作,所以決定優先選擇回傳immutable entity的方式。

要實作immutable entity,可以套用Proxy設計模式,概念如下:

Ministage是一個mutable介面,MinistageImpl是這個介面的實作,而ImmutableMinistage則是將MinistageImpl包裝一層,只要客戶端呼叫到setter的函數,就丟出例外。

這種方法的優點是,從客戶端的角度,他看到的就是Ministage這個domain model的概念。至於它是mutable還是immutable,則只是實作細節。

這種做法當然也有缺點,首先設計上當然比沒套用Proxy設計模式要來的複雜。其次,因為在編譯期間(compile time)客戶端不知道拿到的Ministage是mutable還是immutable,如果不小心拿到immutable但卻呼叫到它的setter函數,則會出現runtime exception。

程式設計師都有偷懶的天性,Teddy想回傳immutable entity但又懶得手動實作proxy,於是google了一下,找到一個Java語言的reflection-util工具,可以自動產生immutable proxy。


▼使用方法很簡單,以maven建構工具為例,首先加入reflection-util的參考。



▼接著在程式中透過ImmutableProxy.create()方法,可以直接產生immutable entity,非常容易使用。

***

從Clean Architecture的角度來看…

使用reflection-util這種工具,可以幾乎無痛產生immutable entity。但是從clean architecture的角度來看,在最核心的entity層引用外部工具,違反了相依性原則,你的架構就變得不再那麼乾淨了。

Teddy覺得這個「小違規」目前是可以接受的折衷方案。因為你可以透過將reflection-util再包一層,讓aggregate root透過間接的方式呼叫reflection-util來獲得immutable entity。有朝一日如果真的不想使用reflection-util,也可以在影響最小的情況下,以其他實作方式將它取而代之。

所以,雖不完美,但還可接受。

***

友藏內心獨白:設計就是取捨後的結果。

2019年6月17日 星期一

領域驅動設計學習筆記(5):Aggregate (上)

June 17 15:36~16:42


問題

在物件導向的domain model裡面,物件和物件之間經常有著複雜的關聯(associations)。如下圖所示,一個看板系統的domain model,Board擁有若干的Stage,一個Stage擁有若干個MiniStage,一個MiniStage擁有若干個SwimLane,一個SwimLane擁有若干個WorkItem。

當修改這些物件狀態時,如果不小心很可能會違反系統的不變量(invariant)或固定規則而導致系統狀態不正確(產生bug)。例如,看板系統的一個不變量是:「SwimLane的WorkItem數量不可以超過WipLimit數值。」

試想在一個多人使用的狀況下,為了滿足上述不變量,當修改SwimLane的狀態時,你可能要鎖住SwimLane,甚至是鎖住整個系統,導致系統無法使用或效率很差。

***

Aggregate Pattern

Aggregate是領域驅動設計(Domain-Driven Design;DDD)裡面很重要的pattern。依據《Domain-Driven Design: Tackling Complexity in the Heart of Software》書中的定義,Aggregate想要解決的問題與解決方案分別為:

Problem

It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Invariants need to be maintained that apply to closely related groups of objects, not just discrete objects. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable.

很難保證具有複雜關聯的模型中物件更改的一致性。 需要維護不變量,這些不變量適用於密切相關的物件組,而不僅僅是離散的物件。 然而,謹慎的鎖定方案會導致多個用戶無意義地相互干擾並使系統無法使用。(中文修改自google翻譯)


Solution

Cluster the ENTITIES and VALUE OBJECTS into AGGREGATES and define boundaries around each. Choose one ENTITY to be the root of each AGGREGATE, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals. This arrangement makes it practical to enforce all invariants for objects in the AGGREGATE and for the AGGREGATE as a whole in any state change.

將ENTITIES和VALUE OBJECTS聚集在一起形成AGGREGATES並定義AGGREGATES的邊界。 選擇一個ENTITY作為每個AGGREGATE的根,並透過根控制對邊界內物件的所有存取。 允許外部物件僅保留對根的引用。可以傳遞對內部成員的瞬時引用,以便僅在單個操作中使用。 因為根控制存取權限,所以內部的更改不能繞過它。 這種安排可以確保任何針對AGGREGATE裡面的物件以及AGGREGATE本身的狀態修改都不會違反不變量。(中文修改自google翻譯)

***

實作細節

以上述看板系統為例子,Stage、MiniStage與SwimLane形成一個Aggregate,Stage是Aggregate Root。依據DDD的定義,為了確保不變量:

  1. 所有對於Aggregate內部物件的操作都要透過Aggregate Root。也就是存取MiniStage與SwimLane都要透過Stage。
  2. 外部物件只可以擁有Stage物件的reference(因為它是Aggregate Root),不可以擁有MiniStage與SwimLane的reference。
  3. 但是,Stage可以將MiniStage與SwimLane往外傳,只不過客戶端程式只能在單一method中操作這些entity,不可以把它們的reference保存起來(例如,不能存在data member裡面)。

前兩點很容易理解與實作,但第三點就需要進一步討論。因為如果把MiniStage與SwimLane往外傳,既使在單一method裡面使用,也可能繞過Stage直接呼叫MiniStage與SwimLane,因此改變Stage這個Aggregat的狀態而破壞不變量。

所以,在實作Aggregate的時候,就有幾種可能的選擇:

  • 完全禁止回傳Aggregate內部的Entity。
  • 可以回傳Entity,但只回傳immutable Entity或是Entity的deep copy。
  • 設計immutable interface,讓Entity實作immutable interface並透過immutable interface回傳給客戶端。

無論採取哪種方式,在實作時都需要特別留意,以免Aggregate的不變量一不小心就被違反了。

***

廣告

對於DDD的Aggregate與Event Storming(事件風暴)有興趣的鄉民,可參考泰迪軟體的【Clean Architecture這樣學就會了實作班】,2019年7月份課程已確定開課。

***


友藏內心獨白:還是要看定義。

2019年5月31日 星期五

如何用看板管理卡住的工作項目?

May 31 10:30~11:53


狀況分析

使用看板的團隊經常會遇到工作做到一半卡住的問題,卡住的原因不外乎:

  • 遇到技術困難不知道該怎麼做下去。
  • 遇到相依性問題,需要等待其他資源(工作、人、設備、材料)完成才可以繼續。

依據卡住的時間長短,卡住的狀況又可分為:

  • 短期:團隊投入資源想辦法排除卡住的原因,每日站立會議追蹤卡住工作的進度,儘量讓工作可以順利往下游移動。
  • 遙遙無期:預期工作會卡住很久,或是在特定時間才有辦法處理該工作。不需要在每日站立會議追蹤卡住工作的進度,一周、兩周甚至一個月更新一次進度即可。

***

用看板管理卡住的工作項目

▼針對短期卡住的工作,在該工作卡片上面貼上一張粉紅色便利貼,提醒團隊該工作項目發生阻礙,需要特別關注排除阻礙讓工作順利流動。


▼每日站立會議時追蹤阻礙項目的狀況,如果尚未排除則在該阻礙項目上用筆點一點,類似progress bar的概念。如果阻礙卡片上的progress bar越長,表示該阻礙停留時間越久。

***

針對被卡住「遙遙無期」的工作項目,短時間團隊並不會也沒辦法去處理它。如果直接把它放在看板上不管,會佔據一個WIP,這樣也不好。

▼一種常見的作法是在看板上建立一個獨立的Ice Box(冰箱)工作階段,把卡住遙遙無期的工作項目移入Ice Box。如此一來原本的工作階段便空出一個WIP,而團隊只需要定期檢視Ice Box裡面的工作卡片有沒有「臭酸」或「超出保存期限」即可。

當Ice Box中的工作項目其卡住的原因排除之後,只要原本的工作階段未達WIP上限,便可移回原本的工作階段繼續施工。

***

如果怕整個看板只有一個Ice Box不容易分類不同工作階段卡住的工作項目,可以參考《Agile Project Management with Kanban》書中的做法,直接在工作階段增加一個追蹤(Track)欄位。

▼如下圖所示,追蹤欄位和Ice Box類似,(通常)沒有WIP限制,放在其中的工作項目數量不會佔據實作工作階段(WIP Limit  = 5)的WIP。

***

結論

看板透過小批量生產消除變異性讓工作流動得更順暢,縮短產品交期。卡住的工作代表產生阻礙,也就是工作流程產生變異,需要特別關注,才不會發生「人進不來,貨出不去,公司發不了財」的問題。

***

友藏內心獨白:貨暢其流。

2019年5月29日 星期三

設定看板的WIP

May 29 11:52~13:41


前言

很多剛開始跑看板的鄉民都有一個疑問:「要如何設定每個工作階段的WIP?」今天介紹《Agile Project Management with Kanban》書中提到的方法。

***

準備工作

要決定如何設定WIP,以下資料需要先準備好:

  • 視覺化工作流程,例如分析、開發、測試。
  • 知道每個工作項目(work item)完成的平均時間,例如分析工作一周完成六個,開發工作一周完成兩個,測試工作一周完成三個。
  • 每個工作階段有多少人負責,特別是瓶頸工作階段的人數(詳見下面說明)。

***

開始計算

有了以上資訊,就可以計算初始看板的WIP。參考下表,計算步驟如下:

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數


3


產能


6/week


WIP


3 * 1.5 = 5


  • 找出瓶頸,也就是速度最慢的工作階段,在這個例子中實作速度最慢。
  • 算出瓶頸的產能:平均每人完成工作速度 * 工作階段人數,2 * 3 = 6,代表實作階段每周可完成6個工作項目。
  • 將實作的WIP訂為:工作階段人數 * 1.5 (四捨五入),實作階段的開發人員有3人,3 * 1.5 = 5。乘上1.5的目的是讓每個人手邊有1.5件工作可以做,以免WIP太小任何一件工作卡住就必須等待。但也不會因為WIP太高導致context switch的浪費。這個技巧在《Kanban in Action》書中也有介紹。
  • 其他工作階段配合瓶頸速度:由於屬於瓶頸的實作階段每周只可完成6件工作,因此分析只需要1人即可搭配實作階段的消耗工作速度。同理,而測試階段只需要兩人。

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數

1

3

2

產能

6/week


6/week

6/week

WIP


3 * 1.5 = 5


  • 計算其他工作階段WIP:分析工作階段WIP = 1 * 1.5 = 2。測試階段WIP = 2 * 1.5 = 3。

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數

1

3

2

產能

6/week


6/week

6/week

WIP

2

3 * 1.5 = 5

3

***

人太多怎麼辦

剛剛的例子是由瓶頸階段的人數與平均每人完成工作的速度,回推其他工作階段需要多少人。但很多情況是團隊人數已經確定,例如分析、實作、測試各2人,請參考下表:

分析

實作

測試

平均每人完成工作速度

6/week

2/week

3/week

工作階段人數

2

2

2

產能


12/week


4/week

6/week

WIP

3

3

3


在這種情況下,因為一周完成12件分析工作但卻只能消化4件,因此分析階段的工作會累積在實作之前。而測試會沒有工作可做,導致人員閒置。

但是,幸好有WIP限制,分析工作不可能無限增加。如下圖所示,假設分析與實作階段已達WIP上限,此時分析人員便不可再從待辦事項中拉取工作來分析,只能停下來看看如何幫助工作流動得更順暢。

同理,測試階段可能沒有多餘的工作可以做,一樣需要思考如何幫助工作流動得更順暢。最直接的方式就是所謂的swarming,分析與測試階段的人到實作階段幫忙,提高實作階段,也就是瓶頸階段,的產能。

如果能夠從每個工作階段的產出可以互相匹配的角度來安排人力與設定WIP,會讓工作流動比較順暢。

當然,實務上平均每人完成工作速度變異性可能很高,受到品質、施工人員的技能與身心靈狀態、工作項目大小、需求不確定性等因素影響,導致需要控制的因素很多,因而需要時時檢討人力、WIP的設置是否恰當,以及透過流程改善來讓工作流動更加順暢,降低交期(lead time)。

***

友藏內心獨白:小批量生產與消除變異性。

2019年5月28日 星期二

不要只聽半套

May 28 23:11~23:54

▲這句怎麼都沒人聽進去XDD


故事一

有一個公司派了整個團隊來泰迪軟體上Scrum敏捷方法實作班,回公司之後團隊跑了一陣子Scrum。有一天因為產品出了包,非常緊急,Product Owner希望團隊加班趕緊把這個問題解決。沒想到,團隊中有成員居然回答PO:「我們去上Teddy的課,Teddy說跑Scrum不能加班。」

公司老闆得知後非常氣憤,有一次遇到Teddy,用開玩笑的語氣說:「我員工去上你的課,回來之後都不加班了」。

這,員工不想加班就大聲說「我不想加班」,幹嘛扯到Teddy頭上哩?!Teddy上課時是說:

敏捷開發講求「穩定的開發步驟」(sustainable pace),早期XP對於穩定開發步驟有一個簡單的說法,就是每週工作四十小時。我自己還在與Scrum團隊的時候,也是儘量利用上班專心把工作完成,以不加班為原則。

假設公司因為產品出問題都火燒屁股了,作為員工此時還在「我跑敏捷我不加班」,如果Teddy是老闆,也會發火啊。古語有云:「善戰者無赫赫之功」,這也是好的敏捷團隊期望達到的狀態。但是,天有不測風雲,總是會遇到需要「江湖救急」的時候。此時願不願意跳下來解決問題,就不是敏不敏捷的問題,而是心態的問題了。

***

故事二

某PO質問Teddy:「為什麼你跟團隊成員說retrospective沒用?」聽到這樣的質問,Teddy也是滿頭黑人問號???

細問之下,原來是Teddy和團隊成員聊天時提到:

如果retrospective會議沒有訂定具體的改善執行計畫(action plan),或是改善計畫都沒有落實,相同的問題便會一再出現,導致每次retrospective都討論一樣的議題。有迭代沒有增量,久了之後團隊就會覺得retrospective沒有用。

不知道這位團隊成員是不是buffer不夠,只擷取到「retrospective沒有用」這幾個字。也未免斷章取義的太厲害了一點吧。

***

問題是自己狀況的投射

相信很多人都有這樣的經驗,聽了一場演講,或是經過一次對話,腦袋中留下來最深刻的只有「與自己現況不滿相關的記憶」。因為這些不滿的力量非常強大,強大到足以「扭曲力場」,把別人的意思曲解成自己的內心獨白,然後採用「借刀殺人」的手段,將自己一直想說卻不敢說出口的話,假借「別人的名義」說出來。

這種斷章取義、扭曲本意、沒有脈絡(context)的引用,並非全無意義。它往往透露出講話者內心真正的感受,只不過平常不敢說出來罷了。

為什麼敏捷開發的始祖XP要提「勇敢」?因為太多人不敢面對自己,不敢正視問題。真、善、美,只有真誠的表達自己的感受,改善才有可能發生。只有改善發生,才有可能逐步趨近完美。

***

友藏內心獨白:Teddy有說多讀書你怎麼都沒聽進去?!

2019年5月24日 星期五

一個Scrum各自表述

May 24 00:40~01:27

▲每個component team都在跑Scrum,啊不就好棒棒…Orz


沒事不要找Teddy聊天XD

去年在某個演講場合,演講結束後某位鄉民找Teddy聊天….

鄉民:Teddy你好,我是你部落格的忠實讀者。

Teddy:你好、你好,謝謝捧場。

Teddy:你看我的部落格,是因為你們有跑Scrum嗎?

鄉民:對,我在用戶研究團隊跑Scrum。

Teddy:喔……用戶研究團隊跑Scrum…..這麼神奇。這是什麼意思?

鄉民:就是我們團隊會在專案開始前期用迭代、增量的方式,跑幾個sprint產出用戶研究報告。

Teddy:然後把產出的報告交給開發團隊去實作?

鄉民:對。

Teddy:怎麼我聽起來好像是瀑布式開發?

鄉民:不是、不是,我們是跑Scrum。

Teddy:你們做出用戶研究報告交給開發團隊之後,需求都不會改變嗎?

鄉民:會啊。

Teddy:那之前花時間所做的用戶研究報告需要重做嗎?

鄉民:這……….

Teddy:你們就是waterfall啊。

鄉民:我們是Scrum啦……至少在公司內我們要說我們在跑Scrum。

Teddy:那你們當初為什麼想「跑Scrum」?

鄉民:因為總經理下令說要跑Scrum。

Teddy:喔,對、對。幾個月前我有看過你們公司發的新聞稿,說你們Scrum跑得很成功。


▲有功大家練,有敏大家捷。

***

高興就好

▲圖片來源在此


陳近南:反清復明 敏捷只不過是個口號,跟阿彌陀佛其實是一樣的。Waterfall一直欺壓我們公司,浪費我們的銀兩,所以我們要反Waterfall!

韋小寶:要反Waterfall搶回我們的錢,是不是?說來說去敏不敏捷根本就是脫褲子放屁!關人鳥事呀?行了,大家聰明人,了解!繼續!

陳近南:嗯。總之,如果成功的話,就有無數的銀兩,你願不願意去呀?

韋小寶:願意~!只不過你剛剛那句九死一生太嚇人了。

***

友藏內心獨白:敏捷就是專案出得去,錢進得來,公司發大財XD。


2019年5月20日 星期一

2019 iMac 27吋開箱

May 20 20:58~22:31


Teddy手邊2016年購買的MBP 15”用了兩年半,已略顯老態。平常上網、文書處理是沒什麼問題,但只要同時開啟Parallels與IntelliJ,加上數十個分頁的Chrome,整台電腦跑起來就會有點卡卡的。

今年決定不要再買筆電,改買桌機。本來考慮iMac或是Mac Pro,但Mac Pro遲遲未上市,最近寫程式的頻率比較高,等不及Mac Pro決定先買2019年iMac。


▼美國今年三月就上市,台灣等到五月七日才開放訂購。這次狠下心來攻頂,購買最高階i9版本。


▼記憶體選擇最基本的8GB,自己另外購買4條OWC 32GB RAM,擴充為128GB。


上禮拜六(5/18)11點多iMac送到家裡,但我人卻在台南。隔天下午回台北後,趕緊開箱。


▼打開後包裝很簡潔。


▼把螢幕撲倒準備加裝記憶體,後來發現其實螢幕立著也可以裝記憶體,不用撲倒。


▼連接電源線的位置有一個按鈕,用十字起子用尖尖的東西刺一下,記憶體被蓋就會彈出。


▼兩條內建的4GB記憶體。


▼鍵盤、滑鼠、觸控板。原本考慮是否要買附數字鍵的鍵盤,但顧及桌面空間,以及之前已經用了六年多的無數字鍵的鍵盤也還算習慣,最後還是選擇無數字鍵鍵盤。這一代的鍵盤、滑鼠、觸控板都內建鋰電池,可以直接充電,方便很多。


▼開機後開始設定。


▼2019 iMac 27” 可以安裝128GB記憶體,Apple Store最多只可客制到64GB,而且價格比自己到外面購買要貴一倍。如果需要擴充記憶體,還是自己動手比較省錢。


▼因為經常需要使用Windows,此次特別買了專業版的Parallels,可以設定比較大的記憶體給VM使用。

***

剛拿到iMac兩天,目前只有一個問題,就是iMac不能調高低,Teddy覺得iMac螢幕高度太高,和以前使用的螢幕高度比較來脖子要稍微抬高,有點不舒服。

桌機的速度還是比筆電快很多,價格也比較便宜。像Teddy這種使用習慣,應該老早買桌機才對。只要搭配一台輕巧、長效的Windows筆電外出使用即可。

***

友藏內心獨白:爽度破表XDD。