界限上下文识别
限界上下文就是“邊界”,這與面向?qū)ο笤O(shè)計中的職責(zé)分配其實(shí)是同一道理。限界上下文的識別并不是一蹴而就的,需要演化和迭代,結(jié)合著我對限界上下文的理解,我認(rèn)為通過從業(yè)務(wù)邊界到工作邊界再到應(yīng)用邊界這三個層次抽絲剝繭,分別以不同的視角、不同的角色協(xié)作來運(yùn)用對應(yīng)的設(shè)計原則,會是一個可行的識別限界上下文的過程方法。當(dāng)然,這個過程相對過重,如果僅以此作為輸出限界上下文的方法,未免有些得不償失。需要說明的是,這個過程除了能夠幫助我們更加準(zhǔn)確地識別限界上下文之外,還可以幫助我們分析需求、識別風(fēng)險、確定架構(gòu)方案。整體過程如下圖所示:
?業(yè)務(wù)邊界識別限界上下文
領(lǐng)域驅(qū)動設(shè)計圍繞著“領(lǐng)域”來開展軟件設(shè)計。在明確了系統(tǒng)的問題域和業(yè)務(wù)期望后,開發(fā)團(tuán)隊(duì)與領(lǐng)域?qū)<医?jīng)過充分地溝通與交流,可以梳理出主要的業(yè)務(wù)流程,這些業(yè)務(wù)流程體現(xiàn)了各種參與者在這個過程中通過業(yè)務(wù)活動共同協(xié)作,最終完成具有業(yè)務(wù)價值的領(lǐng)域功能。顯然,業(yè)務(wù)流程結(jié)合了參與角色(Who)、業(yè)務(wù)活動(What)和業(yè)務(wù)價值(Why)。在業(yè)務(wù)流程的基礎(chǔ)上,我們就可以抽象出不同的業(yè)務(wù)場景,這些業(yè)務(wù)場景又由多個業(yè)務(wù)活動組成,我們可以利用前面提到的領(lǐng)域場景分析方法剖析場景,以幫助我們識別業(yè)務(wù)活動,例如采用用例對場景進(jìn)行分析,此時,一個業(yè)務(wù)活動實(shí)則就是一個用例。
例如,在針對一款文學(xué)閱讀產(chǎn)品進(jìn)行需求分析時,我們得到的業(yè)務(wù)流程為:
登錄讀者根據(jù)作品名或者作者名查詢自己感興趣的作品。在找到自己希望閱讀的作品后,開始閱讀。若閱讀的作品為長篇,可以按照章節(jié)閱讀,倘若作品為收費(fèi)作品,則讀者需要支付相應(yīng)的費(fèi)用,支付成功后可以閱讀購買后的作品。在閱讀時,倘若讀者看到自己喜歡的句子或段落,可以作標(biāo)記,也可以撰寫讀書筆記,還可以將自己喜歡的內(nèi)容分享給別的朋友。讀者可以對該作品和作者發(fā)表評論,關(guān)注自己喜歡的作品和作者。注冊用戶可以申請成為駐站作者。審核通過的作者可以在創(chuàng)作平臺上發(fā)布自己的作品,發(fā)布作品時,可以根據(jù)需要設(shè)置作品的章節(jié)。作者可以在發(fā)布作品之前預(yù)覽作品,無論作品是否已經(jīng)發(fā)布,都可以對作品的內(nèi)容進(jìn)行修改。作者可以設(shè)置自己的作品為收費(fèi)或免費(fèi)作品,并自行確定閱讀作品所需的費(fèi)用。如果是新作品發(fā)布,系統(tǒng)會發(fā)送消息通知該作者的關(guān)注者;若連載作品有新章節(jié)發(fā)布,系統(tǒng)會發(fā)送消息通知該作品的關(guān)注者。駐站作者可以為自己的作品建立作品讀者群,讀者可以申請加入該群,加入群的讀者與作者可以在線實(shí)時聊天,也可以發(fā)送離線信息,或者將自己希望分享的內(nèi)容發(fā)布到讀者群中。注冊用戶之間可以發(fā)起一對一的私聊,也可以直接給注冊用戶發(fā)送私信。通過對以上業(yè)務(wù)流程進(jìn)行分析,結(jié)合在各個流程環(huán)節(jié)中需要的知識以及參與角色的不同,可以劃分如下業(yè)務(wù)場景:
閱讀作品創(chuàng)作作品支付社交消息通知注冊與登錄可以看到,業(yè)務(wù)流程是一個由多個用戶角色參與的動態(tài)過程,而業(yè)務(wù)場景則是這些用戶角色執(zhí)行業(yè)務(wù)活動的靜態(tài)上下文。從業(yè)務(wù)流程中抽象出來的業(yè)務(wù)場景可能是交叉重疊的,例如在讀者閱讀作品流程與作者創(chuàng)作流程中,都牽涉到支付場景的相關(guān)業(yè)務(wù)。
接下來,我們利用領(lǐng)域場景分析的用例分析方法剖析這些場景。我們往往通過參與者(Actor)來驅(qū)動對用例的識別,這些參與者恰好就是參與到場景業(yè)務(wù)活動的角色。根據(jù)用例描述出來的業(yè)務(wù)活動應(yīng)該與統(tǒng)一語言一致,最好直接從統(tǒng)一語言中擷取。業(yè)務(wù)活動的描述應(yīng)該精準(zhǔn)地表達(dá)領(lǐng)域概念,且通過盡可能簡潔的方式進(jìn)行描述,通常格式為動賓形式。以閱讀作品場景為例,可以包括如下業(yè)務(wù)活動:
查詢作品收藏作品關(guān)注作者瀏覽作品目錄閱讀作品標(biāo)記作品內(nèi)容撰寫讀書筆記評價作品評價作者分享選中的作品內(nèi)容分享作品鏈接購買作品一旦準(zhǔn)確地用統(tǒng)一語言描述出這些業(yè)務(wù)活動,我們就可以從如下兩個方面識別業(yè)務(wù)邊界,進(jìn)而提煉出初步的限界上下文:
語義相關(guān)性功能相關(guān)性語義相關(guān)性
從語義角度去分析業(yè)務(wù)活動的描述,倘若是相同的語義,可以作為歸類的特征。語義相關(guān)性主要來自于描述業(yè)務(wù)活動的賓語。例如,前述業(yè)務(wù)活動中的查詢作品、收藏作品、分享作品、閱讀作品都具有“作品”的語義,基于這一特征,我們可以考慮將這些業(yè)務(wù)活動歸為同一類。
識別語義相關(guān)性的前提是準(zhǔn)確地使用統(tǒng)一語言描述業(yè)務(wù)活動。在描述時,應(yīng)盡量避免使用“管理(manage)”或“維護(hù)(maintain)”等過于抽象的詞語。抽象的詞語容易讓我們忽視隱藏的領(lǐng)域語言,缺少對領(lǐng)域的精確表達(dá)。例如,在文學(xué)閱讀產(chǎn)品中,我們不能寬泛地寫出“管理作品”、“管理作者”、“維護(hù)支付信息”等業(yè)務(wù)活動,而應(yīng)該挖掘業(yè)務(wù)含義,只有如此才能得到諸如收藏作品、撰寫作品、發(fā)布作品、設(shè)置作品收費(fèi)模式、查詢支付流水、對賬等符合領(lǐng)域知識的描述。當(dāng)然,這里也有一個業(yè)務(wù)活動層次的問題。在進(jìn)行業(yè)務(wù)分析時,若我們發(fā)現(xiàn)只能使用“管理”或“維護(hù)”之類的抽象字眼來表述該用戶活動時,則說明我們選定的用戶活動層次過高,應(yīng)該繼續(xù)細(xì)化。細(xì)化后的業(yè)務(wù)活動既能更好地表達(dá)領(lǐng)域知識,又能讓我們更好地按照語義相關(guān)性去尋找業(yè)務(wù)的邊界,可謂一舉兩得。
在進(jìn)行語義相關(guān)性判斷時,還需要注意業(yè)務(wù)活動之間可能存在不同的語義相關(guān)性。例如,在文學(xué)閱讀產(chǎn)品中,查詢作品、閱讀作品與撰寫作品具有“作品”的語義相關(guān),而評價作品與評價作者又具有“評價”的語義相關(guān),究竟應(yīng)該以哪個語義為準(zhǔn)呢?沒有標(biāo)準(zhǔn)!我們只能按照相關(guān)性的耦合程度進(jìn)行判斷。如果我們將評價視為一個相對獨(dú)立的限界上下文,則評價作品與評價作者放入評價上下文會更好。
功能相關(guān)性
從功能角度去分析業(yè)務(wù)活動是否彼此關(guān)聯(lián)和依賴,倘若存在關(guān)聯(lián)和依賴,可以作為歸類的特征,這種關(guān)聯(lián)性,代表了功能之間的相關(guān)性。倘若兩個功能必須同時存在,又或者缺少一個功能,另一個功能是不完整的,則二者就是功能強(qiáng)相關(guān)的。通常,這種功能相關(guān)性極具有欺騙性,因?yàn)橄到y(tǒng)總是包含這樣那樣彼此依賴的功能。要判斷這種依賴關(guān)系的強(qiáng)弱,并不比分析人與人之間的關(guān)系簡單。倘若我們運(yùn)用用例分析方法,就可以通過用例之間的關(guān)系來判別功能相關(guān)性,如用例的包含與擴(kuò)展關(guān)系,其中包含關(guān)系展現(xiàn)了功能的強(qiáng)相關(guān)性。所謂“功能相關(guān)性”,指的就是職責(zé)的內(nèi)聚性,強(qiáng)相關(guān)就等于高內(nèi)聚。故而從這個角度看,功能相關(guān)性的判斷標(biāo)準(zhǔn)恰好符合“高內(nèi)聚、松耦合”的設(shè)計原則。
仍然以前面提到的文學(xué)閱讀產(chǎn)品為例。發(fā)布作品與驗(yàn)證作品內(nèi)容是功能相關(guān)的,且屬于用例的包含關(guān)系,因?yàn)槿绻麤]有對發(fā)布的作品內(nèi)容進(jìn)行驗(yàn)證,就不允許發(fā)布作品。對于這種強(qiáng)相關(guān)的功能,我們通常都會考慮將其歸入到同一個限界上下文。又例如發(fā)布作品與設(shè)置作品收費(fèi)模式是功能相關(guān)的,但并非強(qiáng)相關(guān),因?yàn)樵O(shè)置作品收費(fèi)模式并非發(fā)布作品的前置約束條件,屬于用例中的擴(kuò)展關(guān)系。但由于二者還存在語義相關(guān)性,因而將其放入到同一個限界上下文中也是合理的。
兩個相關(guān)的功能未必一定屬于同一個限界上下文。例如,購買作品與支付購買費(fèi)用是功能相關(guān)的,且前者依賴于后者,但后者從領(lǐng)域知識的角度判斷,卻應(yīng)該分配給支付上下文,我們非但不能將其緊耦合在一起,還應(yīng)該竭盡所能降低二者之間的耦合度。因此,我在識別限界上下文時,僅僅將“功能相關(guān)性”作為一種可行的參考,它并不可靠,卻能給你一些提醒。事實(shí)上,功能相關(guān)性往往會與上下文之間的協(xié)作關(guān)系有關(guān)。由于這種功能相關(guān)性恰恰對應(yīng)了用例之間的包含與擴(kuò)展關(guān)系,它們往往又可成為識別限界上下文邊界的關(guān)鍵點(diǎn)。我在后面講解上下文映射時還會詳細(xì)闡釋。
為業(yè)務(wù)邊界命名
無論是語義相關(guān)性還是功能相關(guān)性,都是分類業(yè)務(wù)活動的一種判斷標(biāo)準(zhǔn)。一旦我們將識別出來的業(yè)務(wù)活動進(jìn)行歸類,就自然而然地為它們劃定了業(yè)務(wù)邊界,接下來,我們需要對劃定的業(yè)務(wù)邊界進(jìn)行命名,這個命名的過程其實(shí)就是識別所有業(yè)務(wù)活動共同特征,并以最準(zhǔn)確地名詞來表達(dá)該特征。倘若我們劃分的業(yè)務(wù)活動欠妥當(dāng),對這個業(yè)務(wù)邊界命名就會成為一種巨大的挑戰(zhàn)。例如,我們從建立讀者群、加入讀者群,發(fā)布群內(nèi)消息、實(shí)時聊天、發(fā)送離線消息、一對一私聊與發(fā)送私信等業(yè)務(wù)活動找到“社交”的共同特征,因而得到社交上下文。但如果我們將閱讀作品、收藏作品與關(guān)注作者、查看作者信息放在一個業(yè)務(wù)邊界內(nèi),命名就變得有些棘手了,我們總不可能稱呼其為“作品與作者”上下文吧!因此,對業(yè)務(wù)邊界的命名可以算作是對限界上下文識別的一種檢驗(yàn)手段。
整體而言,從業(yè)務(wù)邊界識別上下文的重點(diǎn)在于“領(lǐng)域”。若理解領(lǐng)域邏輯有誤,就可能影響限界上下文的識別。因此,這個階段需要開發(fā)團(tuán)隊(duì)與領(lǐng)域?qū)<揖o密合作,這個階段也將是一個充分討論和分析的過程。它是一個迭代的過程。很多時候,如果我們沒有真正去實(shí)現(xiàn)這些限界上下文,我們有可能沒有完全正確地理解它。當(dāng)我們距離真正理解業(yè)務(wù)還有距離的時候,不妨先“草率”地規(guī)劃它,待到一切都明朗起來,再尋機(jī)重構(gòu)。
從工作邊界識別限界上下文
正如架構(gòu)設(shè)計需要多個視圖來全方位體現(xiàn)架構(gòu)的諸多要素,我們也應(yīng)借助更多的角度全方位分析限界上下文。如果說為限界上下文劃分業(yè)務(wù)邊界,更多的是從業(yè)務(wù)相關(guān)性(內(nèi)聚)判斷業(yè)務(wù)的歸屬,那么基于團(tuán)隊(duì)合作劃分工作邊界可以幫助我們確定限界上下文合理的工作粒度。
倘若我們認(rèn)可第 3-2 課中提及的三個原則或?qū)嵺`:2PTs 規(guī)則、特性團(tuán)隊(duì)、康威定律,則意味著項(xiàng)目經(jīng)理需要將一個限界上下文要做的工作分配給大約 7~10 人的特性團(tuán)隊(duì)。如此看來,對限界上下文的粒度識別就變成了對工作量的估算。我們并沒有嚴(yán)謹(jǐn)?shù)乃惴ㄈ?zhǔn)確估算工作量,可是對于一個有經(jīng)驗(yàn)的項(xiàng)目經(jīng)理(或者技術(shù)負(fù)責(zé)人),要進(jìn)行工作量的大致估算,還是能夠辦到的。當(dāng)我們發(fā)現(xiàn)一個限界上下文過大,又或者特性團(tuán)隊(duì)的工作分配不均勻時,就應(yīng)該果斷對已有限界上下文進(jìn)行切分。
工作分配的基礎(chǔ)在于“盡可能降低溝通成本”,遵循康威定律,溝通其實(shí)就是項(xiàng)目模塊之間的依賴,這個過程同樣不是一蹴而就的。康威認(rèn)為:
在大多數(shù)情況下,最先產(chǎn)生的設(shè)計都不是最完美的,主導(dǎo)的系統(tǒng)設(shè)計理念可能需要更改。因此,組織的靈活性對于有效的設(shè)計有著舉足輕重的作用,必須找到可以鼓勵設(shè)計經(jīng)理保持他們的組織精簡與靈活的方法。
特性團(tuán)隊(duì)正是用來解決這一問題的。換言之,當(dāng)我們發(fā)現(xiàn)團(tuán)隊(duì)規(guī)模越來越大,失去了組織精簡與靈活的優(yōu)勢,實(shí)際上就是在傳遞限界上下文過大的信號。項(xiàng)目經(jīng)理對此需要有清醒認(rèn)識,當(dāng)團(tuán)隊(duì)規(guī)模違背了 2PTs 時,就該坐下來討論一下如何細(xì)分團(tuán)隊(duì)的問題了。因此,按照團(tuán)隊(duì)合作的角度劃分限界上下文,其實(shí)是一個動態(tài)的過程、演進(jìn)的過程。
我在給某音樂網(wǎng)站進(jìn)行領(lǐng)域驅(qū)動設(shè)計時,通過識別業(yè)務(wù)相關(guān)性劃分了如下限界上下文。
Media Player(online & offline):提供音頻和視頻文件的播放功能,區(qū)分在線播放與離線播放;Music:與音樂相關(guān)的業(yè)務(wù),包括樂庫、歌單、歌詞;FM Radio:電臺;Live:直播;MV:短視頻和 MV;Singer:歌手;Musician:音樂人,注意音樂人與歌手的區(qū)別;Music Community:音樂社區(qū);File Sharing:包括下載和傳歌等與文件有關(guān)的功能;Tag:支持標(biāo)簽管理,包括音樂的分類如最新、話題等分類標(biāo)簽還有歌曲標(biāo)簽;Loyalty:與提高用戶粘度有關(guān)的功能,如關(guān)注、投票、收藏、歌單等功能;Utilities:音樂工具,包括音效增強(qiáng)等功能;Recommendation:推薦;Search:對整個音樂網(wǎng)站內(nèi)容的搜索,包括對人、歌曲、視頻等內(nèi)容的搜索;Activity:音樂網(wǎng)站組織的活動;Advertisement:推廣與廣告;Payment:支付。在識別限界上下文時,我將直播(Live)視為與音樂、電臺、MV 短視頻同等層次的業(yè)務(wù)分類,然而,殊不知該音樂網(wǎng)站直播模塊的開發(fā)團(tuán)隊(duì)已經(jīng)隨著功能的逐漸增強(qiáng)發(fā)展到了接近 200 人規(guī)模的大團(tuán)隊(duì),這顯然不是一個限界上下文邊界可以控制的規(guī)模。即使屬于直播業(yè)務(wù)的業(yè)務(wù)活動都與直播領(lǐng)域知識有關(guān),我們也應(yīng)該基于 2PTs 原則對直播限界上下文作進(jìn)一步分解,以滿足團(tuán)隊(duì)管理以及團(tuán)隊(duì)成員充分溝通的需要。
如果我們從團(tuán)隊(duì)合作層面看待限界上下文,就從技術(shù)范疇上升到了管理范疇。Jurgen Appelo 在《管理 3.0:培養(yǎng)和提升敏捷領(lǐng)導(dǎo)力(Management 3.0: Leading Agile Developers,Developing Agile Leaders)》這本書中提到,一個高效的團(tuán)隊(duì)需要滿足兩點(diǎn)要求:
共同的目標(biāo)團(tuán)隊(duì)的邊界
雖然 Jurgen Appelo 在提及邊界時,是站在團(tuán)隊(duì)結(jié)構(gòu)的角度來分析的;可在設(shè)計團(tuán)隊(duì)組織時確定工作邊界的原則,恰恰與限界上下文的控制邊界暗暗相合。總結(jié)書中對邊界的闡釋,大致包括:
團(tuán)隊(duì)成員應(yīng)對團(tuán)隊(duì)的邊界形成共識,這就意味著團(tuán)隊(duì)成員需要了解自己負(fù)責(zé)的限界上下文邊界,以及該限界上下文如何與外部的資源以及其他限界上下文進(jìn)行通信。團(tuán)隊(duì)的邊界不能太封閉(拒絕外部輸入),也不能太開放(失去內(nèi)聚力),即所謂的“滲透性邊界”,這種滲透性邊界恰恰與“高內(nèi)聚、松耦合”的設(shè)計原則完全契合。針對這種“滲透性邊界”,團(tuán)隊(duì)成員需要對自己負(fù)責(zé)開發(fā)的需求“抱有成見”,在識別限界上下文時,“任勞任怨”的好員工并不是真正的好員工。一個好的員工明確地知道團(tuán)隊(duì)的職責(zé)邊界,他應(yīng)該學(xué)會勇于承擔(dān)屬于團(tuán)隊(duì)邊界內(nèi)的需求開發(fā)任務(wù),也要敢于推辭職責(zé)范圍之外強(qiáng)加于他的需求。通過團(tuán)隊(duì)每個人的主觀能動,就可以漸漸地形成在組織結(jié)構(gòu)上的“自治單元”,進(jìn)而催生出架構(gòu)設(shè)計上的“自治單元”。同理,“任勞任怨”的好團(tuán)隊(duì)也不是真正的好團(tuán)隊(duì),團(tuán)隊(duì)對自己的邊界已經(jīng)達(dá)成了共識,為什么還要違背這個共識去承接不屬于自己邊界內(nèi)的工作呢?這并非團(tuán)隊(duì)之間的“惡性競爭”,也不是工作上的互相推諉;恰恰相反,這實(shí)際上是一種良好的合作,表面上維持了自己的利益,然而在一個組織下,如果每個團(tuán)隊(duì)都以這種方式維持自我利益,反而會形成一種“互利主義”。
這種“你給我搔背,我也替你抓抓癢”的互利主義最終會形成團(tuán)隊(duì)之間的良好協(xié)作。如果團(tuán)隊(duì)領(lǐng)導(dǎo)者與團(tuán)隊(duì)成員能夠充分認(rèn)識到這一點(diǎn),就可以從團(tuán)隊(duì)層面思考限界上下文。此時,限界上下文就不僅僅是架構(gòu)師局限于一孔之見去完成甄別,而是每個團(tuán)隊(duì)成員自發(fā)組織的內(nèi)在驅(qū)動力。當(dāng)每個人都在思考這項(xiàng)工作該不該我做時,變相地就是在思考職責(zé)的分配是否合理,限界上下文的劃分是否合理。
從應(yīng)用邊界識別限界上下文
質(zhì)量屬性
管理的目的在于打造高效的團(tuán)隊(duì),但最后還是要落腳到技術(shù)實(shí)現(xiàn)上來,不懂業(yè)務(wù)分析的架構(gòu)師不是一個好的程序員,而一個不懂得提前識別系統(tǒng)風(fēng)險的程序員更不是一個好的架構(gòu)師。站在技術(shù)層面上看待限界上下文,我們需要關(guān)注的其實(shí)是質(zhì)量屬性(Quality Attributes)。如果把關(guān)乎質(zhì)量屬性的問題都視為在將來可能會發(fā)生,其實(shí)就是“風(fēng)險(Risk)”。
架構(gòu)是什么?Martin Fowler 認(rèn)為:架構(gòu)是重要的東西,是不容易改變的決策。如果我們未曾預(yù)測到系統(tǒng)存在的風(fēng)險,不幸它又發(fā)生了,帶給系統(tǒng)架構(gòu)的改變可能是災(zāi)難性的。利用限界上下文的邊界,就可以將這種風(fēng)險帶來的影響控制在一個極小的范圍,這也是前面提及的安全。為什么說限界上下文是領(lǐng)域驅(qū)動設(shè)計中最重要的元素,答案就在這里。
我曾經(jīng)負(fù)責(zé)開發(fā)一款基于大數(shù)據(jù)平臺的 BI 產(chǎn)品,在架構(gòu)設(shè)計時,對性能的評估方案是存在問題的,我們當(dāng)時考慮了符合生產(chǎn)規(guī)模的數(shù)據(jù)量,并以一個相對可行的硬件與網(wǎng)絡(luò)環(huán)境,對 Spark + Parquet 的技術(shù)選型進(jìn)行測試,測試結(jié)果滿足了設(shè)定的響應(yīng)時間值。然而,兩個因素的缺失為我們的架構(gòu)埋下了禍根。在測試時,我們沒有考慮并發(fā)訪問量,測試的業(yè)務(wù)場景也過于簡單。我們懷著一種鴕鳥心態(tài),在理論上分析這種決策(Spark 是當(dāng)時最快速的基于內(nèi)存的數(shù)據(jù)分析平臺,Parquet 是列式存儲,尤為適合統(tǒng)計分析)是對的,然后就按照我們期望的形式去測試,實(shí)際上是將風(fēng)險悄悄地埋藏起來。
當(dāng)產(chǎn)品真正銷售給客戶使用時,我們才發(fā)現(xiàn)客戶的業(yè)務(wù)場景非常復(fù)雜,對性能的要求也更加苛刻。例如,它要求達(dá)到 100 ~ 500 的并發(fā)訪問量,同時對大數(shù)據(jù)量進(jìn)行統(tǒng)計分析與指標(biāo)運(yùn)算,并期望實(shí)時獲得分析結(jié)果;而客戶所能提供的 Spark 集群卻是有限度的。事實(shí)上,基于 Spark 的 driver-worker 架構(gòu),它本身并不擅長完成高并發(fā)的數(shù)據(jù)分析任務(wù)。對于一個分析任務(wù),Spark 可以利用集群的力量由多個 worker 同時并行地執(zhí)行成百上千的 task,但瓶頸在 driver 端,一旦上游同時有多個請求涌入,響應(yīng)能力就不足了。最終,我們的產(chǎn)品在真正的壓力測試下一敗涂地。
幸而,我們劃定了限界上下文,并由此建立了數(shù)據(jù)分析微服務(wù)。針對客戶高并發(fā)的實(shí)時統(tǒng)計分析需求,在保證 REST API 不變的情況下,我們更改了技術(shù)選型,選擇基于 ElasticSearch 的數(shù)據(jù)分析微服務(wù)替換舊服務(wù)。這種改變幾乎不影響產(chǎn)品的其他模塊與功能,前端代碼僅僅做了少量修改。3 個人的團(tuán)隊(duì)在近一個月的周期內(nèi)基本完成了這部分?jǐn)?shù)據(jù)分析功能,及時掐斷了炸藥的導(dǎo)火線。
重用和變化
無論是重用領(lǐng)域邏輯還是技術(shù)實(shí)現(xiàn),都是在設(shè)計層面上我們必須考慮的因素,需求變化更是影響設(shè)計策略的關(guān)鍵因素。我在前面分析限界上下文的本質(zhì)時,就提及一個限界上下文其實(shí)是一個“自治”的單元。基于自治的四個特征,我們也可以認(rèn)為這個自治的單元其實(shí)就是邏輯重用和封裝變化的設(shè)計單元。這時,對限界上下文邊界的考慮,更多是出于技術(shù)設(shè)計因素,而非業(yè)務(wù)因素。在后面講解的上下文映射(Context Map)模式時,Eric Evans 總結(jié)的共享內(nèi)核其實(shí)就是重用的體現(xiàn),而開放主機(jī)服務(wù)與防腐層則是對變化的主動/被動應(yīng)對。
運(yùn)用重用原則分離出來的限界上下文往往對應(yīng)于子領(lǐng)域(Sub Domain),尤其作為支撐子領(lǐng)域。我在為一家公司的物流聯(lián)運(yùn)管理系統(tǒng)提供領(lǐng)域驅(qū)動設(shè)計咨詢時,通過與領(lǐng)域?qū)<业臏贤?#xff0c;我注意到他在描述運(yùn)輸、貨站以及堆場的相關(guān)業(yè)務(wù)時,都提到了作業(yè)和指令的概念。雖然屬于不同的領(lǐng)域,但指令的收發(fā)、作業(yè)的制訂與調(diào)度都是相同的,區(qū)別只在于作業(yè)與指令的內(nèi)容,以及作業(yè)調(diào)度的周期。為了避免在運(yùn)輸、貨站與堆場各自的限界上下文中重復(fù)設(shè)計與實(shí)現(xiàn)作業(yè)與指令等領(lǐng)域模型,我們可以將作業(yè)與指令單獨(dú)劃分到一個專門的限界上下文中。它作為上游限界上下文,提供對運(yùn)輸、貨站與堆場的業(yè)務(wù)支撐。
限界上下文對變化的應(yīng)對,其實(shí)是“單一職責(zé)原則”的體現(xiàn),即一個限界上下文不應(yīng)該存在兩個引起它變化的原因。還是這個物流聯(lián)運(yùn)管理系統(tǒng),最初團(tuán)隊(duì)的設(shè)計人員將運(yùn)費(fèi)計算與賬目、結(jié)賬等功能放在了財務(wù)上下文中。當(dāng)國家的企業(yè)征稅策略發(fā)生變化時,會引起財務(wù)上下文的變化,引起變化的原因是財務(wù)規(guī)則與政策的調(diào)整。倘若運(yùn)費(fèi)計算的規(guī)則也發(fā)生了變化,同樣會引起財務(wù)上下文的變化,但引起變化的原因卻是物流運(yùn)輸?shù)臉I(yè)務(wù)需求。如果我們將運(yùn)費(fèi)計算單獨(dú)從財務(wù)上下文中分離出來,就可以獨(dú)立演化,符合前面提及的“自治”原則,實(shí)現(xiàn)了兩種不同關(guān)注點(diǎn)的分離。
遺留系統(tǒng)
自治原則的唯一例外是遺留系統(tǒng),因?yàn)轭I(lǐng)域驅(qū)動設(shè)計建議的通常做法是將整個遺留系統(tǒng)視為一個限界上下文。那么,什么是遺留系統(tǒng)?根據(jù)維基百科的定義,它是一種舊的方法、舊的技術(shù)、舊的計算機(jī)系統(tǒng)或應(yīng)用程序,這個定義并不能解釋遺留系統(tǒng)的真相。我認(rèn)為,系統(tǒng)之所以成為遺留系統(tǒng),關(guān)鍵在于知識的缺乏。文檔不夠全面真實(shí),掌握系統(tǒng)知識的團(tuán)隊(duì)成員泰半離開,系統(tǒng)的代碼可能是一個大泥團(tuán)。因此,我對遺留系統(tǒng)的定義是“一個還在運(yùn)行和使用,但已步入軟件生命衰老期的缺乏足夠知識的軟件系統(tǒng)”。
倘若運(yùn)用領(lǐng)域驅(qū)動設(shè)計的系統(tǒng)要與這樣一個遺留系統(tǒng)打交道,應(yīng)該怎么辦?竊以為,粗暴地將整個遺留系統(tǒng)包裹在一個限界上下文中,未免太理想化和簡單化了。要點(diǎn)還是自治,這時候我們應(yīng)該站在遺留系統(tǒng)的調(diào)用者來觀察它,考慮如何與遺留系統(tǒng)集成,然后逐步對遺留系統(tǒng)進(jìn)行抽取與遷移,形成自治的限界上下文。
在這個過程中,我們可以借鑒技術(shù)棧遷移中常常運(yùn)用的“抽象分支(Branch By Abstraction)”手法。該手法會站在消費(fèi)者(Consumer)一方觀察遺留系統(tǒng),找到需要替換的單元(組件);然后對該組件進(jìn)行抽象,從而將消費(fèi)者與遺留系統(tǒng)中的實(shí)現(xiàn)解耦。最后,提供一個完全新的組件實(shí)現(xiàn),在保留抽象層接口不變的情況下替換掉遺留系統(tǒng)的舊組件,達(dá)到技術(shù)棧遷移的目的:
如上圖所示的抽象層,本質(zhì)就是后面我們要提到的“防腐層(Anticorruption Layer)”,通過引入這么一個間接層來隔離與遺留系統(tǒng)之間的耦合。這個防腐層往往是作為下游限界上下文的一部分存在。若有必要,也可以單獨(dú)為其創(chuàng)建一個獨(dú)立的限界上下文。
設(shè)計驅(qū)動力
結(jié)合業(yè)務(wù)邊界、工作邊界和應(yīng)用邊界,形成一種層層推進(jìn)的設(shè)計驅(qū)動力,可以讓我們對限界上下文的設(shè)計變得更加準(zhǔn)確,邊界的控制變得更加合理,畢竟,限界上下文的識別對于整個系統(tǒng)的架構(gòu)至關(guān)重要。在領(lǐng)域驅(qū)動的戰(zhàn)略設(shè)計階段,如果我們對識別出來的限界上下文的準(zhǔn)確性還心存疑慮,那么比較實(shí)際的做法是保持限界上下文一定的粗粒度。倘若覺得功能的邊界不好把握分寸,可以考慮將這些模棱兩可的功能放在同一個限界上下文中。待到該限界上下文變得越來越龐大,以至于一個 2PTs 團(tuán)隊(duì)無法完成交付目標(biāo);又或者該限界上下文的功能各有不同的質(zhì)量屬性要求;要么就是因?yàn)橹赜没蜃兓?#xff0c;使得我們能夠更清楚地看到分解的必要性;此時我們再對該限界上下文進(jìn)行分解,就會更加有把握。這是設(shè)計的實(shí)證主義態(tài)度。
通過以上過程去識別限界上下文,僅僅是一種對領(lǐng)域問題域的靜態(tài)劃分,我們還缺少另外一個重要的關(guān)注點(diǎn),即:限界上下文之間是如何協(xié)作的?倘若限界上下文識別不合理,協(xié)作就會變得更加困難,尤其當(dāng)一個限界上下文對應(yīng)一個微服務(wù)時,協(xié)作成本更會顯著增加。反過來,當(dāng)我們發(fā)現(xiàn)彼此協(xié)作存在問題時,說明限界上下文的劃分出現(xiàn)了問題,這算是對識別限界上下文的一種驗(yàn)證方法。Eric Evans 將這種體現(xiàn)限界上下文協(xié)作方式的要素稱之為“上下文映射(Context Map)”。
理解限價上下文
一個軟件系統(tǒng)通常被分為多個限界上下文,這是運(yùn)用“分而治之”思想來降低業(yè)務(wù)復(fù)雜度的有效手段,設(shè)計的難題往往會停留在“如何分”,然而限界上下文之間的“怎么合”問題同樣值得關(guān)注,分與合遵循的還是軟件設(shè)計的最高原則——高內(nèi)聚、松耦合。分是合的基礎(chǔ),基于內(nèi)聚相關(guān)度進(jìn)行合理的分配,可以在一定程度減少限界上下文之間不必要的關(guān)聯(lián)。假設(shè)分配是合理的,則接下來的“合”就是要盡可能地降低彼此之間的耦合。
既然前面提及限界上下文的識別是一個迭代過程,當(dāng)我們在思考限界上下文該如何協(xié)作時,倘若發(fā)現(xiàn)協(xié)作總有不合理之處,就可能會是一個“設(shè)計壞味道”的信號,它告訴我們:之前識別的限界上下文或有不妥,由是可以審視之前的設(shè)計,進(jìn)而演進(jìn)為更為準(zhǔn)確的限界上下文劃分。即使拋開對設(shè)計的促進(jìn)作用,思考限界上下文是如何協(xié)作的,仍然格外重要,我們既要小心翼翼地維護(hù)限界上下文的邊界,又需要它們彼此之間良好的協(xié)作,并思考協(xié)作的具體實(shí)現(xiàn)方式,這個思考過程既牽涉到邏輯架構(gòu)層面,又與物理架構(gòu)有關(guān),足以引起我們的重視。
領(lǐng)域驅(qū)動設(shè)計通過上下文映射(Context Map) 來討論限界上下文之間的協(xié)作問題,上下文映射是一種設(shè)計手段,Eric Evans 總結(jié)了諸如共享內(nèi)核(Shared Kernel)、防腐層(Anticorruption Layer)、開放主機(jī)服務(wù)(Open Host Service)等多種模式。由于上下文映射本質(zhì)上是與限界上下文一脈相承的,因此要掌握這些協(xié)作模式,應(yīng)該從限界上下文的角度進(jìn)行理解,著眼點(diǎn)還是在于“邊界”。領(lǐng)域驅(qū)動設(shè)計認(rèn)為:上下文映射是用于將限界上下文邊界變得更清晰的重要工具。所以當(dāng)我們正在為一些限界上下文的邊界劃分而左右為難時,不妨先放一放,在定下初步的限界上下文后,通過繪制上下文映射來檢驗(yàn),或許會有意外收獲。
限界上下文的一個核心價值,就是利用邊界來約束不同上下文的領(lǐng)域模型,以保證模型的一致性。然而,每個限界上下文都不是獨(dú)立存在的,多數(shù)時候,都需要多個限界上下文通力協(xié)作,才能完成一個完整的用例場景。例如,客戶之于商品、商品之于訂單、訂單之于支付,貫穿起來才能完成“購買商品”的核心流程。
兩個限界上下文之間的關(guān)系是有方向的,領(lǐng)域驅(qū)動設(shè)計使用兩個專門的術(shù)語來表述它們:“上游(Upstream)”和“下游(Downstream)”,在上下文映射圖中,以 U 代表上游,D 代表下游,理解它們之間的關(guān)系,正如理解該術(shù)語隱喻的河流,自然是上游產(chǎn)生的變化會影響到下游,反之則不然。故而從上游到下游的關(guān)系方向,代表了影響產(chǎn)生的作用力,影響作用力的方向與程序員慣常理解的依賴方向恰恰相反,上游影響了下游,意味著下游依賴于上游。
在劃分限界上下文的業(yè)務(wù)邊界時,我們常常從“語義相關(guān)性”與“功能相關(guān)性”兩個角度去判別職責(zé)劃分的合理性。在上下文映射中,我發(fā)現(xiàn)之所以兩個業(yè)務(wù)邊界的限界上下文能產(chǎn)生上下游協(xié)作關(guān)系,皆源于二者的功能相關(guān)性,這種功能相關(guān)存在主次之分,往往是上游限界上下文作為下游限界上下文的功能支撐,這就意味著在當(dāng)前的協(xié)作關(guān)系下,下游限界上下文中的用例才是核心領(lǐng)域。例如,訂單與支付,下訂單用例才是核心功能,支付功能作為支撐的公開服務(wù)而被調(diào)用;例如,郵件與文件共享,寫郵件用例才是核心功能,上傳附件作為支撐的公開服務(wù)而被調(diào)用;例如,項(xiàng)目管理與通知,分配任務(wù)用例才是核心功能,通知功能作為支撐的公開服務(wù)而被調(diào)用。巧的是,這種主次功能的調(diào)用關(guān)系,幾乎對應(yīng)的就是用例圖中的包含用例或擴(kuò)展用例。
如果我們通過用例圖來幫助識別限界上下文,那么,用例圖中的包含用例或擴(kuò)展用例或許是一個不錯的判斷上下文協(xié)作關(guān)系的切入點(diǎn)。選擇從包含或擴(kuò)展關(guān)系切入,既可能確定了職責(zé)分離的邏輯邊界,又可以確定協(xié)作關(guān)系的方向,這就是用例對領(lǐng)域驅(qū)動設(shè)計的價值所在了。
那么,如何將上下文映射運(yùn)用到領(lǐng)域驅(qū)動的戰(zhàn)略設(shè)計階段?Eric Evans 為我們總結(jié)了常用的上下文映射模式。為了更好地理解這些模式,結(jié)合限界上下文對邊界的控制力,再根據(jù)這些模式的本質(zhì),我將這些上下文映射模式分為了兩大類:團(tuán)隊(duì)協(xié)作模式與通信集成模式。前者對應(yīng)的其實(shí)是團(tuán)隊(duì)合作的工作邊界,后者則從應(yīng)用邊界的角度分析了限界上下文之間應(yīng)該如何進(jìn)行通信才能提升設(shè)計質(zhì)量。針對通信集成模式,結(jié)合領(lǐng)域驅(qū)動設(shè)計社區(qū)的技術(shù)發(fā)展,在原有上下文映射模式基礎(chǔ)上,增加了發(fā)布/訂閱事件模式。
總結(jié)
- 上一篇: 获取硬盘序列号(VC)
- 下一篇: 联想r720自带杜比驱动下载_5499起