業務建模
解決問題還是定義問題
業務建模首先是一個定義問題的方法,其次才是解決問題的方法。我們很容易理解解決問題帶來的價值,但也很容易忽略定義問題的力量。如果問題定義得准確,那么實現起來也不會太復雜;相反,如果沒有搞清楚要解決什么問題,就可能需要用各種奇技淫巧去彌補問題定義的不足。我們有時為了逃避真正的思考,願意做任何事。
為了有效定義問題,需要從業務出發,首先嘗試在業務中尋找簡化問題的可能性,然后在技術中尋找對應的解決方案。
明確業務中的關鍵問題,使用易於實現的模型將業務問題表達出來。
業務建模的難點
而一旦涉及軟件開發的核心難點,也就是處理隱藏在業務知識中的核心復雜度,除了清晰得理解業務訴求之外,還需要通過建模的方式對這種復雜度進行簡化與精煉。
業務建模的方法有很多種,比如着眼於數據庫設計的實體關系法(E-R Modeling)、面向對象分析與設計法(Object Oriented Analysis and Design),圍繞知識消化的領域驅動設計(Domain Driven Design)等等。
業務建模的真正難點並不在於建模本身,而是:
- 清晰地定義業務問題,並讓所有干系人都接受你對業務問題的定義;
- 在特定架構的約束下,將模型實現出來。
如何定義問題並讓所有人接受
這里的定義業務問題,是指對業務問題的梳理和總結,明確對業務的影響及產出。
這就需要對業務進行提煉總結,並通過所選用的業務建模方法中蘊含的邏輯框架去驗證它。如果發現漏洞和不足,要及時提出,讓人參與討論。
這里的挑戰不是建模本身,而是如何獲取業務方的信任,並展開有效的討論。
這是能否有效使用業務建模方法的關鍵。
如何在特定架構下實現模型
建模方法有着更長的生命周期,而技術架構卻在不斷演化。如果忽略架構對模型的影響,往往會因為不知道如何處理架構約束,而無法將其運用到實際工作中。
學習業務建模的建議
- 轉移關注點,不必太在意模型是否完美,是否在概念上足夠抽象,是否使用了模式。反而,更應該關注如何圍繞模型,建立有效的溝通、反饋機制。即該怎么將模型中蘊含的邏輯講給別人,並讓別人聽懂、並給出反饋。
- 對架構演化趨勢保持足夠的關注度。一般每3-5年就會出現新的架構風格。過去15年經歷了從單體到多層,再到微服務的改變。在不同的架構風格下,業務建模和模型實現模式的最佳實踐會存在差異,而這些差異很可能會決定建模的成敗。
領域驅動設計
說起業務建模,領域驅動設計(Domain Driven Design)是一個繞不開的話題。
軟件開發的核心難點在於處理隱藏在業務知識中的復雜度,那么模型就是對這種復雜度的簡化與精煉。領域驅動設計是一種模型驅動的設計方法,通過領域模型(Domain Model)捕捉領域知識,使用領域模型構造更易維護的軟件。
模型在領域驅動設計中有三個用途:
- 通過模型反映軟件實現的結構;
- 以模型為基礎形成團隊的統一語言(Ubiquitous Language);
- 把模型作為精粹的知識,以用於傳遞;
這樣的好處:
- 理解了模型,就大致理解了代碼的結構;
- 在討論需求時,研發人員可以很容易明白需要改動的代碼,並對風險與進度有更好的評估;
- 模型比代碼更簡潔、更抽象,有更低的傳遞成本;
領域模型對於業務系統是更好的選擇
“程序=算法+數據結構”這個著名的公式來自於軟件行業早期,當時堆、棧、鏈表等與領域無關的模型,幫開發人員解決了編譯器、內存管理、索引等大量的基礎問題。這讓從業人員形成了一種習慣:將問題轉化為與具體領域無關的數據結構,即構造與具體領域無關的模型。
而領域驅動設計則是對這種習慣的挑戰,它提倡對於業務軟件而言,從業務出發去構造與業務強相關的模型,是一種更好的選擇。
因為如果我們構造的是業務系統,那么團隊中就會引入不具有開發背景的業務方參與;這種情況下,與領域無關的數據結構及算法,業務方是不了解的;這種認知上的差異,會造成團隊溝通的困難,破壞統一語言,加劇知識傳遞的難度。
於是在業務系統中,構造一種專用的模型(領域模型),將相關的業務流程與功能轉化為模型的行為,就能避免開發人員與業務的認知差異。所以領域模型對於業務系統是更好的選擇。
這種理念的轉變是以面向對象技術開始,直到DDD被行業采納,才最終完成的。
知識消化
不同於軟件行業對數據結構的嘗試研究與積累,在不同的領域中該使用什么樣的領域模型,並沒有一個現成的做法。因而在DDD中,Eric Evans提倡了一種叫知識消化(Knowledge Crunching)的方法幫助我們去提煉領域模型。多年來產生了多種知識消化的方法,但它們在宏觀上仍然遵從知識消化的五個步驟。
知識消化的五個步驟
- 關聯模型與實現;
- 基於模型提取統一語言;
- 開發富含知識的模型;
- 精煉模型;
- 頭腦風暴與試驗;
其中“關聯模型與實現”是知識消化可以順利進行的前提與基礎,它將模型與代碼統一在一起,使得對模型的修改,等同於對代碼的修改。
“基於模型提取統一語言”則會將業務方變成模型的使用者,通過統一語言進行需求討論,實際上就是通過模型對需求進行討論。
后面三步則構成了一個提煉知識的循環:通過統一語言討論需求;發現模型中的缺失或者不恰當的概念,精煉模型以反映業務的實踐情況;對模型的修改引發了統一語言的改變,再以試驗和頭腦風暴的態度,使用新的語言以驗證模型的准確。如此循環往復,不斷完善模型與統一語言:
以上五步可以總結為“兩關聯,一循環”:
- 模型與軟件實現關聯;統一語言與模型關聯;
- 提煉知識的循環;
模型與軟件實現關聯
與模型關聯的實現方法,是一種面向對象的編程風格,即“富含知識的模型”(Knowledge Rich Model),與之相對的,過程式的編程風格,則是一種與模型無關的實現方式。面向對象對象技術在表達領域模型上有天生的優勢。
從貧血模型到富含知識的模型
在“貧血對象模型”中,對象僅僅對簡單的數據進行封裝,而關聯關系和業務計算都散落在對象的范圍之外。
比如極客時間的用戶和訂閱專欄是一對多的關系,“貧血對象模型”類似下面的偽代碼:
class UserDAO{
public User find(long id){
var query = connection.createConnection();
ResultSet result = query.executeQuery();
return new User(rs.getLong(1),rs.getString(2));
}
}
class SubscriptionDAO{
public List<Subscription> findSubscriptionsByUserId(long userId){
...
}
}
這樣的代碼風格沿用過程式方式,沒有發揮面向對象技術的優勢。
與之相對的則是“充血模型”,即“與某個概念相關的主要行為與邏輯,都被封裝到了對應的領域對象中”。這也就是DDD中強調的“富含知識的模型”。按照這種風格,上面的代碼可以改寫為:
class User{
public List<Subscription> getSubscriptions(){
...
}
}
class UserRepository{
public User findById(long id){
}
}
從這段代碼很容易看出:User是聚合根,Subscription被聚合到User中,無法獨立存在。
通過聚合關系表達業務概念
在建模中采用的關聯關系中,聚合關系表示關聯在一起的對象,從概念上講是一個整體。在User與Subscription的關系中,如果脫離了Subscription,Use只是單純的表示個人信息,而脫離了User,Subscription也只是專欄內容,只有把兩者放在放在一起,才能表達需要的含義:User訂閱的Subscription,它們是一個整體。
比起貧血模型的實現方式,充血模型將模型與軟件實現完全對應在一起的,無論是結構還是行為。這簡化了理解代碼的難度。只要在概念上理解了模型,就會大致理解代碼的實現方法與結構。
修改模型就是修改代碼
關聯模型與軟件實現,最終的目的是:修改模型就是修改代碼;修改代碼就是修改模型。
在知識消化中,提煉知識的重構是圍繞模型展開的。如果對於模型的修改,無法直接映射到軟的實現上(比如貧血模型),那么提煉知識的重構循環就必須停下來,等待這個同步的過程;否則模型與軟件將的割裂,就會將模型本身分裂為更接近業務的分析模型,以及更接近實現的設計模型,這個時候分析模型就會逐漸退化為純粹的溝通需求的工具,而一旦脫離了實現的約束,分析模型就會變的天馬行空,不着邊際。
這套做法被無數案例證明難以成功,於是才有了后來的DDD等使用統一模型的方法。
統一語言是必要的嗎
DDD的核心理念,即在業務系統中使用與問題域相關的模型,而不是通用的數據結構去描述問題。
既然領域驅動設計是一種模型驅動的設計方法,為什么不能讓業務方直接去使用模型,而要通過統一語言呢?
統一語言是基於領域模型的共同語言
統一語言(Ubiquitous Language)是基於領域模型的共同語言,業務方與技術方通過共同語言描述業務規則與需求變動,為雙方提供了協作、溝通的基礎。這里的業務方泛指一切非最終軟件實現者,比如客戶、產品經理、業務分析師、解決方案架構師、用戶體驗設計師等。
共同語言也有很多種形式,如用戶畫像、用戶旅程、數據字典等;但DDD的統一語言特指根據領域模型構造的共同語言。
回到開始的問題:“既然領域驅動設計是一種模型驅動的設計方法,為什么不能讓業務方直接去使用模型,而要通過統一語言呢?”
這是因為這樣做的效果不理想:
- 對於研發人員來說,直接使用模型有很多好處,但對業務方來說卻不夠直觀。業務方大多習慣從業務角度如流程、交互、功能、價值等去描述系統,而模型則偏重於數據角度,描述了在不同業務維度下,數據將會如何變化,以及如何支撐對應的計算與統計,業務維度被模型的抽象隱藏了,使業務方無法從模型中直接感知到業務維度。
- 模型是從已知需求中總結提煉的知識,這就意味着模型無法表達未知需求中尚未提煉的知識。所以需要一個相對允許歧義與未知的隔離層,來幫助我們發現和反饋模型的不足。
- 總結來說,需要一種能與模型關聯的共同語言,它既能讓模型在核心位置扮演關鍵角色,又能彌合視角差異,並提供足夠的緩沖。從模型中提取的統一語言,覆蓋了領域模型中的概念與邏輯,還提供了必要的補充,以幫助業務方理解模型。同時,統一語言也扮演了試驗田的角色,其中出現的未被提取的知識,將觸發提煉知識的循環,逐步完善模型。
相對於模型的精確,統一語言的模糊反而更能滿足人與人之間交流的需求。
修改代碼就是改變統一語言
統一語言是從模型中提取的,於是,修改代碼就是修改模型,也會改變統一語言。所以,不僅是因為需求變更,代碼重組引起的代碼修改,最終也會反映到統一語言中。
這相當於讓開發方來定義業務問題,並要求業務方按照開發提出的模型來描述業務和需求。一旦將軟件實現與領域模型關聯,那么對實現的簡化,也就是對領域問題的簡化;從實現中抽取的抽象概念,也就是從問題域中抽取的抽象概念。
這種看起來匪夷所思的情況,實際上一直都在發生,技術方一直都在定義業務,只是沒有合理的途徑讓業務方了解並接納。
統一語言及其背后的領域模型從觀念上改變了這一狀況,它將大家從各自的領地,也就是業務與技術中,拉到了一個中間地帶。雙方有了差不多的話語權。
而一旦業務方接受了統一語言,實際上就是放棄了對業務100%的控制權,也就意味着統一語言在業務上能夠賦予開發人員更大的控制權,賦予了開發人員定義業務的權力。不多在帶來權力的同時,也隱含着額外的義務,即借由統一語言,在提煉知識的循環中,接受業務方的監督與反饋;如果業務方不同意技術方修改的模型,可以修正這種誤差,也就是技術人員也喪失了對代碼100%的控制權。
總結來說,統一語言提供了一種更好的協同方式的可能性,建立了技術反饋業務的途徑,降低了知識消化過程失敗的風險。
一個簡單的統一語言提案
統一語言本身的形式並不重要,或者說並沒有統一的形式。重要的是,統一語言與領域模型關聯、且多方認可並承認對其的集體所有權時,統一語言才能成為真正的統一語言,在此之前,都只是統一語言提案。
統一語言可以包含以下內容:
- 源自領域模型的概念與邏輯
- 界限上下文(Bounded Context)
- 系統隱喻
- 職責的分層
- 模式與慣用法
以極客時間專欄中User與Subscription的模型為例,可以是:
這個模型非常簡單,無法包含上述所有的內容,但根據這個模型,可以從中提取到對應的領域概念,即領域模型中的實體:
- 用戶(User),是指所有在極客時間注冊過的人;
- 訂閱的專欄(Subscription),是指用戶付費過的專欄;
同時還有一個業務邏輯:
- 用戶可以訂閱多個專欄
界限上下文、系統隱喻等其他幾項都可以看作對業務維度的補充與展開。將它們引入通過統一語言后,可以幫助業務方更直觀地理解模型:
- 系統隱喻就是在價值與業務模式維度上的補充與擴展,比如“某某領域的淘寶”這樣的說法,言簡意賅地表示了產品的願景、價值定位、核心模式;
- 職責分層,關注穩定性,哪些是穩定的哪些是易變的;
- 模式與慣用法是業務規則、流程與實現模式;
- 界限上下文是圍繞某些模型設置的邊界,所有人對於如何對於如何利用邊界中的模型有清晰明確的想法,這個想法借由這個邊界保持一致,不受外界信息干擾。界限上下文是現實中某種決定在模型上的反應。這里存在訂閱子域,訂閱也是一個界限上下文。
綜上,抽取的語言是:
- 用戶(User)
- 訂閱的專欄(Subscription)
- 用戶可以訂閱多個專欄
- 訂閱
統一語言是在使用中被確立的。通過定義與解釋,我們使這些詞語在其所使用的是下文中沒有歧義,再通過這些基礎詞匯,來描述業務中的行為或規則,慢慢就可以將其確立為統一語言了。
這個語言可以用來編寫用戶需求:
作為一個User,當我查閱購買過的Subscription時,可以看到其中的教學內容。
可以用來描述行為驅動開發的測試:
當User已購買過某個Subscription,那么當其訪問時,就不需要再為內容付費。
或是實例化需求的說明等等。重點是要在所有可能的地方使用這個語言。只有當所有工種角色都接受它,使用它去描述業務和系統的時候,它才會真正成為統一語言。
領域驅動設計,逼着業務方和實現方像玩蹺蹺板一樣共同構建業務系統,從協作角度看,這個游戲的特點是,【一個人沒法玩】。 板≈共同語言,支點≈模型,以“支點”為中心,以“板”為媒介,雙方你一下我一下地玩。“板”+“支點”很大程度上定義了游戲的基本規則。
如何理解DDD
從技術方的角度來說,統一語言和它背后的領域模型,賦予了研發人員通過重構定義業務的能力。那么從業務方的角度來看,如何利用統一語言去影響研發方呢?這就要涉及到“兩關聯一循環”中提煉知識的循環了,它是DDD的核心流程。
將提煉知識的循環看做開發流程
提煉知識的循環大致為:
- 首先,通過統一語言討論需求;
- 而后,發現模型中的缺失或者不恰當的概念,然后精煉模型,以反映業務的實際情況;
- 接着,對模型的修改會引發統一語言的改變,再以試驗和頭腦風暴的態度,使用新的語言以驗證模型的准確性。
這與重構的過程類似:
1. 發現壞味道以明確改進方向
2. 嘗試消除味道
3. 通過壞味道是否消失判斷改進是否成功
也與TDD的過程類似:
1. 構造失敗測試表明需求變化
2. 修改代碼實現
3. 通過測試以證明滿足需求
提煉知識的循環可以看做以模型為最終產出物的研發流程;如果是重構,業務方就是發現壞味道的人,如果是TDD,業務方就是構造測試的人。
示例
之前極客時間的例子里,抽取的統一語言是:
- 用戶(User)
- 訂閱的專欄(Subscription)
- 用戶可以訂閱多個專欄
- 訂閱
並用這個語言描述了需求:
作為一個User,當我查閱購買過的Subscription時,可以看到其中的教學內容。
當User已購買過某個Subscription,那么當其訪問時,就不需要再為內容付費。
上述語言中只存在購買過的專欄,但如果業務方有了這有一個新的需求:
作為一個User,當我對某個專欄的內容感興趣時,我可以購買這個專欄,使其成為我購買過的專欄
這條業務描述中出現了之前的語言無法表述的概念:未購買的專欄、內容;這意味着當前的統一語言存在無法表述的業務,模型存在漏洞。為了彌補漏洞,團隊可以頭腦風暴,提取缺失的概念,修改已有的模型,並由業務方判斷修改后的模型是不是足夠准確表達了業務概念與邏輯。最后可能得到這樣的新模型:
重新提取統一語言:
- 用戶(User),極客時間注冊過的人
- 付費內容(Content)是課程的承載,可以是文字、視頻、音頻
- 作者(Author)付費內容的創作者
- 專欄(Column)是一組付費內容(Content)的集合,由極客時間的作者(Author)提供
- 訂閱的專欄(Subscription),用戶付費過的專欄
- 用戶可以訂閱多個專欄
- 專欄中可以包含多個付費內容
- 同一作者可以發布多個專欄
- 訂閱
- 課程發布
於是之前無法被描述的業務邏輯變成了:
作為一個User,當我對某個專欄(Column)的內容(Content)感興趣時,我可以購買這個專欄,使其成為我購買過的專欄(Subscription)
研發方與業務方的協同效應
提煉知識的循環可以看做以模型為最終產出物的研發流程;另外,模型與代碼是關聯的,對模型的修改也就是對代碼的修改。如果說統一語言與模型的關聯賦予了技術方定義業務的權力,那么提煉知識的循環也賦予了業務方影響軟件實現的權利。
綜合來看,業務方與技術方的權利義務有:
- 對於技術方來說,通過統一語言獲得了定義業務的權力,但同時也要承擔在提煉知識的循環中接受業務影響實現的業務;
- 對於業務方來說,提煉知識的循環賦予了它們影響軟件實現的權利,同樣需要承擔接受研發方通過統一語言定義業務概念的業務;
可見,知識消化以一種權責明確的方式,讓業務方與技術方參與到對方的工作中,給與了業務方和技術方一種更好的協同方式。
這樣做的好處有:
- 首先,軟件開發的核心難度在於處理隱藏在業務知識中的復雜度。為了處理這種復雜度,需要打破知識壁壘(統一語言),如果雙方對彼此的知識域有基礎的了解(模型),那么知識傳遞與溝通的效率會更高。
- 其次,對於復雜的問題(沒有現成答案的問題),需要快速的反饋周期來試錯(提煉知識的循環)
當討論DDD時,我們到底在說什么
DDD至少可以指代一種建模法、一種協同工作方式、一種價值觀,以及上述三種按照隨意比例的混合。
迭代式試錯建模法
針對同一個問題,不同人得到的模型可能是不一樣的,模型是對問題的抽象,沒有對錯,只有角度不同。正如DDD提出者Eric Evans所說:“知識消化是一個探索的過程,你不可能知道將會在哪里停止”,言外之意“你可能不知道當你停止時,得到的是垃圾還是寶藏”,這只能交給建模者的抽象能力了。
DDD之所以會變成如此不靠譜的建模法,是因為它嘗試解決的問題是復雜問題,即沒有現成答案的問題,那么迭代式試錯法就是唯一可行的方法。
具有協同效應的工作方式
權利與義務的對等,構成了協同的基礎:
- 對於技術方來說,通過統一語言獲得了定義業務的權力,但同時也要承擔在提煉知識的循環中接受業務影響實現的業務;
- 對於業務方來說,提煉知識的循環賦予了它們游戲軟件實現的權利,同樣需要承擔接受研發方通過統一語言定義業務概念的業務;
不過這種協同方式是否能發揮作用,很大程度上依賴與團隊對模型、對統一語言的理解與接納,需要團隊逐步在工作中使用它。這依賴於建模者的變革管理能力。而知識消化希望通過頭腦風暴與試驗的方法,簡化這種變革,實際結果仍然因人而異。
價值觀體系
DDD背后的價值觀為:
- DDD是一種模型驅動的設計方法,模型應處在核心;
- 兩關聯一循環:業務與技術圍繞着模型的協調
從這個角度來說,DDD的定義太過寬泛,以至於可以使用DDD去討論任何事情。所以更建議用“知識消化”來代替DDD,因為“知識消化“除了上述兩條外,還框定了具體的實踐架構:如何更好地構建富含知識的模型、如何保持模型與軟件實現的關聯、如何有效地提取領域語言、如何推動業務方更主動地參與提煉知識的循環。
DDD的特點
- DDD作為建模法,是一種迭代試錯法,是一種保底可行,但效率不高的方法;如果技術方與業務方沒有足夠的信任,可能就迭代不起來了;
- DDD作為一種協同工作方式,提供了相當精彩的思路,統一語言的概念對行業產生了深遠的影響;
- 統一語言並不容易實現,而一旦無法形成真正的統一語言,提煉知識的循環也就無法進行,這個方法也就失敗了;
- DDD更適合敏捷團隊,因為無論是通過統一語言協同交互、還是提煉知識的循環,都需要對這種跨工種協同以及漸進式改進方法有足夠的認同。對於敏捷團隊方法實施較好的團隊,這些都不是問題。否則會有較大的變革成本。
參考資料
極客時間:如何落地業務建模 徐昊