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的手段。沒有對錯,端看自己的取捨而定。

    ***

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