作者:京東零售 趙嘉鐸
前言
從去年開始京東廣告投放系統(tǒng)做了一次以領(lǐng)域驅(qū)動設(shè)計為思想內(nèi)核的架構(gòu)升級,在深入理解DDD思想的同時,我們基于廣告投放業(yè)務(wù)的本質(zhì)特征大膽地融入了自己的理解和改造。新架構(gòu)是從設(shè)計思想到落地框架都進行了徹底的革新,涉及內(nèi)容比較多,因此我們希望通過一系列文章循序漸進地闡述本次架構(gòu)升級的始末。新架構(gòu)并不是一日而成的,而是經(jīng)過了多次架構(gòu)升級的演進,因此我們將本文作為該系列的第一篇文章,先讓大家通過廣告投放平臺的架構(gòu)演進歷程來了解新架構(gòu)的設(shè)計初衷。
如前言所述,本文主要聚焦于廣告投放系統(tǒng)歷代代碼架構(gòu)的演進歷程,我們也不希望本文的篇幅過于冗長,因此對新架構(gòu)中具體框架及API的說明淺嘗輒止,我們會在本系列接下來的數(shù)篇文章中逐步給出愈加具象的描述。
什么是好的代碼架構(gòu)
大家都清楚在當(dāng)前的工作中我們所面臨的主要矛盾是“越來越多的多場景化復(fù)雜業(yè)務(wù)需求與有限的研發(fā)人力之間的矛盾”。而要解決這一矛盾,就要求我們的系統(tǒng)能做到:設(shè)計易拓展、代碼易復(fù)用、邏輯易傳承、運行更穩(wěn)定。這看起來像是一句空喊的口號,但其實每一個特性都有具體的要求:
?設(shè)計易拓展
一個好的架構(gòu)應(yīng)該能夠?qū)崿F(xiàn)業(yè)務(wù)與技術(shù)組件的分離,使設(shè)計者能夠?qū)W⒂跇I(yè)務(wù)流程,以填空的方式直接套用開箱即用的組件、框架和解決方案,不必進行大量的重復(fù)設(shè)計;另外好的架構(gòu)也能夠引導(dǎo)設(shè)計者完成最小子問題的正交分解,將設(shè)計者從錯綜復(fù)雜的上層業(yè)務(wù)邏輯中拯救出來,逐個擊破,降低需求的復(fù)雜度和理解成本。
?代碼易復(fù)用
一個好的架構(gòu)應(yīng)該有良好的分層,強調(diào)正交子模塊的拆分與封裝,同層原子模塊之間避免互相依賴和耦合,讓上層系統(tǒng)能夠輕松實現(xiàn)底層業(yè)務(wù)邏輯的組合復(fù)用,以
?的實現(xiàn)復(fù)雜度支撐
?業(yè)務(wù)復(fù)雜度;另外我們的業(yè)務(wù)邏輯是建立在數(shù)據(jù)之上的,一個封裝良好的代碼架構(gòu)在實現(xiàn)業(yè)務(wù)邏輯復(fù)用的同時應(yīng)該有健全的數(shù)據(jù)模型維護和共享機制,避免同一個數(shù)據(jù)對象的重復(fù)查詢,并能夠輕松通過批量操作降低系統(tǒng)的I/O負載。
?邏輯易傳承
我們歷史上多次嘗試通過維護文檔的方式來建立業(yè)務(wù)知識庫,但都以失敗告終了。在這個過程中我們意識到業(yè)務(wù)功能都是由我們的代碼承接的,它天然具備業(yè)務(wù)知識庫的功能。因此一個好的代碼架構(gòu)不僅能夠?qū)崿F(xiàn)業(yè)務(wù)功能,而且要承擔(dān)起傳遞業(yè)務(wù)知識的職責(zé):當(dāng)有新同學(xué)加入時,代碼能夠以最直接的方式幫助他快速建立起對整個業(yè)務(wù)的宏觀認知,而進行具體的需求開發(fā)時,又能夠按圖索驥,快速定位改動點并深入了解其業(yè)務(wù)細節(jié)。
?運行更穩(wěn)定
一個好的代碼架構(gòu)在面對多場景化的需求時,可以做到場景隔離,避免不同場景的特有邏輯之間互相耦合干擾,出現(xiàn)一個場景需求上線后影響其他業(yè)務(wù)場景的問題;除此之外,一個好的架構(gòu)應(yīng)該通過設(shè)計良好的框架和標(biāo)準模板在有益的程度上對開發(fā)者的編碼行為進行約束,把規(guī)范框架化,而不是過多的依賴人置和code review來實現(xiàn)規(guī)范統(tǒng)一。
架構(gòu)演進之路
在上一章節(jié)列舉出來的特質(zhì)也是評判一個架構(gòu)優(yōu)劣的標(biāo)準,而我們新的架構(gòu)方案也正是在一次次為了實現(xiàn)這些目標(biāo)而采取的摸索中逐漸成型的。在接下來的幾個章節(jié)中,我們將從最早期的代碼架構(gòu)開始,逐代剖析架構(gòu)演進的歷程,通過這種方式讓大家了解每一次改進背后的設(shè)計動機和思路,從而更好的理解新架構(gòu)的設(shè)計思想,也為大家推動架構(gòu)向下一代演進打好基礎(chǔ)。
第一代:沒有架構(gòu)的架構(gòu)
最開始的時候我們的架構(gòu)如下圖所示,這也是我們目前最常見一種代碼架構(gòu)??梢钥闯鏊奶攸c就是“簡單”,沒有過多的封裝和設(shè)計,平鋪直敘,數(shù)據(jù)查詢和業(yè)務(wù)邏輯處理互相交織,是面向數(shù)據(jù)庫編程的典型案例。這種架構(gòu)在早期場景單一、需求簡單的階段可以快速實現(xiàn)功能,沒有多余的設(shè)計成本,但是隨著業(yè)務(wù)的發(fā)展,系統(tǒng)服務(wù)的場景越來越多,這套架構(gòu)就變得越來越不簡單了。
??
問題主要體現(xiàn)在兩個方面:
1.由于大家習(xí)慣“打補丁”式的開發(fā),來了一個業(yè)務(wù)需求就在現(xiàn)有的流程中增加一個if...else分支,然后直接在新分支內(nèi)實現(xiàn)業(yè)務(wù)邏輯。當(dāng)業(yè)務(wù)流程積攢的足夠冗長時,就很容易忽視前置流程已經(jīng)查詢好的數(shù)據(jù)對象,造成數(shù)據(jù)重復(fù)查詢。同時為了實現(xiàn)邏輯的復(fù)用,我們開始把一些常用邏輯封裝為單獨的方法,然后在上層業(yè)務(wù)流程中直接調(diào)用,然而我們在封裝底層方法往往會把數(shù)據(jù)的獲取邏輯封裝下來,這進一步加劇了數(shù)據(jù)重復(fù)查詢的問題,在有循環(huán)調(diào)用的場景中這個問題會更加突出。另外這種邏輯的復(fù)用方式還會造成數(shù)據(jù)庫訪問碎片化,我們很難利用批量操作的優(yōu)勢優(yōu)化系統(tǒng)性能。在最近剛結(jié)束的大促中,我們前期暴露的幾個性能問題基本都是這中模式導(dǎo)致的。
2.除了性能問題之外,由于不同業(yè)務(wù)場景的邏輯互相交織,代碼分支判斷邏輯缺少統(tǒng)一的規(guī)劃,if分支層層嵌套,導(dǎo)致我們的代碼邏輯圈復(fù)雜度不斷飆升,本來應(yīng)該通用的邏輯對不同場景的適配性越來越低。漸漸的,我們發(fā)現(xiàn)新增需求的開發(fā)越來越“不簡單”了:在試圖復(fù)用一段看起來相似的代碼邏輯時會有很多糾結(jié)和不盡人意的地方,對代碼執(zhí)行流程的認知也不似以往那么清晰了,為了防止對舊的業(yè)務(wù)流程造成影響,我們開始增加更多的if分支,這反過來進一步加劇了情況的惡化,于是我們的代碼中充斥著重復(fù)代碼、多達5、6層的嵌套...
為了緩解舊架構(gòu)中的這些問題,我們引入了上下文機制,嘗試將數(shù)據(jù)的查詢邏輯與業(yè)務(wù)流程分離開來,由此引出了第二代基于上下文機制的代碼架構(gòu)。
第二代:略有改善的上下文機制
上下文主要是為了解決數(shù)據(jù)重復(fù)查詢問題引入的,思路特別樸素,就是把一個完整業(yè)務(wù)流程中要用到的全部數(shù)據(jù)提前在方法一開始就查詢好,并做好校驗。查詢出來的數(shù)據(jù)對象保存到一個上下文對象中,這個上下文對象會貫穿整個業(yè)務(wù)流程,業(yè)務(wù)邏輯中需要用得到底層數(shù)據(jù)實體的時候統(tǒng)一從上下文對象中獲取。
??
所有的數(shù)據(jù)集中在“上下文構(gòu)造”步驟中查詢,整個業(yè)務(wù)流程運行在上下文對象中
通過上下文的引入,我們基本上解決了數(shù)據(jù)重復(fù)查詢的問題,另外我們數(shù)據(jù)的提前集中查詢也有助于啟發(fā)我們主動通過數(shù)據(jù)庫批量查詢進一步提升系統(tǒng)性能。而且上下文構(gòu)造的過程其實也是數(shù)據(jù)校驗的過程,通過上下文的提前構(gòu)建,我們在一定程度上實現(xiàn)了預(yù)校驗的邏輯,從而可以提前發(fā)現(xiàn)異常數(shù)據(jù),避免寫入臟數(shù)據(jù)和不必要的數(shù)據(jù)回滾操作。
上下文的引入其實并不算什么架構(gòu)上的改進,它主要是解決了數(shù)據(jù)對象重復(fù)查詢的問題,但是也引入了一些新的痛點,首先就是我們的數(shù)據(jù)模型中數(shù)據(jù)對象往往比較多且關(guān)系復(fù)雜,這導(dǎo)致我們的上下文構(gòu)造邏輯十分冗長。而且同一個業(yè)務(wù)域內(nèi)不同的接口使用的上下文對象中屬性有較大重疊,但是也有各自的差異,因此這些上下文對象的構(gòu)造邏輯又開始出現(xiàn)大量的重復(fù)編碼或者混亂的封裝。比如詢量單的新建接口與修改接口對應(yīng)的上下文中80%的屬性是相同的,這些屬性的查詢和關(guān)聯(lián)邏輯造成了大量的重復(fù)編碼。除了重復(fù)編碼問題之外,上下文機制也并沒有從根本上解決多場景下業(yè)務(wù)流程差異復(fù)雜度高的問題。
第三代:數(shù)據(jù)模型與業(yè)務(wù)模型的分離
在第二代架構(gòu)中我們雖然將數(shù)據(jù)對象的查詢集中到了上下文構(gòu)造步驟中執(zhí)行,但是上下文對象的定義是和接口方法綁定的。對外暴露多少服務(wù)我們就會定義多少上下文對象,甚至不同的場景也會有各自的上下文構(gòu)造邏輯,此時系統(tǒng)的數(shù)據(jù)模型依然隱藏在了具體的業(yè)務(wù)邏輯中。
在一次次的改進嘗試中,我們逐漸意識到多場景化的業(yè)務(wù)特性賦予我們一個動態(tài)的業(yè)務(wù)模型(或者說業(yè)務(wù)規(guī)則集),但是我們的數(shù)據(jù)模型卻是靜態(tài)的,數(shù)據(jù)模型的多場景化程度遠小于業(yè)務(wù)規(guī)則的多場景化程度,即:同一個功能模塊在不同場景下的業(yè)務(wù)規(guī)則存在差異,但卻始終在操作同一套數(shù)據(jù)模型。有些同學(xué)可能會對這一結(jié)論產(chǎn)生質(zhì)疑:在不同的場景下我們對數(shù)據(jù)對象的構(gòu)造也是不同的,比如只有快車的單元下才會有關(guān)鍵詞,京X的單元上綁定的是應(yīng)用集,而直投單元上綁定的是流量包等等,這些例子是不是都說明我們的數(shù)據(jù)模型也在隨業(yè)務(wù)規(guī)則一起動態(tài)變化著呢?對于這個問題我們需要“細品”一下:“數(shù)據(jù)對象屬性值的設(shè)置和校驗”到底是屬于業(yè)務(wù)模型的范疇還是數(shù)據(jù)模型的范疇?其實我們所說的數(shù)據(jù)模型指的是實體及實體之間的關(guān)系, 不論某個產(chǎn)品線或計劃類型是否會去設(shè)置某個子屬性的值,只要我們的數(shù)據(jù)模型完成了定義,那么在任何場景下數(shù)據(jù)模型的中實體的定義及實體之間的關(guān)系都是不變的,實體只要定義出來,它會一直在那,只是某些場景下其屬性值為null而已。而實體屬性值的設(shè)置邏輯則是典型的業(yè)務(wù)模型的范疇。
在明確了“多場景化的動態(tài)業(yè)務(wù)模型是建立在一個相對靜態(tài)的數(shù)據(jù)模型之上”這一本質(zhì)之后,為了解決上下文對象構(gòu)造復(fù)雜度高及重復(fù)編碼的問題,我們需要做的就是數(shù)據(jù)模型的分離和下沉,為此我們引入了領(lǐng)域驅(qū)動設(shè)計思想中的“聚合”概念。在這篇文章里我們不需要教條地引用DDD中關(guān)于聚合的定義,它的含義可以通俗的理解為:一組關(guān)聯(lián)密切且關(guān)系明確的實體或值對象的集合,一個聚合通常會支撐著一個功能極其內(nèi)聚的上層業(yè)務(wù)模塊。一個聚合中會定義唯一個聚合根對象,聚合根是整個聚合中實體操作的中心,聚合中的全部實體都可以通過聚合根直接或間接的訪問到。聚合根通常并不難確定,比如計劃聚合的聚合根自然就是Campaign實體,我們可以直接通過Campaign聚合根對象直接引用到計劃下的預(yù)算、投放時段等子實體信息。
將聚合的概念落地到代碼架構(gòu)中我們需要做以下升級:
1.根據(jù)業(yè)務(wù)流程設(shè)計合理的數(shù)據(jù)模型,需要注意的是數(shù)據(jù)模型中的實體并不一定要與底層的庫表一一對應(yīng),而是應(yīng)該從業(yè)務(wù)本質(zhì)出發(fā)完成實體劃分和定義,另外在模型中也需要體現(xiàn)實體之間的關(guān)聯(lián)關(guān)系。
2.在業(yè)務(wù)流程和底層數(shù)據(jù)庫之間增加一個聚合層,在這一層中將第一步設(shè)計的數(shù)據(jù)模型定義為Java對象,其中實體之間的關(guān)系則轉(zhuǎn)化為類與屬性的關(guān)系。比如AdGroup領(lǐng)域?qū)ο髢?nèi)屬性除了體現(xiàn)ad_group表中定義的字段之外,也定義了單元下的人群、流量包、創(chuàng)意列表等子實體對應(yīng)的屬性。
3.上層的業(yè)務(wù)流程對聚合中實體的訪問和修改都是通過聚合根實現(xiàn)的,而要想獲取聚合根則必須通過聚合層暴露出來的Repository接口。
第三步提到的Repository層接口是完全面向數(shù)據(jù)模型定義的,幾乎與業(yè)務(wù)無關(guān),通常不會為某個特殊的業(yè)務(wù)場景定義專用的數(shù)據(jù)查詢或?qū)懭敕椒?,它定義的都是通用的數(shù)據(jù)訪問接口,讓上層業(yè)務(wù)以聲明式的方法獲取所需的聚合根對象(或集合)。Repository的將數(shù)據(jù)對象的查詢和實體關(guān)系的組裝邏輯屏蔽在其接口實現(xiàn)中,上層業(yè)務(wù)不需要再次執(zhí)行聚合根下子實體對象的查詢和關(guān)聯(lián)邏輯。
?
??
引入聚合后上下文的構(gòu)造和數(shù)據(jù)的寫入流程得以極大地簡化
?
從上圖可以看出,由于上下文中的很多數(shù)據(jù)對象都被轉(zhuǎn)移到了聚合中,之前繁瑣的數(shù)據(jù)查詢和關(guān)聯(lián)邏輯被分離下沉到了Repository的實現(xiàn)中,業(yè)務(wù)模型中不同的服務(wù)接口可以直接復(fù)用Repository中沉淀的數(shù)據(jù)查詢和組裝邏輯,上下文構(gòu)造得以極大的精簡,重復(fù)編碼問題也得到了根本性的解決,體現(xiàn)了我們架構(gòu)目標(biāo)中“代碼易復(fù)用”的要求。
除了更加靈活和優(yōu)雅的復(fù)用數(shù)據(jù)查詢和組裝邏輯之外,聚合的引入讓我們實現(xiàn)了數(shù)據(jù)模型和業(yè)務(wù)模型的分離,聚合層幾乎與業(yè)務(wù)流程無關(guān),直接體現(xiàn)數(shù)據(jù)模型的完整全貌。當(dāng)有新同學(xué)加入的時候,可以通過閱讀聚合層代碼獲取最全、最準確的數(shù)據(jù)模型定義,不再需要從代碼中四處搜集對象關(guān)聯(lián)關(guān)系的蛛絲馬跡,這體現(xiàn)了我們架構(gòu)目標(biāo)中“邏輯易傳承”的要求。
??
RE降級后的補數(shù)邏輯一直是一件令人頭痛的事情,聚合的引入可以極大地簡化這一流程
本文主要探討的是我們引入聚合的動機,關(guān)于數(shù)據(jù)模型的設(shè)計、Repository接口的實現(xiàn)和使用相關(guān)的實戰(zhàn)內(nèi)容只是點到為止,關(guān)于這部分的詳細內(nèi)容屬于多體系架構(gòu)中的數(shù)據(jù)模型管理體系,我們將在該體系的設(shè)計中進行深入的探討。 其實就我個人的實踐經(jīng)驗而言,在實現(xiàn)架構(gòu)升級所作出的眾多嘗試中,聚合的引入是給我?guī)硇腋8凶顝姷囊豁椄倪M,但是我始終沒能找到一種合適的表達方式將我之所感無所保留地傳遞給大家,所言之語總是蒼白,或許聚合引入帶來的收益只有讓大家在實踐中去親身感受了。 另外熟悉領(lǐng)域驅(qū)動設(shè)計的同學(xué)可能已經(jīng)從上面的設(shè)計中嗅到了一絲DDD的味道,但是可能又會覺得沒有那么DDD,關(guān)于這個問題限于當(dāng)前陳述上下文的原因還不好直接給予解答,容筆者在這里賣個關(guān)子,在后面的系列文章中我們會詳細闡明這種設(shè)計的細節(jié)和考量。
第四代:領(lǐng)域能力拆分與編排
通過引入聚合我們基本上解決了數(shù)據(jù)查詢邏輯復(fù)用的問題,但是由于多平臺、多維度和多場景化帶來的業(yè)務(wù)復(fù)雜度的問題卻依然存在。而解決這個問題的基本思路其實祖師爺已經(jīng)給我們準備好了,那就是組合復(fù)用原則。
作為一個典型的2B的平臺,我們的業(yè)務(wù)特點就是流程冗長復(fù)雜,一個業(yè)務(wù)流程通常由多個流程節(jié)點組成,比如單元新建流程,可以分為:基礎(chǔ)信息設(shè)置、單元名稱設(shè)置、投放周期設(shè)置、投放位置設(shè)置、定向設(shè)置、出價設(shè)置、關(guān)鍵詞設(shè)置等多個節(jié)點組成。這些節(jié)點再疊加上不同產(chǎn)品線(展位、快車、觸點)、站外不同媒體(頭、騰、百、快、京X)、不同的投放平臺(京準通、流量貨幣化、京易投)以及不同的站點(國內(nèi)、泰國、印尼、出海)等多維度的業(yè)務(wù)場景,就使系統(tǒng)具備了
?業(yè)務(wù)復(fù)雜度,其中
?為不同業(yè)務(wù)細分維度下的場景復(fù)雜度,而組合復(fù)用原則就是專門為解決這一問題而生的。
組合復(fù)用原則強調(diào)復(fù)雜問題的拆分,拆分出來的最小子問題可以互不干擾地進行獨立的迭代。在此基礎(chǔ)上,上層模塊可以通過對最小子問題的組合編排實現(xiàn)一項完整的業(yè)務(wù)功能。由于最小子問題之間彼此正交,我們獨立維護各個最小子問題的編碼復(fù)雜度就可以降級為
??;谠撍枷耄覀冊谛录軜?gòu)中引入了領(lǐng)域能力拆分與編排機制。
領(lǐng)域能力的識別與拆分
在新架構(gòu)中我們會將一個完整的業(yè)務(wù)流程正交分解為多個“能力節(jié)點”。這里所說的“正交分解”是指拆分出來的各個子模塊之間互不干擾,可以獨立進行迭代。舉個例子來說,在早期大家進行能力梳理的時候,有同學(xué)從單元新建流程中拆分出了“出價信息校驗”和“出價設(shè)置”兩個能力節(jié)點,這其實是不合理的。因為出價信息的校驗和出價屬性的設(shè)置并不正交,他們互相依賴,我們應(yīng)該這兩段邏輯合并到一起,抽象為一個“出價設(shè)置”節(jié)點。
能力節(jié)點主要定義了系統(tǒng)中各個原子模塊的功能范圍。一般來說,一個能力節(jié)點通常包含一個能力門面和0到多個能力實例。能力門面并不承接具體的業(yè)務(wù)邏輯,它的作用是對外暴露統(tǒng)一的調(diào)用入口及請求轉(zhuǎn)發(fā),具體的業(yè)務(wù)邏輯則由能力門面下的能力實例承接。比如出價設(shè)置節(jié)點下會按照出價類型劃分為:手動出價、tCPA智能出價、MC智能出價、eCPC智能出價幾個具體的領(lǐng)域能力實例,而在人群定向設(shè)置節(jié)點下則有京選店鋪人群設(shè)置、樂高人群設(shè)置和自定義人群設(shè)置幾個領(lǐng)域能力實例。
能力編排與請求路由
將整個系統(tǒng)劃分為多個獨立的能力節(jié)點之后,接下來就需要通過能力編排將這些能力節(jié)點串聯(lián)到一起組裝成一個完成的服務(wù)。如下圖所示,所謂的能力編排就是將業(yè)務(wù)流程中所需要的原子模塊對應(yīng)的能力節(jié)點串聯(lián)起來,定義好他們之間數(shù)據(jù)傳遞的方式和編排規(guī)則。需要注意的是,能力編排操作的是能力節(jié)點而不是能力實例,在處理服務(wù)請求時,每一個能力節(jié)點負責(zé)將請求路由到正確的領(lǐng)域能力實例中進行處理。之所以這樣設(shè)計是因為我們的業(yè)務(wù)流程相對穩(wěn)定,系統(tǒng)對外提供的服務(wù)流程中業(yè)務(wù)節(jié)點及節(jié)點間的執(zhí)行順序很少會發(fā)生變化,需求迭代往往是對某個能力節(jié)點進行橫向的拓展,也就是對具體的領(lǐng)域能力實例進行增刪或者修改。通過能力節(jié)點的抽象及路由機制的引入,我們將動態(tài)變化著的部分從相對穩(wěn)定的業(yè)務(wù)流程中分離出去,從而保障核心流程的穩(wěn)定性不被頻繁變化著的需求所影響,這一點與我們當(dāng)時做數(shù)據(jù)模型與業(yè)務(wù)模型分離的動機是一致的,本質(zhì)上都是在隔離變化。
??
一個能力編排示例(點擊放大查看)
除了能力編排框架之外,能力實例的路由機制也是實現(xiàn)復(fù)雜度降維的關(guān)鍵。如下圖所示,路由機制通過將能力門面及門面下用于承接不同場景下具體業(yè)務(wù)規(guī)則的能力實例打包到一起,同時也將原子業(yè)務(wù)模塊內(nèi)的場景復(fù)雜度封裝屏蔽在了模塊內(nèi)部,使上層的業(yè)務(wù)流程定義只需要關(guān)注一次完整的請求需要使用哪些原子業(yè)務(wù)模塊(也就是能力節(jié)點),而無需關(guān)注這個節(jié)點下具體的能力實例,當(dāng)請求到來時,處理流程流經(jīng)相應(yīng)的能力節(jié)點時,將通過當(dāng)前請求上下文中的參數(shù)自動識別業(yè)務(wù)身份并將請求路由到相應(yīng)的能力實例上進行處理。
??
能力編排操作的是能力節(jié)點而不是領(lǐng)域能力實例,這樣可以讓能力實例更靈活的進行橫向拓展(點擊放大查看)
上文提到了能力編排和路由機制都已經(jīng)在新工程中提供了框架化的實現(xiàn),本文主要是為了分享我們架構(gòu)設(shè)計的動機,所以不會介紹這些功能的實現(xiàn)原理和使用方法,對此感興趣的同學(xué)可以觀看能力編排框架專門的視頻教程:https://cf.jd.com/pages/viewpage.action?pageId=954674772?
標(biāo)準的業(yè)務(wù)執(zhí)行模版
在第二、三代架構(gòu)中,系統(tǒng)處理請求時會先執(zhí)行全部參數(shù)的校驗,校驗通過后再將單元新建處理所需的全部數(shù)據(jù)對象查詢出來。在這個過程中可以充分利用批量查詢接口提升系統(tǒng)性能,同時也會對查詢出來的數(shù)據(jù)對象進行校驗,如果存在不合法的數(shù)據(jù)則終止處理流程,如果數(shù)據(jù)對象查詢一切正常,則執(zhí)行后續(xù)的數(shù)據(jù)組裝和處理邏輯,最后批量執(zhí)行數(shù)據(jù)的持久化。盡管會存在上文分析的一些問題,但是這種模式所帶來的收益依然具備十分重要的意義。
然而在新架構(gòu)中我們將原先連貫的業(yè)務(wù)邏輯打散,按照邏輯的內(nèi)聚性將他們重組到一個能力實例中,然后在領(lǐng)域服務(wù)中通過能力編排將這些能力實例組裝成一個完整的業(yè)務(wù)流程。這雖然貫徹了組合復(fù)用的原則,但是如果我們只是簡單地通過順序執(zhí)行多個能力實例來組裝領(lǐng)域服務(wù),那么由于每個能力內(nèi)部又依次執(zhí)行與一小撮業(yè)務(wù)屬性相關(guān)的參數(shù)校驗、依賴數(shù)據(jù)查詢、邏輯處理乃至數(shù)據(jù)持久化操作,從代碼邏輯的執(zhí)行流程上看我們又回退到了“數(shù)據(jù)訪問與邏輯處理互相交織”的第一代架構(gòu)上。除此之外,雖然服務(wù)之間邏輯上互相獨立,但是他們可能會依賴相同的數(shù)據(jù)對象,比如人群包的綁定與預(yù)算調(diào)整兩個能力都會依賴AdGroup對象,如果框架只是簡單地串聯(lián)執(zhí)行這兩個能力,那么必然會造成數(shù)據(jù)的重復(fù)查詢。
為了解決上述問題,我們引入了標(biāo)準的業(yè)務(wù)流程Executor模板,它把業(yè)務(wù)業(yè)務(wù)流程抽象為:參數(shù)校驗、上下文初始化、上下文校驗、業(yè)務(wù)邏輯處理、數(shù)據(jù)持久化、發(fā)布事件幾個標(biāo)準步驟,不論是領(lǐng)域能力的封裝還是領(lǐng)域服務(wù)的實現(xiàn)都必須繼承該模板。標(biāo)準業(yè)務(wù)執(zhí)行模板的引入一方面能夠規(guī)范開發(fā)者的設(shè)計和實現(xiàn),另一方面也將代碼邏輯的串聯(lián)執(zhí)行權(quán)從開發(fā)者手中轉(zhuǎn)移到了能力編排框架中,讓框架能夠?qū)崿F(xiàn)邏輯的自動重組和執(zhí)行,而開發(fā)者專注于業(yè)務(wù)邏輯并進行填空式開發(fā)。而框架在獲取到了代碼邏輯的串聯(lián)執(zhí)行權(quán)之后就可以在領(lǐng)域服務(wù)的每個標(biāo)準步驟中按照能力編排執(zhí)行圖組裝調(diào)用的各個能力實例中相應(yīng)標(biāo)準步驟,從而將打散到不同能力實例中的業(yè)務(wù)邏輯次按照標(biāo)準步驟的類別還原回連貫完整的業(yè)務(wù)邏輯,如下圖所示:
??
標(biāo)準業(yè)務(wù)流程模版的引入讓框架進行業(yè)務(wù)流程還原成為可能
除了實現(xiàn)業(yè)務(wù)邏輯按標(biāo)準步驟自動還原之外,由于標(biāo)準流程模板對每一個標(biāo)準步驟方法的執(zhí)行參數(shù)、依賴的上下文及返回值對象都進行了通用化的抽象,能力編排框架也得以在各個能力標(biāo)準步驟調(diào)用之間插入?yún)?shù)及上下文的映射和傳遞邏輯,從而在不同能力之間以及能力與領(lǐng)域服務(wù)之間實現(xiàn)數(shù)據(jù)分發(fā)和共享。需要說明的是盡管這些流程都可以采用默認的自動處理規(guī)則,開發(fā)者也可以通過能力編排框架提供的DSL對默認的串聯(lián)執(zhí)行、數(shù)據(jù)傳遞、異常處理等規(guī)則進行修改。
在我們新架構(gòu)中,我們通過領(lǐng)域能力拆分將復(fù)雜的問題域正交分解為多個互相獨立的最小問題域,讓設(shè)計者可以分而治之,逐個擊破,降低了問題的復(fù)雜度和設(shè)計成本,同時單個能力節(jié)點下不同業(yè)務(wù)場景下的業(yè)務(wù)邏輯被分離到了不同的領(lǐng)域能力實例中,避免出現(xiàn)不同業(yè)務(wù)場景互相交織,便于快速梳理業(yè)務(wù)邏輯,定位改動點,這些都體現(xiàn)了“設(shè)計易拓展”的設(shè)計目標(biāo)。
由于拆分出來的各個能力節(jié)點彼此正交,內(nèi)部邏輯十分內(nèi)聚,因此可以在各自的維度上進行迭代,比如同樣是在單元維度下的出價設(shè)置和人群設(shè)置能力就分別在出價類型和人群類型這兩個場景維度上各自進行路由,避免了不同場景互相交織帶來的圈復(fù)雜度上升問題,也能夠更加靈活在不同的業(yè)務(wù)場景中實現(xiàn)能力復(fù)用。同時由于我們的業(yè)務(wù)本質(zhì)上就是對物料的創(chuàng)編,物料新建流程中的能力往往可以直接在物料修改流程中復(fù)用。還有一個特殊的場景就是批量物料操作類型的請求,借助能力編排框架提供的循環(huán)編排和數(shù)據(jù)共享機制,我們可以在領(lǐng)域服務(wù)的開始先批量完成所需數(shù)據(jù)的查詢,然后通過循環(huán)編排機制循環(huán)復(fù)用單個請求處理能力中的純內(nèi)存調(diào)用的數(shù)據(jù)校驗及數(shù)據(jù)處理邏輯,最后在批量操作領(lǐng)域服務(wù)中批量完成聚合根對象集合的寫入,在實現(xiàn)邏輯復(fù)用的同時又能保證數(shù)據(jù)準確性及性能,以上特性都體現(xiàn)了“代碼易復(fù)用”的設(shè)計目標(biāo)。
領(lǐng)域能力的編排邏輯提供了一個業(yè)務(wù)流程的全景視圖,當(dāng)有新同學(xué)加入時,可以迅速通過閱讀能力編排邏輯快速建立起對業(yè)務(wù)的宏觀認知,再結(jié)合在第三代架構(gòu)中引入的聚合機制,可以讓新同學(xué)快速熟悉數(shù)據(jù)模型與業(yè)務(wù)流程。同時通過路由機制系統(tǒng)中全部的業(yè)務(wù)規(guī)則打包拆分成數(shù)量有限且邊界清晰的能力節(jié)點,當(dāng)需要快速梳理需求點對應(yīng)業(yè)務(wù)規(guī)則時,可以由粗及細,先確定需求點歸屬的能力節(jié)點,然后根據(jù)場景定位到具體的能力實例,進而可以從代碼中獲取業(yè)務(wù)規(guī)則,這些特性都體現(xiàn)了“邏輯易傳承”的設(shè)計目標(biāo)。
??
基于能力拆分與編排的代碼架構(gòu),最顯著的收益就是同一個能力可以在不同的領(lǐng)域服務(wù)中直接復(fù)用(點擊放大查看)
總結(jié)
以上便是我們?yōu)閷崿F(xiàn)新架構(gòu)所進行的種種嘗試,這些設(shè)計是否正確我們也正在通過需求實戰(zhàn)來進行驗證,把他們發(fā)出來不是要說服大家認同,而是想通過對架構(gòu)演進歷程的推演幫助大家更好的理解我們新架構(gòu)中各項功能的設(shè)計動機,從而更快的上手進行開發(fā);另一方面也希望能夠激發(fā)大家的思考和討論,哪怕是對上述方案的質(zhì)疑和批判,一個好的架構(gòu)一定是在一次次批評聲中改進出來的,我至今還在懷念當(dāng)初摸索新架構(gòu)時那些與永亮(我的良師益友,部門內(nèi)探索中臺化及領(lǐng)域驅(qū)動設(shè)計思想的第一人)爭論到凌晨2、3點的日子。
審核編輯 黃宇
-
DSL
+關(guān)注
關(guān)注
2文章
58瀏覽量
38293 -
數(shù)據(jù)模型
+關(guān)注
關(guān)注
0文章
49瀏覽量
10001 -
架構(gòu)
+關(guān)注
關(guān)注
1文章
513瀏覽量
25468
發(fā)布評論請先 登錄
相關(guān)推薦
評論