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