業(yè)務(wù)中臺(tái)構(gòu)建-服務(wù)識(shí)別
SOA團(tuán)隊(duì) 2020-03-16
對(duì)于中臺(tái)構(gòu)建,實(shí)際上兩個(gè)關(guān)鍵點(diǎn),第一個(gè)是劃分微服務(wù)模塊粒度,第二個(gè)就是在模塊劃分清楚后確定服務(wù)識(shí)別和定義的粒度。在服務(wù)識(shí)別和定義前,可以先參考本文檔【3.2.2.2 Http Rest接口設(shè)計(jì)】這篇文章。
在上篇談中臺(tái)構(gòu)建模塊劃分的時(shí)候,強(qiáng)調(diào)了一個(gè)關(guān)鍵點(diǎn),就是盡可能以數(shù)據(jù)的維度來進(jìn)行模塊拆分,數(shù)據(jù)包括了基礎(chǔ)主數(shù)據(jù)和核心共享數(shù)據(jù),在數(shù)據(jù)驅(qū)動(dòng)下拆分模塊,那么模塊底層對(duì)應(yīng)的數(shù)據(jù)庫如何拆分基本也就清楚了。一定要知道,微服務(wù)架構(gòu)下,我們底層數(shù)據(jù)庫也是拆分了的。
底層數(shù)據(jù)庫沒有拆分,但是仍然用SpringCloud框架開發(fā),可以拆分為多個(gè)JAR包,在這種模式下只能認(rèn)為是一個(gè)微服務(wù)模塊,而不是獨(dú)立,因?yàn)槠洳淮嬖讵?dú)立自治能力。我們看到很多上層開發(fā)采用SpringCloud框架,但是數(shù)據(jù)庫仍然采用一個(gè)數(shù)據(jù)庫的情況,再次說明,對(duì)于這種架構(gòu)設(shè)計(jì),不是標(biāo)準(zhǔn)意義上的微服務(wù)架構(gòu),沒有做到徹底解耦。
■ 面向資源,面向?qū)ο蠛皖I(lǐng)域驅(qū)動(dòng)設(shè)計(jì)
對(duì)于Http Rest接口說的最多的就是面向資源的設(shè)計(jì),對(duì)于資源有標(biāo)準(zhǔn)的Http Put,Post,Delte,Get等操作。因此你需要定義好相應(yīng)的資源。資源可以放在計(jì)算機(jī)上并體現(xiàn)為比特流的事物,可以是結(jié)構(gòu)化的數(shù)據(jù)或?qū)ο蠹?,也可以是圖片或文件流,這些都是可以處理和操作的資源。
面向資源和領(lǐng)域驅(qū)動(dòng)本身不是一種新的軟件工程設(shè)計(jì)方法,真正的方法只有傳統(tǒng)的面向結(jié)構(gòu)設(shè)計(jì)和面向?qū)ο笤O(shè)計(jì)兩種。因此對(duì)于面向資源可以按面向傳統(tǒng)結(jié)構(gòu)化設(shè)計(jì),也可以按面向?qū)ο笤O(shè)計(jì)。當(dāng)然最好的方式仍然是面向?qū)ο筮M(jìn)行設(shè)計(jì)。
資源即實(shí)體,實(shí)體即對(duì)象,這個(gè)對(duì)象代表的是業(yè)務(wù)對(duì)象,有明確的業(yè)務(wù)含義,類似供應(yīng)商,采購訂單,產(chǎn)品,合同等。同時(shí)這些對(duì)象本身存在關(guān)聯(lián)和遞進(jìn)的層次結(jié)構(gòu),比如供應(yīng)商有對(duì)應(yīng)的聯(lián)系人,有對(duì)應(yīng)的銀行賬號(hào)。產(chǎn)品可能有對(duì)應(yīng)的維修記錄等。
而這些業(yè)務(wù)對(duì)象正是我們?cè)陬I(lǐng)域驅(qū)動(dòng)設(shè)計(jì)時(shí)候經(jīng)常會(huì)識(shí)別的領(lǐng)域?qū)ο?,這個(gè)領(lǐng)域?qū)ο笊婕暗蕉鄠€(gè)子類,是否歸結(jié)到一個(gè)大的領(lǐng)域?qū)ο笞铌P(guān)鍵的還是是否共屬一個(gè)生命周期。面向資源設(shè)計(jì),完全可以采用面向領(lǐng)域設(shè)計(jì)方法,首先定義領(lǐng)域?qū)ο?,將領(lǐng)域?qū)ο蠼閷?duì)應(yīng)的資源,然后再考慮看這個(gè)資源應(yīng)該暴露哪些能力接口出來。
所以在微服務(wù)架構(gòu)下,首先要了解清楚面向資源進(jìn)行Rest接口能力設(shè)計(jì),資源的識(shí)別和定義可以參考領(lǐng)域設(shè)計(jì)的思路進(jìn)行,識(shí)別和定義領(lǐng)域?qū)ο?,然后再轉(zhuǎn)為資源定義。
■ 接口服務(wù)的粗粒度是關(guān)鍵
如果我們不按領(lǐng)域?qū)ο蠓绞絹矶x資源,那么我們最容易犯的錯(cuò)誤就是將所有的數(shù)據(jù)庫表對(duì)象都全部定義為一個(gè)個(gè)獨(dú)立的資源,將這些資源的CRUD操作,全部暴露為Get,Put,Post和Delete接口方法。那么這樣暴露出來的Http Rest接口方法就全部是細(xì)粒度的接口。
這種方法很省事,一個(gè)模塊有100張表,你只需要暴露100個(gè)接口,每個(gè)接口都含標(biāo)準(zhǔn)的上述操作就完事了。但是這種接口服務(wù)識(shí)別和定義沒有任何意義,也不符合我們粗粒度的要求。
那么問題的關(guān)鍵點(diǎn)在哪里?其關(guān)鍵就是原來應(yīng)該是粗粒度的體現(xiàn)業(yè)務(wù)價(jià)值的接口服務(wù)全部都變成了細(xì)粒度的DAO訪問類細(xì)粒度接口服務(wù)。失去了接口的意義,同時(shí)又將本身應(yīng)該完全內(nèi)聚在微服務(wù)模塊內(nèi)部的業(yè)務(wù)邏輯全部暴露到外層去解決。這個(gè)有點(diǎn)類似我們?cè)谶M(jìn)行領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的時(shí)候,經(jīng)常談到的貧血的領(lǐng)域服務(wù)層,即我們的Http Rest接口應(yīng)該是粗粒度的,應(yīng)該是滿血的領(lǐng)域服務(wù)層的能力暴露,而不是底層數(shù)據(jù)庫CRUD操作的暴露。
■ 通過識(shí)別的資源來識(shí)別和定義接口
只要確定了資源,那么我們就很容易來確定資源應(yīng)該提供哪些接口。
在我們進(jìn)行接口設(shè)計(jì)的時(shí)候,如果一個(gè)資源完全不需要和外部微服務(wù)模塊或外部應(yīng)用打交道,那么這個(gè)資源完全不用開放任何接口。這個(gè)是我一直強(qiáng)調(diào)的原則,即在微服務(wù)模塊內(nèi)部最好是走傳統(tǒng)API接口交付方法進(jìn)行調(diào)用,而不是走Http Rest接口服務(wù),這一方面是提升性能,一方面是減少各類難以應(yīng)對(duì)的分布式事務(wù)問題。
在資源定義清楚后,往往資源都是一個(gè)復(fù)合對(duì)象,比如采購訂單資源,涉及到采購訂單頭或采購訂單明顯信息,之間還存在關(guān)聯(lián),但是這是一個(gè)完整的資源對(duì)象。對(duì)于采購訂單對(duì)象,根據(jù)業(yè)務(wù)場(chǎng)景,存在外部導(dǎo)入新的采購訂單,存在外部對(duì)已有的采購訂單進(jìn)行變更后導(dǎo)入,存在外部需要查詢采購訂單集合,同時(shí)查看某一個(gè)特定key值的采購訂單的詳細(xì)明細(xì)數(shù)據(jù)。這可能是我們經(jīng)常會(huì)遇到的接口需求場(chǎng)景,從這些場(chǎng)景可以看到,我們的設(shè)計(jì)完全可以基于采購訂單資源展開。
創(chuàng)建新的采購訂單:POST /Orders
修改一張ID為1的已有訂單:PATCH /Orders/1
刪除ID為1的已有訂單:DELETE /Orders/1
查詢所有采購訂單:GET /Orders
查詢ID為1的采購訂單:GET /Orders/1
查詢1月到5月的訂單:GET /Orders?StartData='201801'&&EndDate='201805'
可以看到,第一種方法就是上面的,可以直接在資源后面增加不同的參數(shù)條件進(jìn)行模糊查詢。其次,我們可以將查詢條件定義為一個(gè)查詢實(shí)體類,同時(shí)將整個(gè)查詢實(shí)體類的信息通過一個(gè)完整的實(shí)體對(duì)象傳遞過去進(jìn)行查詢,查詢完成后再返回相應(yīng)的結(jié)果。比如我們定義一個(gè)OrderQueryExt實(shí)體類。
基于特定條件的模糊查詢:POST /Orders/OrderQueryExt
那是否會(huì)存在只查詢一張采購訂單的采購明細(xì)列表信息?如果存在這種情況,應(yīng)該按照資源和資源層次關(guān)系進(jìn)行設(shè)計(jì),由資源逐層展開查詢。
查詢ID為1的訂單的所有采購明細(xì):GET /Orders/1/OrderDetails
查詢ID為1的訂單的流程審批記錄信息:GET /Orders/1/ProcessDetails
查詢ID為1的訂單的所有附件信息:GET /Orders/1/Attaches
對(duì)于PUT和PATCH而言,如果涉及的情況一般是對(duì)實(shí)體的部分?jǐn)?shù)據(jù)進(jìn)行更新,同時(shí)還需要支持SaveAndUpdate操作,那么我們一般都采用PATCH方式,而不是PUT方式。即實(shí)際資源接口設(shè)計(jì)的時(shí)候,單純的PUT場(chǎng)景往往現(xiàn)在已經(jīng)很少發(fā)生。
■ 定義業(yè)務(wù)規(guī)則和邏輯處理類接口
這是我們遇到的第二大類,前面基于資源進(jìn)行接口設(shè)計(jì)思路已經(jīng)很明確。但是對(duì)于業(yè)務(wù)規(guī)則處理類往往比較難,比如我們經(jīng)常遇到的提交報(bào)賬單的時(shí)候需要進(jìn)行預(yù)算校驗(yàn)和控制,這個(gè)就是典型的業(yè)務(wù)規(guī)則處理類,報(bào)賬單提交需要,合同提交往往也需要。
那么這里的資源究竟是什么?
對(duì)于預(yù)算信息應(yīng)該不是這里的資源,因?yàn)轭A(yù)算信息的錄入和維護(hù),才會(huì)涉及到預(yù)算信息開放接口。而這里的業(yè)務(wù)場(chǎng)景是對(duì)已有的預(yù)算信息進(jìn)行規(guī)則計(jì)算和校驗(yàn)。
基于這類場(chǎng)景,我們看到比較好的設(shè)計(jì)方法是定義一個(gè)獨(dú)立的規(guī)則類,將規(guī)則類映射為一個(gè)資源,比如這例子里面我們可以定義一個(gè)BudgetControl的規(guī)則類,這個(gè)類可以定義為一個(gè)資源對(duì)象。任何一個(gè)規(guī)則處理都涉及到有具體的輸入和輸出。
比如預(yù)算校驗(yàn):
輸入:具體的組織信息,預(yù)算科目信息,當(dāng)前申請(qǐng)預(yù)算信息,年度或月度信息
輸出:校驗(yàn)結(jié)果信息
你會(huì)看到對(duì)于預(yù)算校驗(yàn),預(yù)算扣減,預(yù)算凍結(jié),實(shí)際上他們的輸入和輸出都是相同的,那么我們可以劃歸到同一個(gè)規(guī)則處理類里面進(jìn)行處理。那么規(guī)則類的定義,需要增加一個(gè)規(guī)則處理類型即可。
那么不論是預(yù)算校驗(yàn),還是預(yù)算凍結(jié),可以看到實(shí)際的接口調(diào)用都是:
POST /BudgetControls
當(dāng)然也可以將預(yù)算檢查,預(yù)算凍結(jié)等定義為預(yù)算控制類的子對(duì)象,比如預(yù)算檢查Valid,那接口調(diào)用為:
POST /BudgetControls/Valid
所以對(duì)于業(yè)務(wù)規(guī)則的處理可以看到,最重要的是業(yè)務(wù)規(guī)則類的定義,業(yè)務(wù)規(guī)則類定義清楚了,業(yè)務(wù)規(guī)則類轉(zhuǎn)為資源,形成對(duì)資源的Http操作接口。
對(duì)于業(yè)務(wù)規(guī)則類,一定是粗粒度服務(wù)接口,規(guī)則的處理邏輯都應(yīng)該完全控制在模塊內(nèi)部而不是被暴露到外面去。因此對(duì)于規(guī)則類的定義也是,僅僅提供僅有的輸入和輸出,能夠滿足規(guī)則處理和計(jì)算要求即可。