談?wù)劶軜?gòu)層級(jí)的“開閉原則”(一)
簡(jiǎn)介:
本文是關(guān)于架構(gòu)層級(jí)SOLID原則的文章系列的第一篇。你可能熟悉如何在面向?qū)ο蟮膶蛹?jí)遵循SOLID原則來(lái)進(jìn)行類的設(shè)計(jì),或者你也曾經(jīng)疑惑這些原則是否適用于系統(tǒng)的架構(gòu)設(shè)計(jì),關(guān)于這一點(diǎn),我將嘗試給出一些我的見解。
在類的層級(jí),開閉原則(the-Open-Closed-Principle,簡(jiǎn)稱OCP原則)的含義是:一個(gè)類對(duì)擴(kuò)展是“開”放的,而對(duì)變更是封“閉”的,意思是說(shuō),應(yīng)該在不改變類的前提下擴(kuò)展一個(gè)類的行為。而通常的方式是繼承和多態(tài)。
在架構(gòu)層級(jí),我們并不會(huì)變更系統(tǒng)的一部分功能(可能是最適用于當(dāng)前架構(gòu)的進(jìn)程,守護(hù)進(jìn)程,服務(wù),或者微服務(wù)),而是通過(guò)新增功能的方式來(lái)復(fù)用已完成的代碼。為了不對(duì)現(xiàn)有的部分做出變更,系統(tǒng)需要做到完全的解耦。接下來(lái)的內(nèi)容將聚焦于事件驅(qū)動(dòng)系統(tǒng),并以消息隊(duì)列實(shí)現(xiàn)服務(wù)間通信。消息隊(duì)列 可以是ActiveMQ, RabbitMQ, ZeroMQ, Kafka或者其他服務(wù),我將以Kafka的話語(yǔ)體系來(lái)進(jìn)行描述,如主題(Topic),發(fā)布者,訂閱者,以及類似Kafka的多個(gè)訂閱者共享相同主題的能力。
一、消息系統(tǒng)
上圖是一個(gè)一般用例:發(fā)布者向主題發(fā)布消息(或者事件),多個(gè)訂閱者可以從主題處獲得該事件。箭頭指示了通信的流向。假定發(fā)布者和訂閱者都是微服務(wù)的話,雙層的圓角矩形代表某一特定微服務(wù)的多個(gè)實(shí)例。在本例中的四個(gè)微服務(wù):發(fā)布者,訂閱者1,訂閱者2,訂閱者n,每個(gè)微服務(wù)都有多個(gè)實(shí)例。
二、具體示例
舉一個(gè)具體的例子。假設(shè)我們?cè)谝患移囎赓U公司工作,并負(fù)責(zé)建立一個(gè)車輛的可用性系統(tǒng)。整個(gè)租賃流程的簡(jiǎn)化視圖如下:
第1步,車輛租賃:包含租賃協(xié)議的簽訂和客戶選車的過(guò)程。隨即可用的車輛數(shù)減1。
第2步,客戶用車:客戶在一定的時(shí)間范圍內(nèi)使用租賃的車輛。
第3步,車輛歸還:車輛的歸還和簽到。隨即可用的車輛數(shù)加1。
其中第1步和第3步都需要將租賃協(xié)議入庫(kù),因此我們可以設(shè)計(jì)一個(gè)事件,RentalAgreementSaved,在保存數(shù)據(jù)時(shí)觸發(fā)。這一事件將被存儲(chǔ)在RentalAgreementSaved主題中。因此到目前為止,共有兩個(gè)發(fā)布者向主題發(fā)送消息,一個(gè)是CarRental微,另一個(gè)是CarCheckin微服務(wù)。
下面來(lái)定義消息的內(nèi)容。鑒于本主題的意圖是為了表征租賃協(xié)議的保存,因此所需的最小信息量即協(xié)議ID。但系統(tǒng)的使命是跟蹤車輛的可用性,最好還是設(shè)置一個(gè)Status字段。這一字段可以有兩個(gè)值:
激活狀態(tài)。代表客戶正在使用車輛。
關(guān)閉狀態(tài)。代表客戶已經(jīng)歸還了車輛并進(jìn)行了簽到。
CarRental微服務(wù)可以選用如下的JSON數(shù)據(jù)結(jié)構(gòu):
{"Status":"Active","RentalAgreementID":1234}
CarCheckin微服務(wù)對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)是:
{"Status":"Closed","RentalAgreementID":1234}
Status字段本應(yīng)從數(shù)據(jù)庫(kù)讀取,并通過(guò)協(xié)議ID加載租賃協(xié)議,但因?yàn)槲覀冎魂P(guān)心車輛的可用性,直接在JSON消息中寫入?yún)f(xié)議ID更簡(jiǎn)單,性能也更好。這一點(diǎn)在后文還會(huì)提到。
有了消息發(fā)布者和消息的格式,我們就可以完成下面的圖表:
CarAvailability微服務(wù)會(huì)對(duì)發(fā)送給RentalAgreementSaved主題的消息進(jìn)行消費(fèi):如果Status是“激活狀態(tài)”,就減1,如果Status是“關(guān)閉狀態(tài)”,就加1。
現(xiàn)在已經(jīng)有了能夠完成既定目標(biāo)的可用系統(tǒng),可以計(jì)算車輛的可用性。這一系統(tǒng)是否可以擴(kuò)展以適用其他有意義的工作?是否可以真的在此過(guò)程中應(yīng)用開閉原則?
三、系統(tǒng)擴(kuò)展
假定我們需要在租賃流程結(jié)束的時(shí)候,給客戶開具發(fā)票??梢栽O(shè)計(jì)一個(gè)Invoicing微服務(wù)來(lái)訂閱RentalAgreementsSaved主題消息(消息中附加了租賃協(xié)議的ID)。當(dāng)Status是“關(guān)閉狀態(tài)”時(shí),發(fā)票微服務(wù)可以從數(shù)據(jù)庫(kù)中讀取租賃協(xié)議的數(shù)據(jù),并從Customers表中讀取用戶數(shù)據(jù)(Customers表和RentalAgreements表是相關(guān)聯(lián)的)。有了上述信息,Invoicing微服務(wù)將可以向用戶提供發(fā)票。過(guò)程如下圖所示:
我們擴(kuò)展了系統(tǒng)的功能,但并沒(méi)有變更系統(tǒng)的代碼。只是利用了多個(gè)訂閱者可以訂閱同一個(gè)消息主題的機(jī)制。因此是的,OCP原則可以在架構(gòu)層級(jí)得以應(yīng)用。
迪米特法則
迪米特法則(Law of Demeter)又叫作最少知識(shí)原則(Least Knowledge Principle 簡(jiǎn)稱LKP),就是說(shuō)一個(gè)對(duì)象應(yīng)當(dāng)對(duì)其他對(duì)象有盡可能少的了解。
假設(shè),我們對(duì)現(xiàn)有的功能很滿意,并準(zhǔn)備添加一個(gè)新的功能:向用戶發(fā)出感謝信來(lái)感謝他或她使用了我們的服務(wù)。參考發(fā)票微服務(wù)的例子,我們可以同樣從數(shù)據(jù)庫(kù)中獲得租賃協(xié)議和用戶數(shù)據(jù)。但這樣的設(shè)計(jì)效率不高,因?yàn)镃ustomerThanking服務(wù)根本不需要用到租賃協(xié)議的內(nèi)容。事實(shí)上,這也違反了“迪米特法則”,而我們希望所有的系統(tǒng)都是符合良好的架構(gòu)實(shí)現(xiàn)的。
也許我們可以這樣,變更RentalAgreementsSaved主題的消息內(nèi)容,添加一個(gè)“CustomerID”字段,JSON格式數(shù)據(jù)如下:
{"Status":"Closed","RentalAgreementID":1234,"CustomerID":8965}
呃,等等,不是說(shuō)好了要應(yīng)用OCP原則的么,怎么能變更消息內(nèi)容呢??磥?lái)還真是這樣,好吧,我們得想想別的辦法。