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