l

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。

***

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

2019年12月16日 星期一

CRUD魔咒

Dec. 16 09:59~11:19


靜態的領域模型

最近在準備明年新修訂的課程【領域驅動設計與簡潔架構入門實作班】,花了一些時間重讀Alberto Brandolini的《Event Storming》。書中提到Event Storming的Domain Event(領域事件)應該以使用者操作為出發點去思考,而不僅是傳統的CRUD所產生的事件

讀到這段話Teddy突然想到:「自己在練習Event Storming也遇過同樣的疑問,怎麼許多領域事件都是代表CRUD的事件?」

***

領域驅動設計(Domain-Driven Design;DDD)有一個重點就是開發人員與領域專家一起討論領域模型,這個領域模型用來解釋與理解問題領域,成為開發團隊和領域專家在溝通時的通用語言。

使用領域模型(Domain Model)而不是資料模型(Data Model),這一點一直是物件導向分析與設計以及DDD的核心,但是以資料庫(或是使用者介面)為主的軟體設計思考方式已經深入許多開發人員的骨子裡。就算已經有了物件導向領域模型,在設計功能(use case或user story)時總是免不了寫出一堆CRUD(新增、讀取、修改、刪除)

***

動態的使用者操作

▲圖1:代表CRUD的領域事件

Teddy之前用Event Storming來塑模看板系統的工作項目(Work Item),當時直接反應就寫下圖1這三個領域事件。但這就是CRUD啊,系統的行為好像沒有被真實反應出來。


▲圖2:Kanbanize系統修改卡片的功能

上週Teddy花了點時間研究Kanbanize,這是一個做的很棒的商業看板系統,它的卡片(Card,在Teddy的領域模型中叫做WorkItem)提供很多使用者可以直接操作的功能,如圖2所示。例如:

  • Assignee:指派認領工作的人
  • Priority:設定優先順序為Low、Average、High、Critical
  • Deadline:設定卡片的截止日期
  • Done:把卡片移到完成階段

以上面四個操作為例,應該會產生類似以下的領域事件:

  • CardAssigned
  • PrioritySet
  • DeadlineSet
  • CardFinished

而不是只用一個CardUpdated來代表這些系統操作。

***

不只是粒度大小的差異

有些人可能會認為:「這只是系統功能粒度大小的差別而已啊。CardUpdated粒度比較大,包含了CardAssigned、PrioritySet、DeadlineSet、CardFinished這四個事件。」

某種程度來說,的確是功能的粒度大小不同,但除此之外還有更深層的含意。

系統行為有沒有被塑模出來?

換句話說,領域專家與開發人員針對系統的行為到底溝通到什麼程度?有沒有忽略了什麼重要的系統行為?

很顯然地,如果只是討論CRUD,使用者針對「卡片」本身有意義的操作都躲在「修改卡片」這個功能之下,因此很容易忽略的「修改卡片」這個功能在看板系統這個領域中,到底對於使用者的領域意義是什麼。如此一來,軟體功能並沒有反應出系統的領域知識,因而降低系統的易用性。

***

友藏內心獨白:系統行為的塑模和用戶體驗有關。

2019年12月11日 星期三

【模式寫作工作坊】第二梯次

Dec.11 16:40~17:22


第一梯次的【模式寫作工作坊】於上11/30圓滿結束,原本配合AsianPLoP 2020的截稿日期,這個工作坊只預計舉辦一次。

12月初與主辦單位討論,以推廣中文模式寫作為出發點,把中文模式與主辦單位正式徵稿的活動脫鉤。主辦單位將會在AsianPLoP 2020安排一個下午的session,讓(台灣的)與會者體驗中文的 Writer's Workshop,在活動中討論用中文撰寫的模式.

Teddy預計徵求3~5篇中文模式在該session中討論,目前有三位參加第一梯次【模式寫作工作坊】的朋友有興趣嘗試。一直到明年二月底之前,Teddy會免費協助有意願者修改他們的作品。

離明年三月會議舉辦還有一點時間,Teddy計畫舉辦第二梯次的【模式寫作工作坊】。

***

工作坊簡介

模式寫作工作坊議程如下:

  • 介紹模式起源與PLoP研討會Writer’s Workshop進行方式。
  • 模式六大格式介紹,並透過閱讀模式來驗證模式格式的作用。
  • 模式寫作,介紹Pattern Language、確定寫作主題以及現場寫作與修改練習。

***

報名

  • 課程費用: 8,000元。活動由「台灣軟體工程學會」開立收據(無提供發票)。
  • 日期:2020年1/7和1/14日(禮拜二),19:00~22:00,共6小時。
  • 地點:泰迪軟體,台北市延平南路12號四樓
  • 備註:
    報名本工作坊之「本人」將可免費參加2020 Asian PLoP (報名費價值約6,000元),https://pl.csie.ntut.edu.tw/asianplop2020/

    ***

    友藏內心獨白:真的是最後一梯次了。

    2019年12月10日 星期二

    領域邏輯與應用邏輯

    Dec. 10 13:50~14:49


    名詞解釋

    在物件導向分析與設計(Object-Oriented Analysis and Design;OOAD)、領域驅動設計(Domain-Driven Design)或是簡潔架構(Clean Architecture)中,經常會看到領域邏輯(Domain Logic)應用邏輯(Application Logic)這兩個名詞。在Clean Architecture中,前者稱為關鍵業務規則(Critical Business Rule),後者稱為特定應用業務規則(Application-Specific Business Rule)。

    一般針對這兩個名詞的解釋:

    • 領域邏輯:特定業務領域(business domain)都適用的邏輯。
    • 應用邏輯:在某個業務領域中,特定應用程式的邏輯。

    一般情況下,除非很大型的系統,否則開發人員遇到的情況很可能只有開發一個業務領域中的一個應用程式。也就是說,不太容易區分這兩者,特別是對初學者而言。

    ***

    例子

    最近跟北科大資工系ezKanban團隊討論看板系統的領域模型(domain model)。原本的需求只需支援圖1中的看板系統。

    ▲圖1:看板系統範例


    為了支援圖1的看板系統,ezKanban的domain model如圖2所示。

    ▲圖2:ezKanban的Domain Model


    圖2暗示了一個領域邏輯:一個Stage(圖1中的待辦事項、分析、實作等)有一個預設的MiniStage(圖1中待辦事項底下的想法與Top 5),一個MiniStage有一個預設的SwiLane(用來放置工作卡片的物件)。

    ***

    過了一陣子,Teddy覺得一個看板系統應該有一個預設的Stage用來存放剛剛新增的Work Item(工作卡片),還有另一個預設的Stage用來存放已完成的Work Item。如圖3所示。

    ▲圖3:新的看板系統需求,一個看板有兩個預設Stage:Backlog與封存(Archive)


    如此一來,增加了一個新的邏輯:一個Board物件至少有兩個Stage,如圖4所示。

    ▲圖4:Board與Stage的關係


    圖4中的關係,應該要算是領域邏輯還是應用程式邏輯?

    很顯然地這應該是一個應用程式邏輯,因為ezKanban這個應用程式的要求,才讓Board與Stage產生這樣的關係限制。如果是其他人使用相同的domain model來開發看板系統,則不一定會對Board與Stage的關係規範這種限制。

    所以要實作圖4的邏輯,應該是放在DDD所說的應用程式層(Application Layer),或是Clean Architecture裡面的使用案例層(Use Case Layer)。

    至於圖2中Stage與MiniStage以及SwimLange的關係,屬於domain model的核心邏輯,所以在DDD裡面的Domain Layer(又稱為Model Layer)或是Clean Architecture的Entity Layer實作。

    ***

    友藏內心獨白:分層負責才會乾淨。

    2019年12月9日 星期一

    變成專家之後的學習

    Dec. 09 14:10~15:10

    ▲畫片節錄自電影「鹿鼎記」


    念博士班的時候

    有一種說法,變成專家需要一萬小時的刻意練習(請參考〈一萬個小時的練習〉)。至於鄉民們對於這「一萬小時」的數據是否買單,信或不信,不是今天想要談的重點。Teddy想要說的是,每個人都有自己「學會一件事」(變成專家)所需要的時間。這個時間,依據每個人的能力可能會有所差異,但從「正常人類」的範圍來討論,大致上應該會落在一個相差不多的區間內。

    Teddy以前在念博士班的前三年,都屬於「練功階段」,修課、準備資格考、讀書、讀論文、練習寫作e-learning patters。因為還在蹲馬步階段,嘗試弄清楚「地形地物」,當時沒能力寫出什麼偉大的論文可以拿去投稿。

    後來基本功練得差不多,漸漸發現,接觸到一個軟工相關的新題目之後,可以在18個月(一年半)左右至少寫出一篇國際研討會的論文,算是對這個題目的學習成果給一個階段性的交代。

    後來這「18個月」就變成自己學習新題目的一個時程表,每當自己覺得「好棒棒」的時候,就會提醒:「你才接觸這個題目不到半年,一定還有很多問題你自己沒遇到,不要高興太早。多讀點書和論文,多動手寫程式才是真的。」

    18個月,以一天4小時計算,一年算200個工作天,18個月有300個工作天,一共是1200小時。這1200小時就是Teddy估算在自己成為軟工專家之後,學習軟工新題目到有所小成所需要的時間。

    ***

    成立泰迪軟體之後

    成立泰迪軟體之後,前幾年所開的課都是以前工作與唸書10幾年所累積的成果。即使是「單元測試這樣學就會了實作班」與「軟體重構入門實作班」這些原本已經很熟的技術,要變成一門課,也是要近百個小時的準備。

    2020年新開的「領域驅動設計與簡潔架構入門實作班」(原本的Clean Architecture實作班),則是前後花了約三年的時間才演化成現在的版本。以前念博士班的時候比較專心,WIP比較少,所以只要18個月就可以學會一個新的題目。現在年紀越來越大,理論上「知識越來越豐富」,但因為泰迪軟體個工作本質上context switch很大,一下子講Scrum,相隔沒幾天又要講Design Pattern,接著又切換成例外處理設計。這個多不同的主題平常都要花點時間去注意現況的發展,無法像以前念書時那麼專心學一件事,所以整個lead time拉長,從以前18個月,變成現在36個月。

    ***

    結論

    這幾年在北科大兼任授課,有些學生跟Teddy反映:「為什麼同一個問題Teddy可以翻來覆去用好幾種不同的角度不斷地拿出來討論?」因為學生覺得「這樣好浪費時間,希望Teddy教快一點,他們想多學一些。」

    Teddy總是跟學生說:「你們以為已經學會了,但其實只是學到表面薄薄一層的含意。這些表面的東西,根本不用來上課,去Google就可以找到答案。上課就是要讓你們學會自己思考,養成自我批判與學習的能力。老師不可能一直跟在你們身邊,遇到問題只要跑來問老師就有答案。要練習讓自己變成自己的老師。」

    但坦白說,並不是每個人都想要當自己的老師。想要搖一搖蘋果樹就有蘋果可以吃的人還是比較多。

    變成專家是一種獲得Quality Without A Name的過程,變成嘴砲王則是一種獲得A Name Without Quality的手段。沒有對錯,端看自己的取捨而定。

    ***

    友藏內心獨白:聽到「有一批便宜的牛肉」要提高警覺啊。

    2019年12月7日 星期六

    感受作用力

    Dec. 07 09:21~10:25

    ▲使用者故事對照(User Story Mapping;USM)活動


    不是可不可以的問題

    有一個問題每過一陣子Teddy就會被跑Scrum或Kanban的朋友被問一次:「可不可以用軟體加投影機取代實體Scrum Board或是Kanban Board?

    這種問題,問「可不可以」之前,應該先問「Scrum Board或Kanban Board要解決什麼問題?」,然後再問「改用電子化之後,原本要解決的問題依然持續被解決嗎?有沒有產生新的問題?」。

    ***

    低科技,高接觸

    敏捷圈有一種說法:「Low Tech, high touch. High tech, low touch」。有好幾位跑Scrum的朋友跟Teddy提過,他們從實體Scrum Board(Task Board)改用軟體加投影機之後,原本Daily Scrum每個人都投入的狀況,變成傳統會議那種大家看著投影機「假裝有在聽」別人說話,但卻少了很多之前的互動,原本Daily Scrum每天「重新計畫」的目的因而大打折扣。

    但也有朋友告訴Teddy,他們花重本買了大觸控電視用來取代實體Scrum Board,效果比起投影機要好很多,接近實體Scrum Board。

    感受案發現場的作用力(forces),看看解決方案有沒有平衡這些作用力,你就可以判斷目前的解決方案是否合適。

    通常會想用數位工具取代實體Scrum Board或Kanban Board,不外乎:

    1. 工作環境沒有牆面可以拿來建置實體Scrum Board或Kanban Board。
    2. 不是所有人都在同一工作地點,可以直接觀看實體Scrum Board或Kanban Board。
    3. 想要保存Scrum Board或Kanban Board的歷史紀錄。

    第1點,工作環境不允許應該是要「突破」的限制而不是反過來被它限制。工作場所沒有電腦怎麼辦?總不能上班的時候把程式寫在紙上,回家再輸入自己的電腦中吧!沒有電腦公司要去買啊(或是凹員工自己帶)

    至於其他兩點,Teddy建議以實體Scrum Board或Kanban Board為主,在讓團隊成員或指派特定人員每日更新電子看板即可。聽起來好像很麻煩,實體與電子各有一份,還要手動維持同步。但實際上,同步的工作一天頂多也就花個10分鐘,是值得的投資。

    ***

    感受作用力

    今年11月的【Scrum敏捷方法實作班】 ,不知道是不是因為學員組成比較多元,包含專案經理、產品經理、開發人員、主管,在各項練習活動中都特別投入,效果也很好。

    特別是在使用者故事對照(User Story Mapping;USM)活動中,所有人一起同步討論需求。有幾位學員跟Teddy反應,和他們以往的需求討論會議有很大的差別,不只是在形式上USM比較有趣,在實質面獲得的有用資訊量也大勝傳統方式。

    但這並不能完全歸功於USM,同樣的練習活動,Teddy也遇過產出空洞使用者地圖的團隊。所以說,好的團隊成員加上用對方法,身處現場便可感受到好的力場。

    這不是風水,更不是迷信。認真做事,活在當下,便可察覺到團隊的狀況。

    ***

    友藏內心獨白:工具與方法最後都是要遺忘的東西。

    2019年12月6日 星期五

    如何閱讀模型驅動設計建構區塊的模式語言

    Dec. 06 21:40~23:10

    ▲圖1:Model-Driven Design模式語言,節錄自藍皮書


    模式語言範例

    Domain-Driven Design: Tackling Complexity in the Heart of Software》(藍皮書)Part II,「The Building Blocks of a Model-Driven Design(模型驅設計的建構區塊) 」有一張圖( 如圖1所示),代表Model-Driven Design模式語言

    這個模式語言代表DDD單一bounded context內的核心模式,今天Teddy要談一下如何閱讀這個模式語言。

    ***

    模式語言

    模式語言由建築師Christopher Alexander所發明,透過一組有方向性的模式來解決一個大的設計問題。一個模式語言最頂端的模式,代表使用這個語言的人想要「製作出來的東西」。在圖1中,模式語言第一個模式是Model-Driven Design(模式驅動設計),它本身是一個DDD模式。這是一個比較大(或是說比較抽象、比較高層次)的模式,為了落實Model-Driven Design,需要依靠它下方第一層的其他四個模式,讓Model-Driven Design更完整:

    • Service
    • Entity
    • Value Object
    • Layered Architecture

    Service模式下方沒有其他模式,代表Service模式本身不特別需要其他模式來支持它就已經很完整。Entity模式則需要Aggregate、Repository與Factory這三個模式使其完整。Entity可透過Factory來產生實例。Aggregate包含Entity與Value Object,Entity也需要倚靠Aggregate來確保資料的完整性。

    在圖1中,標示著Entity透過Repository來存取,但Teddy認為其實這個關係可以不用畫在模式語言中,因為Aggregate才會有Repository,單獨的Entity是不能透過Repository來存取。客戶端只能透過Aggregate Root來讀取Aggregate內部的Entity。

    ***

    Value Object下方則有Aggregate與Factory,這個關係很清楚,就不解釋了。Aggregate下方則有Repository與Factory,分別用來儲存與讀取以及生成Aggregate。

    ***

    Model-Driven Design下方最後一個Layered Architecture模式,比起前述其他模式都要大得多。前面幾個模式算是「設計模式」,Layered Architecture已經是「架構模式」。嚴格說起來,Layered Architecture下方還是可以展開其他模式來支持它的實作。

    還有一點要注意,在藍皮書中,對於Layered Architecture模式的描述,屬於傳統階層式架構,階層之間的相依性是由上往下,上層依賴於下層。以現在的角度來看,這種解法應該改套用dependency inversion(相依反轉)原則,拿掉上層對於下層的直接依賴。

    如果是Teddy現在來畫這個模式語言,會用Clean Architecture模式來取代Layered Architecture。

    ▲中文版《領域驅動設計》第68頁描述Layered Architecture模式的解決方案。

    ***

    最後,圖1還有一個Smart UI模式,但是它與Model-Driven Design模式之間卻出現一個X符號。Teddy在Alexander原始的模式語言中並沒有看過這種表示方法,作者的用意只是提醒讀者,Smart UI與Model-Driven Design彼此互斥。

    ***

    重畫Model-Driven Design模式語言

    ▲圖2:Teddy重劃後的Model-Driven Design模式語言


    依據以上討論,圖2是Teddy重畫後的Model-Driven Design模式語言。Teddy習慣由上而下來畫製模式語言,而非像藍皮書中由左而右繪製。自己重新整理之後,感覺比藍皮書的版本還要清爽與容易記憶 XD。

    ***

    工商服務

    對於領域驅動設計(Domain-Driven Design;DDD)與簡潔架構(Clean Architecture)有興趣的鄉民,歡迎參加泰迪軟體的領域驅動設計與簡潔架構入門實作班】。

    對於設計模式有興趣的鄉民,歡迎參加泰迪軟體的Design Patterns這樣學就會了–入門實作班

    ***

    友藏內心獨白:用模式語言思考可以看到問題全貌。

    2019年12月4日 星期三

    把問題寫成一個問句

    Dec. 04 08:29~10:25


    選擇解決方案的困難

    使用設計模式(design pattern)超過20年,也教了好幾年的設計模式。Teddy發現一個常見的問題,就是遇到設計問題時,不知道要套用哪一個設計模式

    Teddy一直覺得,讀模式除了模式名字(Name)以外,最重要的就是要知道模式要解決什麼問題 (Problem),最好能夠把問題寫成一個問句,用一句話就能說出該模式存在的目的。

    如此一來,在套用模式的時候,針對解決相同問題的模式,只要考慮他們彼此之間forces的差異,就可以判斷哪一個解決方案比較合適。

    可惜很多模式的撰寫風格並沒有明確指出模式所要解決的問題,而是使用敘述性的文字把問題與forces甚至是解決方案混在一起談論。讀者只關注到模式的解決方案,導致誤用模式的情況。

    ***

    Value Object

    ▲翻拍自《Domain-Driven Design: Tackling Complexity in the Heart of Software》,p.98 。

    上圖為《Domain-Driven Design: Tackling Complexity in the Heart of Software》(藍皮書)書中描述Value Object模式的段落,黑體字的部分指出幾點問題,這個看起來好像是Value Object所要解決的問題(從Teddy的角度來看,黑體字的部分比較像是forces,也就是問題的限制或特徵)。但是,讀完之後還是不知道如何用一句話說明Value Object的Problem。

    黑體字之後突然冒出一句「An object that represents a descriptive aspect of the domain with no conceptual identity is called a VALUE OBJECT 」,這看起來像是在說明解決方案。一下子討論問題,突然又冒出解決方案(書中下一頁才是詳細的解決方案章節),更容易讓讀者抓不住模式所要解決的問題到底是什麼。

    ***

    改寫練習

    藍皮書第五章A Model Expressed in Software提到了四個模式:ENTITY、VALUE OBJECT、SERVICE、MODULE,Teddy認為它們都要解決一個共同的大問題。因為forces不同,所以衍生出四種不同的解決方案(四個不同的模式)。

    在此Teddy試著改寫前三個模式,它們擁有相同的Context以及Problem:

    Context:你正在使用物件導向方法定義領域模型。

    Problem:你如何表達物件(領域元素)?

    ***

    ENTITY

    Forces:

    • 物件不是由其屬性所定義。
    • 兩個屬性不同的物件可能被視為相同。
    • 即使兩個物件具有相同的屬性,它們可能被視為不同的物件。
    • 弄錯物件辨識碼可能導致資料損壞。

    Solution:將物件歸類為ENTITY如果它是透過辨識碼而不是自身的屬性來區分。使此辨識碼成為模型中其定義的主要依據。使類別定義保持簡單,並專注於生命週期的連續性和標識。定義一種區分每個物件的方法,無論其形式或歷史如何皆可依據該方找到想要找的物件。將需求中依據屬性匹配物件的要求改成依據辨識碼。定義一個保證為每個物件匹配產生唯一結果的操作,可能透過附加一個保證唯一的符號來實現。這種標識方式可能來自外部,也可能是系統為系統創建的任意標識符號,但必須與模型中的辨識碼相對應。模型必須規範物件相等的定義。

    ***

    VALUE OBJECT

    Forces:

    • 物件由它們的屬性定義。你只關心它們是什麼,而不關心它們是誰。
    • 紀錄物件的識別碼至關重要,但是將識別碼附加到每個物件身上可能會損害系統性能,增加分析工作並弄亂模型,使所有物件看起來都一樣。

    Solution:當你只關心物件的屬性時,將物件分類為VALUE OBJECT。使它表達其傳達屬性的含義並賦予它相關的功能。將VALUE OBJECT視為不可變。不要給它任何識別碼,並避免為了維護ENTITIES所需的設計複雜性。

    ***

    SERVICE

    Forces:

    • 領域操作(domain operations)本質上是活動或動作,而不是事物。
    • 領域操作無法被歸屬於某個ENTITY或VALUE OBJECT的自然職責時。

    Solution:在模型中增加一個操作,作為代表SERVICE的獨立介面。根據模型的語言定義介面,並確保操作名稱是UBIQUITOUS LANGUAGE的一部分。使SERVICE為無狀態。

    ***

    以上內容大多出自藍皮書,Teddy只是把格式重新調整,抓出Context與問題。以後看到ENTITY、VALUE OBJECT、SERVICE,鄉民們第一個想到的就是「設計領域物件嘿用得到」(這是它們的Context)。接著問題就是有哪幾種領域物件的種類?依據不同的forces,可以分成三種不同的領域物件。

    如果用這種方式去思考與記憶,Teddy覺得比較不會在藍皮書中所提到的一堆模式中迷路。

    ***

    動動腦

    同樣的內容,為什麼有些人可以用很有條理的方式讓別人聽懂,有些人卻講得非常冗長,讓人越聽越迷糊?!

    模式的六大格式是一種很好的收納知識方法,鄉民們可以自己練習一下,找幾個DDD書中其他模式,把它們的問題、解決方案,以及forces抓出來。

    練習過後會產生和吃「瀨尿牛丸」的效果,人都變聰明了!

    ***

    友藏內心獨白:練習把飯菜裝進六個格子的便當盒裡面。

    2019年12月3日 星期二

    泰迪軟體2020年上半年開課時間表

    Dec. 03 16:43~17:28

    ▲領域驅動設計與簡潔架構入門實作班課程照片 (原Clean Architecture實作班)


    時間過得好快轉眼就要跟2019說Bye Bye,又到了年底排課表的時間。2020年Teddy預計新開一門【行為驅動設計與實例化規格實作班】課程,開課時間大約在2020年8月之後。

    以下是泰迪軟體2020年上半年課表,原本的【Clean Architecture實作班】,因為課程內容包含許多領域驅動設計(Domain Driven Design;DDD),所以Teddy將課名調整成【領域驅動設計與簡潔架構入門實作班】。

    Scrum敏捷方法實作班

    • 假日班:2月22、23號(六、日)。
    • 假日班:5月9、10號(六、日)。

    看板方法與精實開發實作班

    • 假日班:3月21、22號(六、日)

    Design Patterns這樣學就會了--入門實作班

    • 假日班:3月7、8、14號(六、日、六)。

      Design Patterns這樣學就會了--進階實作班

      • 假日班:4月24、25、26號(五、六、日)。

      敏捷開發懶人包:物件導向技能

      • 平日班:3月24(二)。

      領域驅動設計與簡潔架構入門實作班(舊課程名稱為:Clean Architecture實作班

      • 平假日班:2月14、15、16號(五、六、日)。

      單元測試實作班

      • 假日班:4月11、12號(六、日)。

      例外處理設計與重構實作班

      • 假日班:6月13、14號(六、日)。

        軟體重構入門實作班

        • 平假日班:4月17、18、19號(五、六、日)


        ▼看板方法與精實開發實作班課程照片

        螢幕截圖 2017-12-12 10.08.45

        ▼「單元測試實作班」課程照片。

        螢幕截圖 2017-12-12 10.14.10螢幕截圖 2017-12-12 10.15.07

        螢幕截圖 2017-12-12 10.15.26螢幕截圖 2017-12-12 10.15.51螢幕截圖 2017-12-12 10.16.10

        ▼「敏捷開發懶人包:物件導向技能」課程照片。

        螢幕截圖 2017-12-12 10.11.38

        ***

        友藏內心獨白:2020年還請鄉民們繼續支持。

        2019年12月2日 星期一

        你也可以撰寫模式

        Dec. 02 09:53~10:55


        重複使用知識

        11月30日Teddy辦了第一次一整天的【模式寫作工作坊】,六個小時的工作坊內容包含了:

        • 介紹模式歷史與常見格式。
        • 閱讀模式
        • 寫作模式

        Teddy發現對於第一次寫作模式的人來講,第一個遇到的困難點就是如何挑選寫作題材。模式的定義,可以簡單用以下一句話來說明:

        模式就是在一個特定領域中,針對重複出現問題所提出證明可行的解決方案。

        也就是說,模式不是創新,而是將已知可行且可重複使用的解決方案與其所要解決的問題記錄下來。因此,模式寫作可視為一種整理知識的過程。要寫出好的模式,最簡單的方式就是從自己熟悉的身邊事物開始著手

        ***

        生活觀察家

        模式不限於軟體開發,而是可以很生活化,包含所有的領域。只要認真做事、認真過生活,便可觀察到許多有趣的模式。

        例如,對於一個專業宅宅而言,每天在家裡關注不同的網路紅人與YouTuber,久而久之很容易察覺這個領域的常見模式。像是:

        • 自己的定位:確定自己要當哪一種類型的網紅,例如知識性、網美、搞笑、旅遊等。
        • 鐵粉:找到自己的定位之後,想辦法吸引一群死忠支持自己的鄉民。
        • 人頭粉絲:有了基本盤之後,要擴大自己的網路人氣。此時可考慮廣納各方人士,增加自己的粉絲人數,壯大氣勢。
        • 互相拉抬:與其他知名的網紅合作,增加彼此的人氣與粉絲人數。
        • 系列故事:製作主題性的系列故事來吸引更多的關注者。
        • 業配:「風蕭蕭兮易水寒,網紅一樣要吃飯」。為了讓網紅這個職業可以長久持續下去,可以考慮接業配增加收入。
        • 安排好的偷拍照片:現在網紅這麼多,如何增加自己的曝光度?利用人們偷窺的好奇心,設計一些「偷拍自己的照片」,然後故意流出到各大社群網站,讓「小時不讀書,長大當記者」的朋友們幫你免費宣傳。
        • 小鈴鐺:你經常發布訊息、照片或影片更新,但你的粉絲卻無法即時收到。請粉絲們按下「小鈴鐺」,以便收到你最新的訊息。

        ***

        模式範例

        接下來Teddy寫一個網紅模式給鄉民們參考。

        安排好的偷拍照片

        ▲圖片來源在此

        Context:你將自己定位為網美型網紅。

        Problem:如何增加自己的粉絲數?

        Forces:

        • 你在知名社群軟體,例如IG與FB經營一段時間,定期發布自己的照片與短片。
        • 你有基本的粉絲。
        • 你不想花太多經費或時間。
        • 你父母不是知名藝人。

        Solution:拍攝自己的清涼偷拍照片,找人將其張貼在PTT、爆料公社等社群,吸引 無腦 媒體報導。

        Resulting Context:大部分的人喜歡看美女/俊男,更喜歡看被偷拍的美女/俊男。套用本模式可在短時間內衝高自己的人氣,進而增加自己的 豬哥 粉絲人數。實施本模式的成本很低,只要自己願意,可以簡易套用。

        但是,不可忽視鄉民的智慧。如果偷拍照片拍得太假,可能造成反效果,被鄉民貼上「假掰」、「做作」的標籤。

        ***

        友藏內心獨白:模式就是有六個空格的便當盒。

        2019年11月26日 星期二

        透過協作產生敏捷需求

        Nov. 26 17:48~18:35


        不是需求

        許多跑敏捷的團隊會使用user story(用戶故事、使用者故事)來表達「需求」,大家一般認為寫user story是Product Owner的責任,團隊只要照著user story所描述的內容就可以做出PO所希望的功能。

        但實際上功能做好之後卻經常被PO要求改東改西,導致開發團隊抱怨:「Product Owner寫的user story太簡略,害他們不能『按圖施工,保證成功』,要不斷地重工」。PO則是不滿開發團隊都不動腦筋,遇到問題也不及時跟他溝通與釐清。

        其實user story不算是傳統軟體開發所說的需求或規格,user story只是引發團隊討論需求的一句話真正的需求,透過開發團隊、Product Owner與利害關係人的對話、討論、探索而產生。討論之後所產生的需求,可以透過specification by example(實例化規格)的方式記錄下來。

        另外,撰寫user story的責任也不能全部推給PO。User story的「為了…(目的)」所描述的是使用者所遭遇的問題,這是PO需要負責搞清楚的部分。而「我想要…(做什麼)這句話已經包含某種解決方案,則是需要PO與開發團隊一起討論,尋求各種可能的做法

        ***

        心態改變

        上週Teddy在上【Scrum敏捷方法實作班】介紹user story的時候跟學員提到上述觀念,下課時有一位學員說…

        以前團隊在product backlog refinement workshop的時候,我們都習慣要求PO要把user story寫清楚,如果有不清楚的地方,我們會責怪PO,覺得他沒有準備好就找我們來開會。但上完課之後,我才發現原來撰寫user story的責任不能完全推給PO一個人。下次再開refinement workshop,我會改變作法,多提供意見。

        ***

        協作遊戲

        敏捷開發是一種協同合作的活動,不是傳統瀑布式開發那種「透過文件溝通」的模式,更不是找個issue tracking system(議題追蹤系統)來「開票、領票」(分派、追蹤工作)。

        只是做做敏捷的樣子,心態沒變,還是白搭。

        ***

        友藏內心獨白:讓團隊每個人都動腦真的不容易。

        2019年11月19日 星期二

        Pattern寫作工作坊

        Nov. 19 16:54~17:57


        一起做功德

        PLoP(Pattern Languages of Programs)是模式(Pattern)社群一年一度的聚會,在全球各洲都有類似的活動,在亞洲舉辦的PLoP稱為AsianPLoP。

        AsianPLoP 2020 明年3月4-6日將於台北舉辦,這個活動往年都在日本東京舉辦,明年是第二次移師到台北來。因為Teddy的指導教授是活動主辦人之一,而Teddy也是模式的愛好者,利用此次機會跟AsianPLoP 2020的主辦單位「台灣軟體工程學會」合作,於今年11/30日在泰迪軟體舉辦一天的【Pattern寫作工作坊】,藉此活動讓更多鄉民有機會接觸的模式社群,從了解模式、閱讀別人的模式,進而能夠利用模式來整理自己的知識(寫作模式)。

        本工作坊費用8,000元,由「台灣軟體工程學會」開立收據。所繳交費用可全額抵免明年的AsianPLoP 2020研討會費用。也就是說,報名此工作坊的學員本人明年可以免費參加在北科大舉辦的AsianPLoP 2020 (無論是否投稿,都可參加)。

        工作坊已確定開課,對模式有興趣的朋友,歡迎一起來聽聽模式的歷史、閱讀模式、寫作模式。

        ***

        Teddy的模式之旅

        Teddy從1997年開始接觸到設計模式,當時讀了GoF的《Design Patterns》,實際應用之後覺得設計模式對於軟體開發很有用,但對於書中提到的23個模式也是一知半解。

        幾年後回北科大念博士班,因為之前工作從事e-learning系統與數位教材製作工具的開發,對design patterns也熟,因此指導教授建議Teddy可以研究e-learning領域的patterns。因此,讀了pattern發明人建築師Christopher Alexander一系列的幾本書,同時也開始寫作e-learning patterns。在2004年時,第一次到美國參加PLoP,那時有種劉姥姥逛大觀園的震撼---原來這就是傳說中的PLoP啊。

        這幾年Teddy建議不少人讀Alexander的書,但似乎效果不適很好。Alexander的書有點哲學的味道,剛開始不是那麼容易讀懂。但他的思想的確影響了很多軟體社群的人,Design Patterns、Architecture Patterns、Testing Patterns這些大家都耳熟能詳就不說了,最近1~2年在台灣流行的領域驅動設計(Domain Driven-Design;DDD),發明人Eric Evans所寫作的「藍皮書」,其實就是一本DDD Pattern Languages,書中介紹了數個DDD patterns。


        ▲「藍皮書」本身就是一堆patterns


        ***

        有什麼用

        PLoP研討會已經舉辦了20幾年,學習pattern不是什麼趕流行,而且坦白說有點門檻。對Teddy而言,pattern(特別是pattern language)的訓練有三個主要的好處:

        • 讓人具備一種關照全面看到事情本質的能力。學習pattern很重要的一環就是觀察Forces,體驗Forces。漸漸地,你更能關注事物核心的Quality,而不是表面的Name(因為Quality Without A Name)。
        • 吸納新的知識:很多領域專家,會將他們多年累積的知識寫成pattern。對於真正了解pattern的人,就比較容易吸收這些以pattern所表達的知識。以DDD為例,Teddy很晚(2013)年才開始接觸,但因為Teddy的腦中已經內建 九陽神功 pattern解譯器,對於DDD的學習就比一般鄉民要來得容易與深入一些。
        • 系統性的整理自己的知識與經驗。你覺得自己很厲害,好,然後呢?FB貼廢文、寫blog、參加社群聚會當講者,這些都很好。但,如果要更進一步挑戰自己,將自己的經驗整理成pattern是一個很好的方式。

        ***

        報名

        報名表單在此,錯過這次,下一次就要等……不知道有沒有下一次了。

        ***

        友藏內心獨白:我以為畢業後就不用再寫pattern了XD。

        2019年10月18日 星期五

        學習DDD有感

        Oct. 18 14:46~16:18


        客戶的發問

        昨天下午到客戶家介紹領域驅動設計(Domain-Driven Design;DDD),以及如何運用事件風暴(Event Storming)、Clean Architecture、TDD的工具落實DDD。

        活動結束時有同仁問Teddy:「你演講中提到DDD的好處我們都可以理解,具體上有沒有什麼實際的經驗可以分享?」

        ***

        第一次接觸

        Teddy首先想起幾年前剛接觸DDD,讀到《Domain-Driven Design: Tackling Complexity in the Heart of Software》這本書的情景。由於多年來Teddy已經習慣物件導向分析與設計(Object-Oriented Analysis and Design;OOAD)方法,當時覺得DDD強調建立領域模型(domain model)這件事,和OOAD並沒有什麼不同。另外書中提到的ubiquitous language、bounded context等觀念,其實就是pattern發明人Christopher Alexander在《Notes on the Synthesis of Form 》與《The Timeless Way of Building》等書中所提到的設計方法。至於DDD的Tactical Design(戰術設計)所包含的entity、value object、aggregate、factory、service、layered architecture這些patterns,也不過就是物件導向設計中常見的作法。

        結論就是,這本書稍微翻一下就沒再理它了XD。

        ***

        第二次接觸

        過了一陣子,可能是因為微服務(Microservice)流行的緣故,DDD也跟著流行起來,Teddy同溫層的「臉友」談論DDD的人漸漸多了起來。這也引起Teddy的好奇,想說光看《Domain-Driven Design: Tackling Complexity in the Heart of Software》可能不夠,又買了幾本DDD的書,花了不少時間用力讀了一下。此時對於DDD所提出的這堆patterns的內涵有比較完整的認識,但總覺得,要開發軟體我就用熟知的OOAD就可以了,找不到一個施力點來使用DDD。

        ***

        第三次接觸

        兩年前在指導教授的建議之下,Teddy停掉在北科大資工所兼任的「軟體生命週期管理」課程,改教「軟體架構」。當時剛好Robert C. Martin 的新書《Clean Architecture: A Craftsman's Guide to Software Structure and Design》 出版,Teddy就用這本書當教科書。兩年來為了學習與教學的目的,Teddy設計兩個不同的專案,用了好幾種方式來詮釋Clean Architecture的實作。後來發現搭配DDD的domain model、ubiquitous language、bounded context、event storming、entity、aggregate、repository、service、domain event等pattern,以及Clean Architecture,加上採用TDD/BDD/SBE的方式實作系統,可以很漂亮的串起這些軟體開發方法。

        此時,Teddy才慢慢感受到DDD的威力,特別是ubiquitous language in code的實踐,對於軟體的開發與維護所帶來的好處。另外,aggregate與domain event的使用,讓系統模組自然支援現今主流的分散式架構,這是傳統OOAD方法需要特別而外去關注才做得到的地方。

        ***

        第四次接觸

        約一年前Teddy帶幾位研究生開發一個看板系統,當初一開始設定的目標是要採用Clean Architecture作為此系統的架構。隨著開發活動進行,慢慢發現使用到DDD patterns越來越多,像是原本Clean Architecture就很強調的domain model,以及DDD的ubiquitous language in code、aggregate、repository、service、domain event等。

        多年來Teddy也帶過好多組學生開發軟體,每次都立下宏願,希望用畢生所學,讓學生可以開發出「高品質」軟體。坦白說,多年來這個宏願一直無法實現。一方面研究生時間不多,要修課、寫作業、過日子,而且每年都有學生畢業,真正有時間投入專案開發頂多就是一個寒暑假而已。

        另一方面學生的軟體開發能力與經驗也不足,還在學習當中,不容易要求在短時間內就可以做出設計良好的系統。

        但是,這次開發看板系統Teddy感受到一絲曙光。初期Teddy花了很多時間與學生code review,先確認他們的Clean Architecture沒有走偏(目前還是有點偏,但至少沒有歪的太厲害)。後來隨著一個個user story實作的機會,幫助學生認識如何落實domain model與ubiquitous language in code。Teddy覺得光是這兩點做到,再搭配Clean Architecture,整個軟體系統的可讀性提升好幾個檔次。

        Teddy希望有朝一日能做到「即是是學生所開發的軟體,也能夠達到專業的水準」。

        ***

        老外幫我們做實驗

        Teddy後來告訴發問的同仁:「DDD外國外已經提出10幾年了,有很多老外已經幫我們做過實驗。落實DDD的難度相對而言是比較高的,但是一旦公司與團隊能夠掌握這項方法,以我切身的經驗,我相信可以讓軟體開發更接近『讓軟體變軟』這個目標。」

        落實DDD除了技術上的門檻以外,還有一點非常重要,就是團隊中有沒有領域專家,以及這位(或這一票XD)領域專家是否願意與開發團隊緊密合作,採用迭代與增量的模式,共同建立軟體系統的domain model與ubiquitous language。

        DDD的技術門檻,可以來上泰迪軟體的〈Clean Architecture這樣學就會了實作班〉補足。至於有沒有領域專家願意與團隊緊密合作,就只能靠緣分了XD。

        ***

        放下偶包

        整個學習DDD的過程讓Teddy有一個很深刻的感觸:「很多時候以往的成功經驗可能會阻礙自己學習新的事物。」OOAD太熟導致一開始忽視DDD、Waterfall太熟導致覺得敏捷無用、壓專案時程太方便導致沒有給團隊自主決定的機會、成功的大公司導致無法冒險也沒有容忍失敗的空間。

        所以武俠小說裡面的橋段:「欲練神功,揮劍自宮」都是真的。

        ***

        友藏內心獨白:該不會下一頁是不用自宮,也能成功吧!

        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)。

        ***

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