一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

循序漸進(jìn)搭建復(fù)雜B端系統(tǒng)整潔架構(gòu)

京東云 ? 來源:jf_75140285 ? 作者:jf_75140285 ? 2024-11-18 17:11 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

前言:信息時代技術(shù)更迭和傳播速度不斷加快,技術(shù)變得泛娛樂化,大數(shù)據(jù)、云計算、區(qū)塊鏈、元宇宙、大模型,一代代技術(shù)熱點在社會輿論的裹挾之下不斷地吸引著資本的眼球,技術(shù)人員為了不被時代所淘汰也不得不時刻追趕潮流。在這樣一個時代背景下,軟件工程作為一門不起眼到有些枯燥的古老學(xué)科,似乎早已被開發(fā)者們遺忘在角落。作為一名技術(shù)人員我們自然應(yīng)該時刻保持對前沿技術(shù)的追蹤,然而,當(dāng)發(fā)生線上問題我們卻面對著成片的屎山代碼毫無頭緒時;當(dāng)業(yè)務(wù)方提出個性化需求我們卻因為不敢對系統(tǒng)做出修改而強(qiáng)迫對方做出妥協(xié)時;當(dāng)一次請求處理流程中出現(xiàn)多達(dá)數(shù)萬次重復(fù)地數(shù)據(jù)庫操作而影響到整個系統(tǒng)的穩(wěn)定性時,大家都應(yīng)該沉下心來思考一下,我們是不是忘記了作為一名程序員的初心和對代碼的極致追求。 還記的當(dāng)年我抱著朝圣的心態(tài)從傳統(tǒng)行業(yè)踏入京東職場時的興奮與期待,然而這份期待很快就被四處可見的屎山代碼給澆滅了,后來從朋友口中了解到其他頭部互聯(lián)網(wǎng)廠商的業(yè)務(wù)系統(tǒng)其實也是半斤八兩。這似乎是軟件行業(yè)中的一個電車難題,一邊是無盡的業(yè)務(wù)需求和倒排的工期,一邊是補(bǔ)丁摞補(bǔ)丁的糟糕代碼,是繼續(xù)泡在醬缸中縫縫補(bǔ)補(bǔ)還是向屎山代碼說不,開發(fā)人員被困在中間不知該如何抉擇。然而事實上,追求整潔架構(gòu)與提升研發(fā)效率之間從來就不是一個悖論。正如Robert C.Martin在其著作《Clean Architecture》中所說:“不管你多敬業(yè)、加多少班,(在面對爛系統(tǒng)時)你仍然會寸步難行,因為你大部分的精力是在應(yīng)對混亂(而不是在開發(fā)需求)?!痹斐晌覀冋占影嘹s需求和疲于應(yīng)對線上問題的根本原因,恰是那些不被我們重視的糟糕代碼。業(yè)務(wù)天然就是復(fù)雜的,這決定了軟件系統(tǒng)的本質(zhì)復(fù)雜度(Essential Complexity),這種復(fù)雜度是無法通過軟件架構(gòu)去消除的。那么解決上述問題的關(guān)鍵就是找到某種架構(gòu)去引導(dǎo)開發(fā)者對復(fù)雜業(yè)務(wù)進(jìn)行問題拆解,分而治之,在這個基礎(chǔ)上再通過標(biāo)準(zhǔn)規(guī)約和工具約束及輔助開發(fā)者寫出可理解、易拓展、好維護(hù)的代碼,以此來對抗軟件系統(tǒng)本身的偶然復(fù)雜度(Accidental Complexity,F(xiàn)rederick P.Brooks,Jr, 《The Mythical Man-Month》)。 為了找到這樣的一種架構(gòu),我們從19年就開始對各類架構(gòu)思想和實踐案例進(jìn)行了深入地學(xué)習(xí)和探索,并在接下來的3年時間里通過局部架構(gòu)演進(jìn)的方式進(jìn)行了大量的實踐驗證,在這個過程中我們對這些架構(gòu)思想的理解也從早期的懵懂教條式執(zhí)行逐漸做到了如今的融匯貫通,并最終在22年底形成了一套成體系的框架及方法論,并在京東廣告投放平臺重構(gòu)工作中進(jìn)行了實戰(zhàn)應(yīng)用。本文也將以廣告投放平臺架構(gòu)升級作為背景案例,從設(shè)計思想到落地框架,循序漸進(jìn)地為您介紹這套新架構(gòu)的誕生始末,而這套架構(gòu)思想的演進(jìn)歷程則在《改進(jìn)我們的架構(gòu)》一文中有詳細(xì)的闡述。

一、架構(gòu)升級背景

與高并發(fā)請求給C端系統(tǒng)帶來的系統(tǒng)高性能、高可用能力挑戰(zhàn)相比,B端系統(tǒng)所面臨的挑戰(zhàn)則是如何在海量多維度、多模塊、多場景融合的復(fù)雜業(yè)務(wù)需求中保持系統(tǒng)健康、穩(wěn)定、快速地迭代。京東廣告投放平臺就是一個典型的復(fù)雜B端業(yè)務(wù)系統(tǒng),它承擔(dān)著集成廣告業(yè)務(wù)體系中各個垂直業(yè)務(wù)模塊,構(gòu)建、維護(hù)和分發(fā)廣告物料的重要職責(zé)。經(jīng)過多年迭代,京東廣告投放系統(tǒng)目前已集成40余個垂直業(yè)務(wù)系統(tǒng),支撐7條核心產(chǎn)品線,先后賦能10余個獨立投放平臺,維護(hù)著一個擁有200余個業(yè)務(wù)實體的龐大數(shù)據(jù)模型,每天都需要處理海量長事務(wù)、多系統(tǒng)交互的復(fù)雜業(yè)務(wù)請求。同時作為整個廣告業(yè)務(wù)鏈路上的首發(fā)環(huán)節(jié)和功能門面,廣告投放系統(tǒng)每年都要承接400余個來自不同業(yè)務(wù)方的差異化需求、執(zhí)行1000余次代碼合并及600余次功能發(fā)布。在極高的需求密度之下,作為撬動廣告主預(yù)算的重要戰(zhàn)場,投放系統(tǒng)在為廣告主提供優(yōu)秀投放體驗的同時,還需要每天向廣告業(yè)務(wù)鏈路穩(wěn)定輸送PB級的物料數(shù)據(jù),這對系統(tǒng)的性能、穩(wěn)定性以及團(tuán)隊的研發(fā)效能提出了極高的要求。

wKgaomc7BH6AVYDVAANn0HkT3GI202.jpg

廣告投放平臺是一個典型的多平臺、多模塊集成的復(fù)雜B端系統(tǒng)

二、傳統(tǒng)架構(gòu)的研發(fā)痛點

近年來隨著技術(shù)和業(yè)務(wù)的飛速發(fā)展,新的廣告業(yè)務(wù)形態(tài)和投放組件層出不窮,廣告物料結(jié)構(gòu)愈發(fā)復(fù)雜。與此同時,為了提高廣告主留存和撬動預(yù)算,業(yè)界各大平臺都在向著極簡版、智能化和集成化的方向發(fā)展。這些新的業(yè)態(tài)發(fā)展方向一方面給廣告主帶來了更加便捷和流暢的投放體驗,另一方面也讓投放系統(tǒng)內(nèi)部業(yè)務(wù)流程愈發(fā)復(fù)雜,如何用有限的研發(fā)人力快速支撐越來越多的多場景復(fù)雜業(yè)務(wù)需求成為各大廣告投放平臺必須要解決的關(guān)鍵問題。然而傳統(tǒng)的“三層架構(gòu)+面向數(shù)據(jù)庫編程”的研發(fā)模式由于過于簡單的封裝及粗暴的設(shè)計思想在面對這些高復(fù)雜度業(yè)務(wù)需求時變得愈發(fā)吃力,逐漸成為阻塞研發(fā)效能提升的罪魁禍?zhǔn)住?/p>

客觀:傳統(tǒng)架構(gòu)面對高復(fù)雜度的業(yè)務(wù)時毫無應(yīng)對之法

作為一個典型的Web應(yīng)用,廣告投放系統(tǒng)長期以來采用的都是傳統(tǒng)的三層架構(gòu),這種沒有架構(gòu)的架構(gòu)極其簡單、易上手,因此一直以來都是業(yè)界的主流。但是由于它缺少統(tǒng)一明確的邏輯拆分與封裝工具,業(yè)務(wù)的復(fù)雜度會等比滲透到代碼實現(xiàn)中,進(jìn)而導(dǎo)致系統(tǒng)的代碼復(fù)雜度飆升,模塊之間隨意耦合,邏輯糾結(jié)纏繞,經(jīng)過幾輪迭代之后就成了看不懂、動不了、不敢動的醬缸代碼。這些看似基礎(chǔ)的編碼問題實際上卻是阻礙我們研發(fā)效能提升的罪魁禍?zhǔn)祝?/p>

1.需求交付提速困難:不同平臺、產(chǎn)品線及業(yè)務(wù)場景邏輯交織,晦澀難懂,導(dǎo)致系統(tǒng)功能迭代時梳理及設(shè)計耗時漫長,同時在測試階段需投入大量精力進(jìn)行聯(lián)動功能回歸;

2.對新業(yè)態(tài)的接受度低、響應(yīng)能力差:系統(tǒng)拓展性差,且模塊間深度耦合,在面對新業(yè)態(tài)時我們卻為了控制影響范圍而不得讓業(yè)務(wù)選擇讓步,結(jié)果錯失商機(jī)。

3.問題評估和排查效率低下:缺少明確統(tǒng)一的邏輯歸屬與封裝準(zhǔn)則,邏輯四處復(fù)寫與逃逸,導(dǎo)致問題定位時間長,難以快速評估影響范圍和修復(fù)方案。

4.接口性能與穩(wěn)定性下降:混亂的封裝與復(fù)用導(dǎo)致一次接口請求就會產(chǎn)生導(dǎo)致大量重復(fù)的IO操作,嚴(yán)重影響接口性能,每臨大促都需要花費(fèi)大量人力進(jìn)行性能優(yōu)化。

主觀:“面向數(shù)據(jù)庫編程”的設(shè)計思維讓系統(tǒng)加速腐化

我們的業(yè)務(wù)本質(zhì)就是獲取、處理、存儲及傳輸數(shù)據(jù),在傳統(tǒng)架構(gòu)中業(yè)務(wù)邏輯通常以事務(wù)腳本(Transaction Script)的形式實現(xiàn):業(yè)務(wù)規(guī)則直接在開發(fā)者的大腦中轉(zhuǎn)化為數(shù)據(jù)庫的增刪改查操作(這也是很多程序員調(diào)侃自己是CRUD工程師的原因),然后被寫到代碼里。這種模式在場景單一、需求簡單的業(yè)務(wù)發(fā)展早期階段可以快速實現(xiàn)功能,但是隨著業(yè)務(wù)復(fù)雜度的提升,這種過于粗糙的設(shè)計思維所帶來的問題就會逐漸顯現(xiàn)出來:

1.難以建立對整個數(shù)據(jù)模型的全景認(rèn)知:完整的數(shù)據(jù)模型信息被拆分到不同的業(yè)務(wù)接口實現(xiàn)中,往往需要對整個工程代碼進(jìn)行逐行review才能梳理出完整的數(shù)據(jù)模型,當(dāng)工程代碼量和數(shù)據(jù)模型膨脹到一定程度后,模型梳理成本急劇飆升。

2.模型野蠻膨脹、存在大量相似或重復(fù)的實體,增加系統(tǒng)運(yùn)維成本:數(shù)據(jù)模型全景認(rèn)知的缺失導(dǎo)致開發(fā)者難以進(jìn)行統(tǒng)一的頂層設(shè)計,數(shù)據(jù)模型泛化表達(dá)能力弱,在多需求并行開發(fā)過程中極易形成信息孤島,無法實現(xiàn)模型合并與共享,系統(tǒng)中存在大量相似的業(yè)務(wù)實體與庫表結(jié)構(gòu)。

3.代碼對業(yè)務(wù)語義表達(dá)能力弱、業(yè)務(wù)知識傳承效率低下:代碼經(jīng)過開發(fā)者的轉(zhuǎn)譯失去了對業(yè)務(wù)語義的直接表達(dá),導(dǎo)致系統(tǒng)中存在大量只有開發(fā)者本人才能理解的魔法邏輯,系統(tǒng)維護(hù)與人員更迭成本過高。

要想解決上述問題,就亟需一種面向未來的架構(gòu)思想來指導(dǎo)我們對系統(tǒng)進(jìn)行全面地升級。在此背景下,業(yè)界眾多平臺紛紛進(jìn)行了領(lǐng)域驅(qū)動設(shè)計思想的探索和嘗試,經(jīng)典的案例有阿里的星環(huán)與COLA、快手的Baldr等,京東也推出了藏經(jīng)閣平臺與Matrix框架。這些實踐案例和架構(gòu)迭代路線給了我們很多啟發(fā),本著腳踏實地、事實就是的基本原則,在經(jīng)過充分調(diào)研和長期驗證之后,我們立足于京東廣告業(yè)務(wù)的本質(zhì)特征推出了一套可復(fù)用的復(fù)雜B端業(yè)務(wù)支撐框架,其核心內(nèi)容可以分為PICASO能力編排框架與聚合及資源庫機(jī)制兩部分。

網(wǎng)絡(luò)上能夠找到很多介紹領(lǐng)域驅(qū)動設(shè)計思想的文章,但是大多都聚焦在對領(lǐng)域驅(qū)動設(shè)計中眾多術(shù)語和概念的介紹上,對領(lǐng)域驅(qū)動設(shè)計思想的落地實踐卻淺嘗輒止。再加上中英文語境的差異和國內(nèi)外軟件開發(fā)生態(tài)的不同,都在很大程度上將領(lǐng)域驅(qū)動設(shè)計思想“妖魔化”了,讓很多同學(xué)望而卻步或者不得其要義。然而我們在摸索實踐的過程中逐漸意識到,領(lǐng)域驅(qū)動設(shè)計作為一種軟件架構(gòu)設(shè)計的指導(dǎo)思想其實并沒有創(chuàng)造什么新的東西,而是對基本的軟件設(shè)計思想進(jìn)行的系統(tǒng)化總結(jié)和升華。但正是這種系統(tǒng)性的歸納將各類技巧、準(zhǔn)則和思想凝練成體系化的方法論,并且在行業(yè)內(nèi)形成了被所有開發(fā)者所公認(rèn)的行為準(zhǔn)則,這才是領(lǐng)域驅(qū)動設(shè)計思想強(qiáng)大生產(chǎn)力的源泉和魅力所在。 與傳統(tǒng)的三層架構(gòu)相比領(lǐng)域驅(qū)動設(shè)計思想其實并沒有復(fù)雜多少,其要義就在于保持業(yè)務(wù)、模型與代碼三者的統(tǒng)一,只要掌握了這一點,領(lǐng)域驅(qū)動設(shè)計思想中的各種理念都將是水到渠成的事情。初讀《領(lǐng)域驅(qū)動設(shè)計》時書中眾多晦澀的術(shù)語也曾讓我十分困惑,但其中的很多內(nèi)容其實已經(jīng)是很多優(yōu)秀架構(gòu)師的工作日常了。隨著對領(lǐng)域驅(qū)動設(shè)計思想理解的逐漸深入,我不時會產(chǎn)生“咳,這說的不就是xxx么”的感慨。這也是沒有辦法事,誰讓國外那些提前入局的大佬們牢牢掌握著專業(yè)領(lǐng)域的命名權(quán)呢。也正因為如此,在本文中我們不會去介紹、甚至?xí)M量避免引用領(lǐng)域驅(qū)動設(shè)計理論中的術(shù)語,避免大家一開始就陷入到那些晦澀難懂的概念里而無法自拔。希望大家能將更多的精力放在框架內(nèi)各個模塊的設(shè)計動機(jī)與運(yùn)行機(jī)制上,這才是我們最應(yīng)該思考和關(guān)注的內(nèi)容。至于領(lǐng)域驅(qū)動設(shè)計思想在新架構(gòu)演進(jìn)過程中的指導(dǎo)作用我們將會在《領(lǐng)域驅(qū)動設(shè)計與PICASO框架》一文中進(jìn)行詳細(xì)地介紹。

三、升級措施

(一)PICASO框架:從混亂到有序,構(gòu)建圖書館式的代碼架構(gòu)

圖靈獎得主Frederick在其著作《The Mythical Man-Month》中將軟件系統(tǒng)的復(fù)雜度劃分為本質(zhì)復(fù)雜度(Essential Complexity)和偶然復(fù)雜度(Accidental Complexity),其中本質(zhì)復(fù)雜度是問題本身所具有的復(fù)雜度,與求解方法無關(guān),而偶然復(fù)雜度是求解方法引入的復(fù)雜度。本質(zhì)復(fù)雜度無法避免,但是我們可以通過優(yōu)化求解方法來盡可能降低系統(tǒng)的偶然復(fù)雜度。這給了我們很大的啟發(fā),業(yè)務(wù)天然就是復(fù)雜的,這是一個客觀事實,架構(gòu)設(shè)計的目標(biāo)不是消除業(yè)務(wù)上的本質(zhì)復(fù)雜度,而是應(yīng)該引導(dǎo)和輔助開發(fā)者更好的拆解和分析業(yè)務(wù)帶來的復(fù)雜度(是handle而不是eliminate)。同時,軟件架構(gòu)應(yīng)該提供足夠靈活的標(biāo)準(zhǔn)規(guī)約與框架工具,讓所有開發(fā)者都能夠按照統(tǒng)一的思想寫出可理解、易拓展和好維護(hù)的代碼,減少甚至是消除由于沒有封裝或封裝不統(tǒng)一帶來的偶然復(fù)雜度。在這一思想的指導(dǎo)之下,經(jīng)過兩年多的打磨,我們推出了PICASO框架。

PICASO概述

PICASO是一套以領(lǐng)域驅(qū)動設(shè)計(Domain-Driven Design, DDD)作為思想內(nèi)核,專門為集成式復(fù)雜業(yè)務(wù)系統(tǒng)設(shè)計的通用基礎(chǔ)框架。它的命名來自“PICASOIs aContextualAbilitySeparate andOrchestrate Framework(PICASO是一種基于上下文的能力分解與編排框架)”的首字母縮寫。有趣的是這個縮略詞的發(fā)音恰好與西班牙現(xiàn)代派繪畫大師畢加索(Picasso)的姓名讀音相同,畢加索在畫作中經(jīng)常對人體部位進(jìn)行解構(gòu)和重組,在接下來的介紹中我們將發(fā)現(xiàn)這一點與PICASO框架所強(qiáng)調(diào)的能力拆分與編排思想有異曲同工之妙,而這也是我們最終采納這個命名的原因。

PICASO的命名啟發(fā)自筆者比較喜愛的一個開源項目——WINE,其功能是通過內(nèi)核適配器在Linux環(huán)境中運(yùn)行Windows應(yīng)用程序,其命名也是這種藏頭詩的風(fēng)格:WINEIsNotEumlator(WINE不是模擬器)。

PICASO框架的職責(zé)是引導(dǎo)開發(fā)者將復(fù)雜業(yè)務(wù)流程正交分解為多個簡單子問題,然后將這些簡單子問題的處理邏輯封裝為邊界明確的標(biāo)準(zhǔn)可執(zhí)行實體,在PICASO框架中這些可執(zhí)行實體被稱為領(lǐng)域能力。完成能力拆解之后,開發(fā)者可以通過PICASO提供的能力編排框架將不同的領(lǐng)域能力的組合成一個完整的請求處理流程,這個處理流程所在的可執(zhí)行實體就是一個領(lǐng)域服務(wù)。領(lǐng)域服務(wù)會為每次請求生成一個上下文對象,通過這個上下文對象可以在不同領(lǐng)域能力以及領(lǐng)域能力與領(lǐng)域服務(wù)之間進(jìn)行數(shù)據(jù)傳遞與共享,進(jìn)而避免重復(fù)及碎片化的IO操作。PICASO框架還提供了開箱即用的通用可執(zhí)行實體發(fā)現(xiàn)與路由組件,開發(fā)者可以通過該組件按功能域?qū)︻I(lǐng)域能力及領(lǐng)域服務(wù)進(jìn)行分組和聚合,每個分組對外暴露統(tǒng)一的請求路由門面,從而向上層調(diào)用實體屏蔽分組內(nèi)部的場景復(fù)雜度,進(jìn)而實現(xiàn)復(fù)雜度降維。如果在領(lǐng)域能力或領(lǐng)域服務(wù)的路由維度之外還存在其他維度的細(xì)微邏輯差異,開發(fā)者可以通過PICASO提供的拓展點機(jī)制進(jìn)一步實現(xiàn)差異點分離。同樣的,拓展點依然可以接入通用可執(zhí)行實體發(fā)現(xiàn)及路由組件,向上層實體屏蔽拓展點所在功能域內(nèi)的場景復(fù)雜度。

上文對PICASO框架的整體架構(gòu)進(jìn)行了整體地介紹,接下來我們將從軟件系統(tǒng)復(fù)雜度根源分析開始,循序漸進(jìn)地詳細(xì)闡述PICASO各個模塊的設(shè)計動機(jī)及運(yùn)行機(jī)制。

wKgZomc7BH-Afl6xAAR-OzAak40998.png

PICASO框架整體架構(gòu)

復(fù)雜度的根源

軟件設(shè)計的本質(zhì)就是持續(xù)對抗軟件本身產(chǎn)生的復(fù)雜度,早在最開始進(jìn)行新架構(gòu)探索的時候我們就意識到,構(gòu)建整潔架構(gòu)的前提是厘清系統(tǒng)復(fù)雜度的根源。

本質(zhì)復(fù)雜度

通過對復(fù)雜業(yè)務(wù)系統(tǒng)發(fā)展歷程的分析,我們發(fā)現(xiàn)業(yè)務(wù)復(fù)雜度一般來自水平方向上的多維度拓展和垂直方向上的多模塊集成。

業(yè)務(wù)發(fā)展的早期往往都是單一場景,隨著業(yè)務(wù)的發(fā)展,產(chǎn)品形態(tài)開始變得豐富多樣,服務(wù)的用戶及業(yè)務(wù)方也越來越多,業(yè)務(wù)架構(gòu)從原來的單點結(jié)構(gòu)逐漸演變?yōu)閺?fù)雜的樹狀結(jié)構(gòu),樹的每一層都代表一個業(yè)務(wù)維度,業(yè)務(wù)的發(fā)展讓系統(tǒng)在水平方向上呈現(xiàn)出多維度增長的特征。以廣告投放系統(tǒng)為例,最初的投放系統(tǒng)只有合約展示包段一種業(yè)務(wù)形態(tài),隨著程序化廣告和智能廣告的興起,廣告投放及播放形式層出不窮,業(yè)務(wù)樹中開始出現(xiàn)“產(chǎn)品線”的維度;而為了服務(wù)不同業(yè)務(wù)方,我們在系統(tǒng)中增加了“投放平臺”的維度;對不同投放標(biāo)的物的支持又在系統(tǒng)中引入了“計劃類型”的維度......就這樣廣告投放系統(tǒng)的業(yè)務(wù)架構(gòu)也逐漸演變成了如下圖所示的復(fù)雜樹狀結(jié)構(gòu)。

wKgaomc7BIKAbpi7AAVvle-4rwE522.png

多維度、多模塊、多場景的廣告投放業(yè)務(wù)

而在垂直方向上,早期的業(yè)務(wù)流程一般比較簡短,只有少數(shù)幾個業(yè)務(wù)環(huán)節(jié)。隨著業(yè)務(wù)的發(fā)展,系統(tǒng)功能越來越豐富,業(yè)務(wù)流程也變得愈發(fā)冗長,開始呈現(xiàn)出鮮明的模塊化特征。同樣以廣告投放系統(tǒng)為例,早期的廣告物料只有時段、預(yù)算、出價、創(chuàng)意幾個基礎(chǔ)模塊,隨著業(yè)務(wù)的發(fā)展陸續(xù)新增了智能出價、人群定向、地域定向、商品定向、智能創(chuàng)意、智能選品等業(yè)務(wù)模塊,物料創(chuàng)編流程也越來越冗長。除此之外,單個模塊內(nèi)部也開始出現(xiàn)多場景分化,如廣告投放系統(tǒng)中的智能出價模塊內(nèi)部就存在tCPA、tROI、eCPC、MC等不同的智能出價模型,其數(shù)據(jù)模型及業(yè)務(wù)規(guī)則也不盡相同,這進(jìn)一步增加了業(yè)務(wù)的復(fù)雜度。

本小節(jié)從業(yè)務(wù)架構(gòu)演進(jìn)歷程的視角分析了業(yè)務(wù)復(fù)雜度的來源,這構(gòu)成了系統(tǒng)的本質(zhì)復(fù)雜度。而對這些復(fù)雜業(yè)務(wù)規(guī)則的實現(xiàn)方案(好的、或者是壞的)就成了系統(tǒng)偶然復(fù)雜度的來源。

偶然復(fù)雜度

業(yè)務(wù)在多個維度上向著熵增的方向不斷發(fā)展,但是我們的代碼始終只有一套,不同維度的業(yè)務(wù)場景可能對同一個業(yè)務(wù)環(huán)節(jié)提出不同的個性化需求,造成不同維度的業(yè)務(wù)邏輯互相耦合,代碼中開始出現(xiàn)大量層層嵌套的if-else分支,圈復(fù)雜度不斷飆升,系統(tǒng)開始出現(xiàn)腐化跡象。此時一些工程師可能會意識到這個問題并開始著手優(yōu)化,但是由于缺少統(tǒng)一的邏輯封裝與拓展工具,再加上開發(fā)者的水平與技法也不盡相同,導(dǎo)致優(yōu)化方案五花八門,這種方案上的不一致反而進(jìn)一步增加了代碼的復(fù)雜度。除此之外,隨著系統(tǒng)集成的業(yè)務(wù)模塊越來越多,業(yè)務(wù)流程愈發(fā)冗長,與外部子系統(tǒng)的交互邏輯越來越復(fù)雜,開發(fā)者不得不去處理超時、重試、冪等、長事務(wù)、分布式事務(wù)及跨系統(tǒng)的數(shù)據(jù)一致性等問題,這些技術(shù)方案的引入對系統(tǒng)來說也是復(fù)雜度的來源。

對架構(gòu)設(shè)計的啟發(fā)

從上面的論述中可以看出,系統(tǒng)偶然復(fù)雜度的高低在很大程度上取決于開發(fā)者能否分析處理好業(yè)務(wù)的本質(zhì)復(fù)雜度,另外在多人協(xié)作開發(fā)場景中,軟件架構(gòu)的標(biāo)準(zhǔn)性和解決方案的一致性也是決定系統(tǒng)偶然復(fù)雜度的重要因素,這就是我們推出PICASO框架的根本原因。我們希望PICASO能夠引導(dǎo)開發(fā)者對復(fù)雜業(yè)務(wù)流程進(jìn)行模塊化拆解,采用分治思想逐一擊破,并通過標(biāo)準(zhǔn)的邏輯封裝規(guī)約與框架來實現(xiàn)多維度邏輯拓展,讓團(tuán)隊中每一位開發(fā)者都能夠以統(tǒng)一的思想寫出清晰、簡潔、有序、可檢索的代碼。

到這里相信有些讀者可能會產(chǎn)生一些疑問,既然軟件系統(tǒng)的偶然復(fù)雜度是技術(shù)方案本身的復(fù)雜度,那么引入PICASO框架是否也在增加系統(tǒng)的偶然復(fù)雜度呢?答案是肯定的,新框架的引入的確會增加系統(tǒng)的偶然復(fù)雜度。PICASO框架由于采用了全新的設(shè)計思想,在推行早期曾經(jīng)歷過痛苦的磨合期,也出現(xiàn)過不少由于開發(fā)者不理解新架構(gòu)的運(yùn)行機(jī)制而導(dǎo)致的設(shè)計缺陷或線上問題。但是任何架構(gòu)迭代之路都是螺旋上升的,新技術(shù)帶來的系統(tǒng)復(fù)雜度畢竟是靜態(tài)的,隨著開發(fā)人員對新架構(gòu)運(yùn)行機(jī)制及使用技巧的逐漸掌握,系統(tǒng)便開始趨于穩(wěn)定,新技術(shù)帶來的優(yōu)化收益也會逐漸顯現(xiàn)出來。但是如果我們不對現(xiàn)有的架構(gòu)做出升級,那么系統(tǒng)將隨著源源不斷的業(yè)務(wù)需求向著不可控熵增的方向不斷發(fā)展,由此帶來的系統(tǒng)復(fù)雜度將是動態(tài)且持續(xù)增加的。

PICASO的復(fù)雜度應(yīng)對之道

在分析完系統(tǒng)的復(fù)雜度來源之后,接下來我們將詳細(xì)介紹PICASO如何協(xié)助開發(fā)者對抗軟件系統(tǒng)的復(fù)雜度。IEEE對軟件架構(gòu)的定義為:架構(gòu)是由系統(tǒng)之間的組織、組件及組件之間的關(guān)系、以及對設(shè)計與演進(jìn)的指導(dǎo)原則組成的,其中前兩者是具體的實體框架,后者是指導(dǎo)思想。而軟件架構(gòu)的指導(dǎo)思想往往決定著前兩者的實現(xiàn),對指導(dǎo)思想的理解與掌握程度也直接決定了開發(fā)者能否在實際業(yè)務(wù)中用好架構(gòu)。以Spring框架為例,Spring的指導(dǎo)思想為:控制反轉(zhuǎn)(IoC)、依賴注入(DI)及面向切面編程(AOP),這三大核心思想一方面直接決定了Spring框架核心模塊的實現(xiàn),另一方面也是開發(fā)者要想用好Spring則必須掌握的內(nèi)容。而對PICASO來說,其指導(dǎo)思想可以概括為:能力拆分、拓展點抽象及能力編排。

wKgZomc7BIKAKmCGAAFlQ3Aps5Y369.png

軟件架構(gòu)的構(gòu)成

領(lǐng)域能力拆分與路由助力多模塊集成

神經(jīng)認(rèn)知學(xué)家喬治·米勒在他的論文《神奇的數(shù)字7》中指出人腦能夠同時處理的信息容量是有限的,人腦的短時記憶容量為7(7個數(shù)字、6個字母或5個單詞),后來的研究更是將這個數(shù)字降到了4個左右。所以當(dāng)冗長的業(yè)務(wù)流程疊加上多維度的個性化訴求,系統(tǒng)的業(yè)務(wù)復(fù)雜度將飆升為

,這顯然超出了我們大腦的瞬時處理容量,此時就需要利用

關(guān)注點分離

分類

分層

思想對復(fù)雜問題進(jìn)行求解。

分離

關(guān)注點分離(Separation of concerns,SOC)就是把復(fù)雜問題正交分解為多個互不相關(guān)的最小子問題,聚焦整體問題的局部復(fù)雜性,逐步進(jìn)行求解。我們在《復(fù)雜度的根源》章節(jié)中指出,復(fù)雜的業(yè)務(wù)系統(tǒng)往往會呈現(xiàn)出鮮明的模塊化特征,因此我們可以自然而然地根據(jù)業(yè)務(wù)模塊的功能邊界對冗長的業(yè)務(wù)流程進(jìn)行拆分,然后聚焦單個模塊進(jìn)行設(shè)計與抽象,避免陷入多模塊、多場景互相耦合的思維泥沼。PICASO框架為此引入了領(lǐng)域能力及領(lǐng)域服務(wù)的概念,其中領(lǐng)域能力用來承接單個業(yè)務(wù)模塊內(nèi)部的邏輯細(xì)節(jié),而領(lǐng)域服務(wù)則負(fù)責(zé)通過組合不同的領(lǐng)域能力實現(xiàn)一個完整的業(yè)務(wù)流程。如下圖所示,以單元新建流程為例,我們可以把單元新建流程劃分為:單元基礎(chǔ)信息構(gòu)造、優(yōu)化目標(biāo)設(shè)置、出價設(shè)置、人群設(shè)置、地域定向設(shè)置和商品定向設(shè)置多個子模塊,我們可以將這些模塊內(nèi)部邏輯封裝成領(lǐng)域能力,然后通過這些能力的組合構(gòu)建一個完整的單元信息領(lǐng)域服務(wù)。

chaijie_default.png

一個完整的業(yè)務(wù)流程可以拆分為多個原子業(yè)務(wù)模塊,每個原子業(yè)務(wù)模塊還可以按照其內(nèi)部的業(yè)務(wù)模式進(jìn)行進(jìn)一步細(xì)分

PICASO框架中的領(lǐng)域服務(wù)與DDD思想中的領(lǐng)域服務(wù)是同一個概念,其職責(zé)和定位都是承接無法在單個實體與值對象內(nèi)部直接實現(xiàn)的業(yè)務(wù)邏輯(事實上,B端系統(tǒng)對外提供的大部分服務(wù)都無法在單個聚合內(nèi)直接實現(xiàn))。而領(lǐng)域能力的概念則經(jīng)常出現(xiàn)在一些企業(yè)級中臺化框架中,如阿里的星環(huán)、京東的Matrix等。盡管當(dāng)年如火如荼的中臺化戰(zhàn)略如今已經(jīng)偃旗息鼓,但是我們還是將這個命名引入到了PICASO中,因為我們確實沒有找到一個比它更合適的命名,可以如此形象地描述一個足夠內(nèi)聚、自治且能夠被復(fù)用和拓展的原子實體。當(dāng)然PICASO中的領(lǐng)域能力與那些企業(yè)級中臺化框架中的領(lǐng)域能力相比要輕量和易用的多,不需要繁瑣的身份申請,也不存在跨工程熱加載的問題,畢竟中臺化的重心在管理域平臺及前中臺團(tuán)隊的協(xié)作上,而PICASO則始終聚焦在代碼本身的復(fù)雜度控制上。其實中臺化也好,組件化也罷,系統(tǒng)的復(fù)雜度就擺在那里,不管用什么由頭,要想提升團(tuán)隊整體的研發(fā)效能,它都是我們必須要去解決的一個問題。在本小節(jié)的論述中,領(lǐng)域能力似乎就是根據(jù)業(yè)務(wù)模塊的邊界簡單劃分出來的。但是在實際開發(fā)中的能力劃分要復(fù)雜的多,需要綜合考慮能力的應(yīng)用場景、會被哪些領(lǐng)域服務(wù)使用、以及能力之間的依賴關(guān)系等諸多因素進(jìn)行反復(fù)地推導(dǎo)和調(diào)整。本文對能力劃分方法論只是簡單地做了問題引入,更加具體的內(nèi)容我們將在《PICASO框架最佳實踐——能力識別與劃分》一文中進(jìn)行詳細(xì)介紹。

領(lǐng)域能力的拆解除了能夠降低業(yè)務(wù)流程分析的復(fù)雜度之外,也提高了代碼復(fù)用和拓展的靈活性。領(lǐng)域能力就像積木一樣,可以被組裝到不同的領(lǐng)域服務(wù)中,如人群設(shè)置能力可以同時被單元新建服務(wù)、單元編輯服務(wù)、人群快捷修改等領(lǐng)域服務(wù)復(fù)用。而能力拆解帶來的拓展靈活性性是相對于樸素模版設(shè)計模式而言的。在傳統(tǒng)架構(gòu)中模板類可能是我們使用最多的設(shè)計模式,它的確能夠簡單有效地實現(xiàn)復(fù)用共性流程、分離差異的目標(biāo)。但是由于復(fù)雜業(yè)務(wù)流程中不同業(yè)務(wù)節(jié)點的差異化維度往往是不同的,直接將業(yè)務(wù)主流程抽象成一個模板類,將各個節(jié)點作為模板中的抽象方法,那么該模板類子類的繼承關(guān)系復(fù)雜度將是各個業(yè)務(wù)節(jié)點內(nèi)部場景復(fù)雜度的叉乘。再加上傳統(tǒng)架構(gòu)并沒有積極引導(dǎo)開發(fā)者落實面向?qū)ο缶幊痰乃枷?,?dǎo)致我們基本上還在以面向過程的方式開發(fā)我們的系統(tǒng),通常會將同一個產(chǎn)品線中不同的業(yè)務(wù)方法實現(xiàn)到同一個Service或者M(jìn)anager類中,這將進(jìn)一步加重模板抽象及子類繼承關(guān)系的復(fù)雜度。而PICASO框架通過領(lǐng)域能力拆解將不同的業(yè)務(wù)環(huán)節(jié)拆分到了單獨的原子業(yè)務(wù)實體中,將模板中的抽象方法算子化。由于不同的原子業(yè)務(wù)模塊之間互相正交、互不干擾,因此能夠讓這些業(yè)務(wù)算子獨立迭代,在各自的業(yè)務(wù)維度上靈活地進(jìn)行繼承和拓展。

分類

只是把業(yè)務(wù)流程按照功能邊界拆分成不同的模塊通常是不夠的,因為單個模塊內(nèi)部往往還存在細(xì)分的業(yè)務(wù)模式,如上圖中的出價設(shè)置模塊,其內(nèi)部還存在手動、MC、tCPA、eCPC等不同的出價模型,這個時候就需要根據(jù)分類思想進(jìn)行進(jìn)一步拆解。分類思想是關(guān)注點分離思想進(jìn)一步的延伸,它在分離的同時還注重元素之間的共性特征。當(dāng)模塊內(nèi)部出現(xiàn)場景分化時,PICASO框架建議開發(fā)者對模塊進(jìn)行進(jìn)一步細(xì)分,將模塊內(nèi)不同場景的業(yè)務(wù)規(guī)則封裝為不同的能力實例。這些能力實例之間盡管存在邏輯差異,但是畢竟屬于同一個原子業(yè)務(wù)模塊,在數(shù)據(jù)模型、接口協(xié)議乃至業(yè)務(wù)流程上都存在很大的相似度。因此PICASO會將同模塊下不同業(yè)務(wù)場景對應(yīng)的領(lǐng)域能力實例聚合到一起,這樣的一組能力被稱為一個能力節(jié)點。同一個能力節(jié)點下的各個能力實例使用相同的接口參數(shù)及上下文定義,每個能力節(jié)點下會額外定義一個能力門面,能力門面通常不承載具體的業(yè)務(wù)規(guī)則,它僅負(fù)責(zé)定義當(dāng)前能力節(jié)點對外的接口協(xié)議以及從請求參數(shù)中提取業(yè)務(wù)場景標(biāo)識的邏輯,它是能力節(jié)點下所有能力實例對外提供服務(wù)的統(tǒng)一入口。上層的領(lǐng)域服務(wù)組合領(lǐng)域能力時,引用的不是具體的領(lǐng)域能力實例,而是各個能力節(jié)點下的能力門面。PICASO框架內(nèi)置的可執(zhí)行實體發(fā)現(xiàn)與路由機(jī)制會在應(yīng)用啟動時掃描出系統(tǒng)中所有的能力門面,并建立好能力門面與各個能力實例的路由表。當(dāng)請求到來時,領(lǐng)域服務(wù)不必關(guān)注本次請求應(yīng)該使用哪個具體的能力實例,而是直接調(diào)用能力門面的統(tǒng)一入口,PICASO框架會通過內(nèi)置的可執(zhí)行實體發(fā)現(xiàn)與路由機(jī)制提取請求中的場景標(biāo)識,然后將請求路由到對應(yīng)的領(lǐng)域能力實例上,從而實現(xiàn)模塊內(nèi)部的場景復(fù)雜度與領(lǐng)域服務(wù)模塊集成復(fù)雜度之間的解耦。以出價模塊為例,出價模塊內(nèi)部會根據(jù)不同的出價類型細(xì)分為tCPA、MC、eCPC等智能出價能力實例,但是單元新建領(lǐng)域服務(wù)并不會直接操作這些具體的能力實例,它引用是出價設(shè)置能力門面。當(dāng)請求到來時,PICASO框架會根據(jù)請求中的出價類型自動將請求路由到相應(yīng)的能力實例上。可執(zhí)行實體發(fā)現(xiàn)與路由機(jī)制是PICASO框架內(nèi)置的一個底層通用組件,是能力編排、拓展點機(jī)制等頂層功能的基礎(chǔ)。其本質(zhì)上就是一個增強(qiáng)型的門面+策略模式,我們通過一些實現(xiàn)技巧將其做成了一個可以適配任意可執(zhí)行實體的通用組件。如下圖所示,單元新建業(yè)務(wù)流程涉及標(biāo)的物設(shè)置、出價設(shè)置及人群設(shè)置等業(yè)務(wù)環(huán)節(jié),這些業(yè)務(wù)環(huán)節(jié)內(nèi)部都有各自的細(xì)分場景。在代碼實現(xiàn)中,這些業(yè)務(wù)環(huán)節(jié)被抽象為3個能力節(jié)點,節(jié)點內(nèi)部的細(xì)分場景被隔離到不同的能力實例中,在構(gòu)建領(lǐng)域服務(wù)時就不需要考慮當(dāng)前各個能力節(jié)點下的細(xì)分邏輯,只需要專注于業(yè)務(wù)流程本身,實現(xiàn)各個能力門面的組裝邏輯即可。

wKgaomc7BIWAOOvzAAEihCV9iHs039.png

能力門面與能力實例的抽象實現(xiàn)了能力編排復(fù)雜度的降維

分層

分層則是分類思想在領(lǐng)域服務(wù)、拓展點等其他實體粒度上的延伸。如快車、互動、推薦三條產(chǎn)品線的單元新建服務(wù)會被劃分到同一個服務(wù)分組下,對外暴露一個單元新建服務(wù)門面。這樣做目的是下層實體對上層實體暴露統(tǒng)一的門面接口,自下而上地逐層屏蔽下層實體的內(nèi)部復(fù)雜度,實現(xiàn)維度間復(fù)雜度解耦,進(jìn)而將代碼的整體復(fù)雜度由

降維到

。下圖展示了PICASO框架中各個業(yè)務(wù)實體的層級結(jié)構(gòu),最下層是各個領(lǐng)域能力實例執(zhí)行器,它們承載了具體的業(yè)務(wù)規(guī)則;相同功能子域的領(lǐng)域能力實例會對上層的領(lǐng)域服務(wù)實例暴露一個統(tǒng)一的領(lǐng)域能力門面執(zhí)行器,領(lǐng)域服務(wù)實例執(zhí)行器會通過組合領(lǐng)域能力門面定義具體的業(yè)務(wù)流程;而相同的功能子域的領(lǐng)域服務(wù)實例又會對領(lǐng)域服務(wù)統(tǒng)一入口(Domain Service Faced)暴露一個領(lǐng)域服務(wù)門面執(zhí)行器,屏蔽模塊內(nèi)領(lǐng)域服務(wù)實例之間的細(xì)分規(guī)則;領(lǐng)域服務(wù)統(tǒng)一入口將不同的領(lǐng)域服務(wù)門面集成到一起,對上層不同的流量來源暴露統(tǒng)一的請求入口。

這種分層結(jié)構(gòu)讓開發(fā)者逐層解構(gòu)業(yè)務(wù)復(fù)雜度的同時,實際上也構(gòu)造了一個索引結(jié)構(gòu),為實現(xiàn)可檢索的代碼架構(gòu)打下基礎(chǔ)。

wKgZomc7BIaAfGvkAALPFsd84Dc491.png

自下而上逐層屏蔽層級內(nèi)部的業(yè)務(wù)場景復(fù)雜度

在分層架構(gòu)中,除了可以通過通用可執(zhí)行實體路由機(jī)制自下而上地屏蔽下層實體的內(nèi)部場景復(fù)雜度之外,有時我們還要反過來自上而下地進(jìn)行復(fù)雜度合并。我們用一個例子來說明這種設(shè)計技巧:在廣告投放業(yè)務(wù)中有一個經(jīng)典的出價計算器模塊,它會根據(jù)廣告物料上的基礎(chǔ)出價、人群溢價、關(guān)鍵詞出價、流量包溢價、時段溢價等信息預(yù)估廣告物料最終的出價值范圍。計算邏輯只有一個,但是由于計算邏輯關(guān)聯(lián)了眾多底層模塊,物料新建、修改以及關(guān)聯(lián)模塊的快捷修改、還有對物料進(jìn)行修改過程中實時出價預(yù)估回顯(此時最新的修改并未落庫)等接口都會調(diào)用出價計算器模塊,但是這些使用場景對出價預(yù)估參數(shù)的填充程度是不同的,需要模塊針對不同的使用場景執(zhí)行不同的參數(shù)補(bǔ)充查詢邏輯。這是一個典型的上層模塊的調(diào)用場景復(fù)雜度滲透到底層模塊實現(xiàn)復(fù)雜度中的例子。在分層架構(gòu)中,越是底層的模塊在設(shè)計上需要考慮的場景應(yīng)該越少,而且要避免與上層模塊的使用場景耦合。因為上層模塊的使用場景是動態(tài)增加的,不知道什么時候就會有新的使用場景出現(xiàn),而底層模塊的真正需要處理的內(nèi)部場景應(yīng)該比頂層使用該模塊的場景要少且穩(wěn)定的多。所以解決這個問題的措施就是底層模塊面向自己內(nèi)部的業(yè)務(wù)模式在參數(shù)中定義一個隱式的標(biāo)識屬性,讓調(diào)用方根據(jù)自己的使用場景和業(yè)務(wù)訴求隱式地設(shè)置該參數(shù),底層模塊則直接根據(jù)參數(shù)中的這個標(biāo)識屬性執(zhí)行相應(yīng)的分支邏輯?;氐匠鰞r計算器的案例中,該模塊的使用場景有:單元新建后事件觸發(fā)、單元整體修改后事件觸發(fā)、單元新增關(guān)鍵詞后事件觸發(fā)、單元新建中臨時觸發(fā)、單元修改中臨時觸發(fā)、單元添加關(guān)鍵詞中臨時觸發(fā)等多種使用場景,未來也不確定會出現(xiàn)什么新的使用場景。但是對出價計算器模塊的內(nèi)部計算邏輯來說,其實只有需要補(bǔ)充查詢單元下關(guān)鍵詞信息和不補(bǔ)充查詢這兩種場景。為此我們在出價計算器能力參數(shù)中增加一個布爾類型的參數(shù),能力內(nèi)部直接根據(jù)該參數(shù)判斷是否需要執(zhí)行關(guān)鍵詞的查詢操作,出價計算器模塊的調(diào)用方則分別根據(jù)自己的使用場景判斷該如何設(shè)置這個參數(shù),從而起到自上而下的合并上層調(diào)用場景復(fù)雜度、保持底層模塊穩(wěn)定的作用。

有些讀者或許會覺得這種機(jī)制與上文介紹的能力路由機(jī)制是互相矛盾的,然而他們實際上并不沖突。因為對那些能夠自下而上屏蔽內(nèi)部場景復(fù)雜度的模塊而言,它們通常顯式地定義了內(nèi)部不同業(yè)務(wù)模式的標(biāo)識屬性,如出價模塊的出價類型、人群定向模塊的人群類型等,用戶在請求參數(shù)中也會顯式地設(shè)置請本次請求對應(yīng)的業(yè)務(wù)標(biāo)識,因此框架能夠直接對這些模塊應(yīng)用通用可執(zhí)行實體路由機(jī)制。但是實際業(yè)務(wù)中也存在一些模塊,它們內(nèi)部沒有定義明確的業(yè)務(wù)模式標(biāo)識,而是根據(jù)請求來源、調(diào)用場景等動態(tài)條件執(zhí)行不同的業(yè)務(wù)邏輯。此時我們可以先暫時忘掉這些模塊的調(diào)用場景,而是聚焦模塊內(nèi)部的業(yè)務(wù)分支提煉出隱藏其中的業(yè)務(wù)模式,然后讓上層模塊將動態(tài)調(diào)用場景轉(zhuǎn)化為底層模塊定義的隱式業(yè)務(wù)模式標(biāo)識參數(shù),接下來就能繼續(xù)應(yīng)用通用可執(zhí)行實體路由機(jī)制了。因此,分層思想中自下而上屏蔽的是模塊內(nèi)部的固有場景復(fù)雜度,而自上而下合并的則是模塊外部的使用場景復(fù)雜度,二者其實是互補(bǔ)的關(guān)系。

拓展點機(jī)制協(xié)助走出多維度泥潭

領(lǐng)域服務(wù)與領(lǐng)域能力的路由機(jī)制能夠較好的應(yīng)對系統(tǒng)多模塊集成帶來的復(fù)雜度,但是領(lǐng)域能力及領(lǐng)域服務(wù)必須嚴(yán)格遵守框架規(guī)約,繼承標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版(后續(xù)章節(jié)會有詳細(xì)講解),定義出明確的數(shù)據(jù)交換協(xié)議及上下文對象,這些都是相對較重的操作。因此能力或服務(wù)路由的維度必須抓住最核心的業(yè)務(wù)差異,而不是把所有存在業(yè)務(wù)差異的維度都納入到路由規(guī)則中,否則就會造成沙?;鸱?,反而增加系統(tǒng)的維護(hù)成本。因此我們還需要一種機(jī)制能夠以更加輕量的方式承載除了領(lǐng)域服務(wù)或能力路由維度之外其他業(yè)務(wù)維度上的細(xì)微差異,這就是拓展點機(jī)制要解決的問題。

拓展點機(jī)制是通用可執(zhí)行實體發(fā)現(xiàn)與路由機(jī)制在更細(xì)粒度上的延伸應(yīng)用,本質(zhì)上就是將存在差異化邏輯的環(huán)節(jié)抽象為一個接口從主流程中分離出去,然后將不同場景的差異化邏輯隔離在不同的拓展點接口實現(xiàn)中,這其實就是依賴倒轉(zhuǎn)原則(Dependence Inversion Principle, DIP)的應(yīng)用。與領(lǐng)域服務(wù)和領(lǐng)域能力相比,拓展點的定義和實現(xiàn)成本都要低很多,框架對拓展點接口內(nèi)的方法及方法參數(shù)都不會做過多的約束,定義一個拓展點僅需要繼承框架提供的標(biāo)準(zhǔn)接口并指定路由標(biāo)識的提取邏輯,而實現(xiàn)一個拓展點接口時也僅需要在實現(xiàn)拓展邏輯之外額外指定當(dāng)前拓展點實例能適配哪些路由標(biāo)識。拓展點可以嵌入到領(lǐng)域服務(wù)、領(lǐng)域能力以及資源庫(Repository,下文中會詳細(xì)闡述)中任何一處存在差異化邏輯的流程中。

拓展點機(jī)制作為能力與服務(wù)拆分路由機(jī)制的補(bǔ)充,支持任意維度上的差異化邏輯隔離。以下圖為例,在廣告投放系統(tǒng)中,底層的人群設(shè)置能力節(jié)點已經(jīng)按照其核心屬性人群類型進(jìn)行了能力實例的劃分。由于系統(tǒng)還賦能了多個投放平臺,不同的投放平臺對可綁定的人群上限有著不同的限制,此時就可以將各個能力實例中人群綁定數(shù)量校驗環(huán)節(jié)抽象為一個拓展點接口,以投放平臺類型作為路由KEY為各個投放平臺提供不同的接口實現(xiàn),從而自上而下地解決多維度拓展的難題。

這里說的“自上而下”是一種形象的描述,可以理解為父層級業(yè)務(wù)維度內(nèi)不同的業(yè)務(wù)場景在子層級模塊上產(chǎn)生的差異化邏輯。但實際上拓展點機(jī)制并不限制邏輯的維度拓展方向,如下圖的例子中,右側(cè)觸點新建服務(wù)領(lǐng)域服務(wù)實例所屬的服務(wù)門面定義的服務(wù)路由維度是產(chǎn)品線,但是不同的計劃類型的單元新建流程之間依然存在細(xì)微的邏輯差異,此時盡管計劃類型是產(chǎn)品線的子維度,但是依然可以通過拓展點來承載這些細(xì)微的邏輯差異。

wKgaomc7BIeASER0AAXRp-5dtmU414.png

拓展點機(jī)制的核心作用是作為能力及服務(wù)路由維度的補(bǔ)充,進(jìn)一步實現(xiàn)差異點的分離

能力編排框架確保架構(gòu)思想切實落地

前面幾個小節(jié)一直在論述如何對復(fù)雜邏輯進(jìn)行拆解和分離,但是系統(tǒng)要想對外提供可用的功能,就必須再次把這些分離出來的能力及拓展點組合起來,構(gòu)成一個完整的領(lǐng)域服務(wù)。最簡單的組合方式就是直接硬編碼依次調(diào)用各個能力門面的功能入口,手動實現(xiàn)前置方法調(diào)用結(jié)果與后置方法入?yún)⒌膶傩杂成浜娃D(zhuǎn)換,但是這種組合方式會在業(yè)務(wù)主流程中插入大量的膠水代碼,稀釋代碼的信息密度,將流程關(guān)鍵節(jié)點掩蓋在大量繁瑣無趣的`setter`、`gettter`方法調(diào)用中。為了解決這個問題,同時確保新架構(gòu)設(shè)計思想能夠精準(zhǔn)落地,讓規(guī)范和標(biāo)準(zhǔn)框架化,PICASO自建了能力編排框架,它為前文所述的各類思想落地提供了框架基礎(chǔ),將前文提到的各種實體、組件與設(shè)計思想有機(jī)結(jié)合到一起,自動實現(xiàn)模塊串聯(lián),讓開發(fā)者專注于業(yè)務(wù)邏輯本身,實現(xiàn)填空式開發(fā),最終構(gòu)建出一個完整的工程應(yīng)用。

目前業(yè)界有很多流程編排引擎,有老牌廠商的Netflix Conductor、AWS Step Function等,也有開源的Apache Activiti、Zeebe等。我們在早期架構(gòu)探索階段對這些解決方案也進(jìn)行了調(diào)研和試用,但是發(fā)現(xiàn)它們都無法滿足我們的訴求:以輕量級的方式實現(xiàn)模塊組合,提高模塊與組件的復(fù)用性,同時凸出呈現(xiàn)核心業(yè)務(wù)流程,輔助開發(fā)者快速抓住業(yè)務(wù)主線并建立對業(yè)務(wù)的全景認(rèn)知。這很大程度上是由于上述開源組件的定位大都是接口級別的服務(wù)編排或者是審批流之類的流程引擎,因此其實現(xiàn)方案或執(zhí)行成本往往較重,很多流程編排框架過分強(qiáng)調(diào)通過UI框架拖拽式實構(gòu)建業(yè)務(wù)流程,導(dǎo)致開發(fā)者需要先在代碼工程中實現(xiàn)業(yè)務(wù)組件,再到UI界面中構(gòu)建串聯(lián)流程,適用的場景有限且造成強(qiáng)烈的割裂感不說,開發(fā)者依然需要手動配置組件之間的參數(shù)映射與數(shù)據(jù)傳遞邏輯,而脫離了開發(fā)工具的代碼提示與補(bǔ)全功能,這些邏輯的實現(xiàn)成本反而增大了。與這些問題相比,拖拽式的UI界面雖然炫酷,但并不是我們的核心訴求。還有一些編排框架采用了中心化的部署方式,流程串聯(lián)與組件服務(wù)分離部部署,通過RPC實現(xiàn)組件調(diào)用,這種方式會付出巨大的網(wǎng)絡(luò)開銷及中間結(jié)果存儲成本。這種設(shè)計讓它們在批處理任務(wù)場景中有較好的應(yīng)用,但是在交互式服務(wù)應(yīng)用場景中則會造成嚴(yán)重的性能問題并付出巨大的運(yùn)行成本。因此,在經(jīng)過一次次嘗試之后我們最終決定舉起自研大旗,開發(fā)一套與PICASO架構(gòu)基本思想相適配的能力編排框架。

wKgaoWc7BImAMJ3pAA1anm7UCsw016.png

要想滿足我們在上文中提出的能力編排相關(guān)的訴求,能力編排框架需要提供兩個基本功能:分別是在編碼階段通過簡潔、直觀、易用的API輔助開發(fā)者定義業(yè)務(wù)流程,以及在請求處理階段根據(jù)開發(fā)者制定的執(zhí)行圖串聯(lián)各個業(yè)務(wù)組件完成請求處理流程。為了實現(xiàn)這兩個基本功能,PICASO框架采取了制定標(biāo)準(zhǔn)化業(yè)務(wù)執(zhí)行模版、內(nèi)嵌標(biāo)準(zhǔn)上下文機(jī)制以及自建能力編排框架三項舉措。

標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版

標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版立足于軟件系統(tǒng)的內(nèi)在本質(zhì)定義了適用任何業(yè)務(wù)場景的基本處理流程,就像Object對象在JDK中的作用一樣,標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版并不復(fù)雜,但它卻是PICASO框架中所有組件功能得以實現(xiàn)的基礎(chǔ)。從本質(zhì)上看,所有的軟件系統(tǒng)都在做三件事:數(shù)據(jù)的獲取、處理與存儲(或傳輸);從業(yè)務(wù)視角看,數(shù)據(jù)的處理又可細(xì)分為輸入數(shù)據(jù)的合法性校驗以及數(shù)據(jù)的計算與轉(zhuǎn)換,而數(shù)據(jù)的合法性校驗又可細(xì)分為對輸入數(shù)據(jù)直接進(jìn)行的校驗以及需要結(jié)合系統(tǒng)內(nèi)外部詳情數(shù)據(jù)進(jìn)行的校驗?;谏鲜稣撌觯琍ICASO框架定義的業(yè)務(wù)處理的基本流程為:

1.參數(shù)預(yù)校驗:直接對請求入?yún)⑦M(jìn)行的校驗,這些校驗邏輯通常都是簡單的內(nèi)存計算,不依賴任何外部數(shù)據(jù),如參數(shù)完整性校驗、參數(shù)值范圍校驗、數(shù)據(jù)長度校驗等。

2.上下文初始化:基于校驗后的入?yún)⒉樵償?shù)據(jù)詳情并填充到上下文中,如根據(jù)入?yún)⒅械膯卧狪D查詢單元詳情、根據(jù)userId獲取賬戶詳情等,這些數(shù)據(jù)將會在后續(xù)流程中使用。

3.基于上下文的業(yè)務(wù)校驗:執(zhí)行需要結(jié)合上下文中詳情數(shù)據(jù)才能進(jìn)行的業(yè)務(wù)校驗,如根據(jù)單元狀態(tài)判斷是否可以執(zhí)行物料的修改操作、判斷標(biāo)的物類型與物料計劃類型是否匹配等。

4.業(yè)務(wù)邏輯處理:基于參數(shù)及上下文中的詳情數(shù)據(jù)執(zhí)行領(lǐng)域模型(下一章節(jié)介紹)的構(gòu)造和修改,注意對于一些查詢類的服務(wù),這個步驟可能不是必須的。

5.數(shù)據(jù)持久化:將新建或修改后的領(lǐng)域模型保存到數(shù)據(jù)庫中或者調(diào)用外部服務(wù)API完成數(shù)據(jù)傳遞,這同樣是一個可選的標(biāo)準(zhǔn)步驟,另外有些服務(wù)在業(yè)務(wù)邏輯處理環(huán)節(jié)就已經(jīng)完成了數(shù)據(jù)傳遞。

6.發(fā)布領(lǐng)域事件:一些修改類的領(lǐng)域服務(wù)在完成請求處理之后可能需要通知其他領(lǐng)域內(nèi)的業(yè)務(wù)實體做一些相關(guān)的后置操作,PICASO框架是通過領(lǐng)域事件機(jī)制來實現(xiàn)這個功能的(后續(xù)章節(jié)中進(jìn)行詳細(xì)介紹)。

7.構(gòu)造處理結(jié)果:業(yè)務(wù)流程執(zhí)行完成后構(gòu)建返回給調(diào)用方的響應(yīng)數(shù)據(jù)。

標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版本質(zhì)上就是一個Executor模板類,上述基本業(yè)務(wù)流程也就是該模板類中主要的模板方法。在PICASO框架中,領(lǐng)域服務(wù)和領(lǐng)域能力都要繼承標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模板類,這樣做的目的是引導(dǎo)和約束開發(fā)者對領(lǐng)域服務(wù)和領(lǐng)域能力的具體實現(xiàn)邏輯按照標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行流程進(jìn)行二次拆分,從而可以讓框架對代碼進(jìn)行精細(xì)化地調(diào)用控制。標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行模版是對所有業(yè)務(wù)處理流程進(jìn)行的最頂層抽象,模版類中各個標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行步驟API的制定讓把不同業(yè)務(wù)模塊的串聯(lián)執(zhí)行職責(zé)從開發(fā)者手中轉(zhuǎn)義到框架手中成為可能,開發(fā)者不必手動實現(xiàn)不同模塊和方法的串聯(lián)調(diào)用,而是專注于業(yè)務(wù)邏輯,實現(xiàn)填空式開發(fā),從而減少系統(tǒng)中的膠水代碼,提高信息密度,這本質(zhì)上就是依賴倒轉(zhuǎn)原則(Dependency Inversion Principle, DI)的應(yīng)用。下面的代碼片段給出了標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版的定義,出于突出呈現(xiàn)PICASO框架設(shè)計思想的目的,示例代碼去除了框架功能的具體實現(xiàn)邏輯,僅保留了核心要素及模版方法的定義。

/** * 標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模板基類,定義了基本的業(yè)務(wù)處理流程,所有領(lǐng)域服務(wù)和領(lǐng)域能力執(zhí)行器都必須繼承該類。 * * @param 業(yè)務(wù)執(zhí)行器對應(yīng)的參數(shù)類型,所有的執(zhí)行器參數(shù)都應(yīng)該繼承自標(biāo)準(zhǔn)參數(shù)基類Command對象 * @param 業(yè)務(wù)執(zhí)行器最終返回的執(zhí)行結(jié)果類型 * @param 業(yè)務(wù)執(zhí)行器使用的上下文對象類型,所有執(zhí)行器的上下文對象都應(yīng)該繼承標(biāo)準(zhǔn)上下文基類, * 請求的入?yún)⒑彤a(chǎn)生的中間結(jié)果都會保存在上下文對象中 */ public abstract class CommandExecutor > { /** * 參數(shù)預(yù)校驗,該步驟應(yīng)該只進(jìn)行純內(nèi)存計算操作 * @param context 上下文,此時的上下文中只有參數(shù)對象 */ protected Response doPreValidate(CTX context) { return Response.success(); } /** * 執(zhí)行上下文初始化,根據(jù)參數(shù)執(zhí)底層情數(shù)據(jù)的拓展查詢,并將查詢結(jié)果填充到context對象中 * @param context 上下文,調(diào)用該方法時的上下文中只有參數(shù)對象,調(diào)用完成后上下文將被填充 */ protected Response doInitContext(CTX context) { return Response.success(); } /** * 結(jié)合上下文中的底層數(shù)據(jù)執(zhí)行業(yè)務(wù)校驗 * @param context 上下文,此時的上下文中已經(jīng)完成了依賴的業(yè)務(wù)詳情數(shù)據(jù)的填充 */ protected Response doContextualValidate(CTX context) { return Response.success(); } /** * 結(jié)合上下文中的底層數(shù)據(jù)執(zhí)行業(yè)務(wù)邏輯的處理,對已有實體的變更及生成的新業(yè)務(wù)實體都會填充回上下文對象中 * @param context 上下文,業(yè)務(wù)邏輯執(zhí)行過程中的中間結(jié)果也可以暫存到到該上下文中 */ protected Response doProcessBizLogic(CTX context) { return Response.success(); } /** * 保存業(yè)務(wù)流程執(zhí)行過程中新建或者被修改過的業(yè)務(wù)實體,調(diào)用該方法時,這些數(shù)據(jù)已經(jīng)被寫入到了上下文對象中 * @param context 上下文 */ public Response doPersistAggregates(CTX context) { return Response.success(); } /** * 構(gòu)造本次業(yè)務(wù)請求流程中需要對外發(fā)布的領(lǐng)域事件 * @param context 上下文 */ protected Response> doPublishAppEvent(CTX context) { return Response.success(); } /** * 構(gòu)造請求的返回值 * @param context 上下文 */ protected Response doAssembleResponse(CTX context) { return Response.success(); } }

到這里有些讀者可能還沒有意識到“把不同業(yè)務(wù)模塊的串聯(lián)調(diào)用職責(zé)從開發(fā)者手中轉(zhuǎn)移到框架手中”的價值,這項措施其實并沒有直接解決我們在本文第二章提出任何一個痛點問題,要想理解這一措施我們必須用辯證法重新審視前文介紹的各項復(fù)雜度應(yīng)對措施。根據(jù)前面幾個小節(jié)的論述,我們?yōu)榱藨?yīng)對業(yè)務(wù)復(fù)雜度而對請求處理流程進(jìn)行了各種粒度和場景的拆分,拆分出來的各類實體再疊加上實體內(nèi)部標(biāo)準(zhǔn)執(zhí)行步驟的二次拆解,必然會增加后續(xù)邏輯串聯(lián)和組裝的復(fù)雜度。如果此時還要求開發(fā)者手動硬編碼實現(xiàn)邏輯組裝,那么勢必會帶來極高的開發(fā)負(fù)擔(dān)和出錯概率,而且硬編碼組裝帶來的大量膠水代碼還會稀釋和掩蓋代碼中的關(guān)鍵信息,后續(xù)再進(jìn)行迭代時就容易產(chǎn)生改動點遺漏和影響評估不全等問題。PICASO框架解決這些問題的措施就是由框架代替開發(fā)者實現(xiàn)合個模塊的串聯(lián)組裝,這也我們要定義標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版的根本原因:統(tǒng)一標(biāo)準(zhǔn)的調(diào)用入口是讓框架實現(xiàn)流程串聯(lián)的前提,進(jìn)而才可能實現(xiàn)將膠水代碼隱藏在框架內(nèi)部、提高業(yè)務(wù)層代碼信息密度、降低開發(fā)者編碼負(fù)擔(dān)的設(shè)計目標(biāo)。PICASO框架內(nèi)模塊串聯(lián)的詳細(xì)邏輯我們將在本章后三個小節(jié)中進(jìn)行闡述,在那之前我們先繼續(xù)介紹標(biāo)準(zhǔn)業(yè)務(wù)模版中的其他核心要素。

上下文機(jī)制

從標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行模版的示例代碼中我們可以看到,除了各個標(biāo)準(zhǔn)步驟的方法聲明之外,標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行模版還通過泛型變量定義了執(zhí)行器接收的請求參數(shù)類型、返回值類型以及上下文對象類型,其中“上下文”是標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版中的核心要素,業(yè)務(wù)數(shù)據(jù)就是通過它在各個標(biāo)準(zhǔn)步驟之間流轉(zhuǎn)的。所謂的上下文本質(zhì)上就是一個POJO(Plain Old Java Object),其內(nèi)部定義了業(yè)務(wù)流程執(zhí)行所需要的各種詳情數(shù)據(jù)。在《改進(jìn)我們的架構(gòu)》一文中我們已經(jīng)對上下文機(jī)制進(jìn)行了詳細(xì)的闡述,在這里我們簡單回顧一下它的作用。如下圖所示,在傳統(tǒng)架構(gòu)中開發(fā)者往往直接面向數(shù)據(jù)庫編程,業(yè)務(wù)邏輯與數(shù)據(jù)庫操作互相交織,容易造成重復(fù)或碎片化的數(shù)據(jù)庫讀寫操作。而采用上下文機(jī)制之后,業(yè)務(wù)流程中的各個子模塊都不再封裝數(shù)據(jù)的讀寫操作,而是在請求一開始先將后續(xù)流程所需要的數(shù)據(jù)集中初始化到上下文對象中,后續(xù)各個業(yè)務(wù)模塊統(tǒng)一從上下文中獲取所需的詳情數(shù)據(jù),并把產(chǎn)生的中間結(jié)果寫入到上下文對象中,最后在所有子模塊業(yè)務(wù)邏輯執(zhí)行完成之后,集中將上下文中新增或發(fā)生變化的業(yè)務(wù)實體持久化到存儲介質(zhì)中。這種設(shè)計一方面能夠避免子模塊劃分導(dǎo)致的重復(fù)及碎片化的數(shù)據(jù)讀寫操作,另一方面,集中的數(shù)據(jù)操作可以啟發(fā)開發(fā)者采用批量、異步和并行等措施進(jìn)行極致地性能優(yōu)化。

wKgZoWc7BIyATkcrAAQsIVuLcfI607.png

上下文機(jī)制與傳統(tǒng)架構(gòu)業(yè)務(wù)處理流程對比

PICASO框架對上下文機(jī)制做了進(jìn)一步升級和深度集成,上下文作為核心要素被直接定義到標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版中,領(lǐng)域服務(wù)和領(lǐng)域能力執(zhí)行器都要通過泛型參數(shù)來聲明自己所需的上下文對象類型。需要說明的是,盡管領(lǐng)域能力執(zhí)行器中也定義了“上下文初始化”標(biāo)準(zhǔn)步驟,但是PICASO框架依然建議開發(fā)者盡量在領(lǐng)域服務(wù)執(zhí)行器的上下文初始化步驟中就將各個領(lǐng)域能力所依賴的業(yè)務(wù)實體或外部數(shù)據(jù)集中批量查詢好,然后填充到領(lǐng)域服務(wù)上下文中。后續(xù)各個領(lǐng)域能力會優(yōu)先從領(lǐng)域服務(wù)上下文中獲取所需的詳情數(shù)據(jù),領(lǐng)域能力的上下文初始化步驟僅做依賴數(shù)據(jù)的非空校驗或者做為從領(lǐng)域服務(wù)上下文中獲取不到所需數(shù)據(jù)時的托底補(bǔ)充查詢措施,這是由于領(lǐng)域服務(wù)作為整個業(yè)務(wù)流程的全局把控者,擁有最全的數(shù)據(jù)視角,可以對代價昂貴的IO操作進(jìn)行極致地調(diào)優(yōu),而領(lǐng)域能力則聚焦于業(yè)務(wù)流程的局部細(xì)節(jié),在能力內(nèi)部封裝的數(shù)據(jù)讀寫操作很容易隨著能力的組合或循環(huán)復(fù)用而被碎片化或重復(fù)地執(zhí)行。

需要注意的是上文中“領(lǐng)域能力優(yōu)先使用領(lǐng)域服務(wù)上下文中的數(shù)據(jù)”并不意味著領(lǐng)域能力會直接訪問外層領(lǐng)域服務(wù)的上下文對象,這是由于同一個領(lǐng)域能力可能會被不同的領(lǐng)域服務(wù)所復(fù)用,因此領(lǐng)域能力不可能與其中任何一個領(lǐng)域服務(wù)的上下文耦合到一起。為了解決這個問題,PICASO框架要求每個領(lǐng)域能力都要定義自己專有的上下文對象。在調(diào)用領(lǐng)域能力之前先將領(lǐng)域服務(wù)上下文中的數(shù)據(jù)傳遞到領(lǐng)域能力的上下文中,領(lǐng)域能力中的業(yè)務(wù)邏輯直接訪問的依然是領(lǐng)域能力自己的上下文對象,在能力執(zhí)行過程中構(gòu)建的新實體或者對已有實體的修改也會直接保存到領(lǐng)域能力上下文中。而在完成能力調(diào)用之后,PICASO會將領(lǐng)域能力上下文中新生成或者發(fā)生變更的屬性傳遞回領(lǐng)域服務(wù)上下文中,從而在保持領(lǐng)域能力與領(lǐng)域服務(wù)解耦的前提下實現(xiàn)領(lǐng)域服務(wù)與領(lǐng)域能力上下文數(shù)據(jù)的共享。因此在PICASO框架中上下文機(jī)制除了起到避免碎片化及重復(fù)讀寫數(shù)據(jù)的作用之外,還負(fù)責(zé)在不同領(lǐng)域能力以及領(lǐng)域服務(wù)與領(lǐng)域能力之間進(jìn)行數(shù)據(jù)的傳遞和共享。

我們可以用一個例子來詳細(xì)描述上述機(jī)制,如下圖所示,領(lǐng)域服務(wù)內(nèi)編排了三個領(lǐng)域能力:A、B、C,其中能力A和C分別依賴業(yè)務(wù)實體1和實體4,能力B依賴能力A生成的數(shù)據(jù)實體2,完成業(yè)務(wù)邏輯處理后框架需要把能力B和C構(gòu)建的業(yè)務(wù)實體3和5以及能力C對實體4的修改保存到數(shù)據(jù)庫中。當(dāng)請求到來時PICASO框架會首先調(diào)用領(lǐng)域服務(wù)的上下文初始化標(biāo)準(zhǔn)步驟(initContext)完成實體1與實體4的查詢,在調(diào)用能力A之前會將實體1從領(lǐng)域服務(wù)上下文拷貝到能力A上下文中,完成能力A的調(diào)用后會將其構(gòu)建的實體2從能力A的上下文中拷貝回領(lǐng)域服務(wù)上下文,然后將領(lǐng)域服務(wù)的上下文作為兩個能力之間數(shù)據(jù)共享和交換的通道,在調(diào)用能力B之前將實體2拷貝到能力B的上下文中......以此類推用相同的方式完成能力B和能力C的調(diào)用,最后PICASO框架會調(diào)用領(lǐng)域服務(wù)的聚合根持久化標(biāo)準(zhǔn)步驟(persistAggregate),集中將新生成的實體3和5以及由能力C修改后的實體4持久化到數(shù)據(jù)庫中。

wKgaoWc7BIyAWh1qAAGOJeSo7fQ622.png

領(lǐng)域服務(wù)與領(lǐng)域能力上下文之間的數(shù)據(jù)傳遞關(guān)系

上下文機(jī)制作為PICASO框架的基礎(chǔ)組件,其內(nèi)部除了用戶自定義的業(yè)務(wù)屬性之外還承載著大量框架內(nèi)部運(yùn)行所需的狀態(tài)數(shù)據(jù)以及大量為開發(fā)者提供的工具API。在本文中我們僅對其基本運(yùn)行機(jī)制進(jìn)行了介紹,關(guān)于上下文的使用技巧及其基類中各種工具API的使用方法,我們將在《PICASO框架最佳實踐——上下文機(jī)制》一文中進(jìn)行詳細(xì)的闡述。

標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎

在上一小節(jié)的最后我們通過一個架空的例子論述了PICASO框架內(nèi)部的數(shù)據(jù)傳遞流程,這些數(shù)據(jù)傳遞規(guī)則并不需要開發(fā)者手動實現(xiàn),而是通過PICASO框架內(nèi)置的標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎自動觸發(fā)的。接下來我們就將詳細(xì)闡述標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎是如何將領(lǐng)域服務(wù)及領(lǐng)域能力各個標(biāo)準(zhǔn)步驟串聯(lián)到一起的。但是在此之前,我們有必要必再次明確領(lǐng)域服務(wù)和領(lǐng)域能力執(zhí)行器的職責(zé),這對理解標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎的設(shè)計動機(jī)十分重要。

在PICASO框架中,系統(tǒng)對外提供的服務(wù)都是由領(lǐng)域服務(wù)執(zhí)行器承載的,作為整個業(yè)務(wù)流程的全局把控者,領(lǐng)域服務(wù)執(zhí)行器的基本職責(zé)就是定義業(yè)務(wù)流程(編排組裝領(lǐng)域能力)以及管理業(yè)務(wù)數(shù)據(jù)(上下文的初始化及持久化),而領(lǐng)域能力執(zhí)行器則聚焦在完整業(yè)務(wù)流程中的某個特定模塊,負(fù)責(zé)實現(xiàn)該模塊內(nèi)部具體的業(yè)務(wù)規(guī)則。如前文所述,領(lǐng)域服務(wù)是通過領(lǐng)域能力組合編排而成的,并且它們都繼承了標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版,因此不難推導(dǎo)出領(lǐng)域服步驟其實就是通過各個領(lǐng)域能力的相應(yīng)標(biāo)準(zhǔn)步驟組合而成的。但是這并不意味著業(yè)務(wù)流程中所有的業(yè)務(wù)邏輯都會下沉到領(lǐng)域能力中,比如領(lǐng)域服務(wù)上下文初始化操作就必須在領(lǐng)域服務(wù)執(zhí)行器中直接定義。此外,考慮到領(lǐng)域能力聚焦于局部業(yè)務(wù)細(xì)節(jié),無法獨立對外提供服務(wù),為了明確組件職責(zé),避免給開發(fā)者帶來困惑,領(lǐng)域能力執(zhí)行器對標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器模版進(jìn)行了二次拓展,隱藏了完整請求流程處理維度才需要的聚合根持久化(persisteAggregates)、構(gòu)建并發(fā)布領(lǐng)域事件(publishAppEvent)以及組裝請求響應(yīng)數(shù)據(jù)(assembleResponse)標(biāo)準(zhǔn)步驟,因此這三個模版方法對應(yīng)的業(yè)務(wù)邏輯也需要直接在領(lǐng)域服務(wù)執(zhí)行器中定義。

領(lǐng)域服務(wù)及領(lǐng)域能力執(zhí)行器的職責(zé)劃分決定了二者之間的數(shù)據(jù)傳遞時機(jī)及其標(biāo)準(zhǔn)步驟之間的組合關(guān)系,標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎的模塊串聯(lián)規(guī)則就是基于此制定的。其核心設(shè)計就是對業(yè)務(wù)流程中各個領(lǐng)域能力標(biāo)準(zhǔn)步驟的重組執(zhí)行。在PICASO框架的早期摸索階段,我們曾傾向于將同一個業(yè)務(wù)模塊的參數(shù)校驗與業(yè)務(wù)處理邏輯劃分到兩個不同的領(lǐng)域能力中。這種能力劃分方式固然也能實現(xiàn)業(yè)務(wù)功能,甚至也能起到復(fù)雜度分離的作用,但是這種劃分方式會造成業(yè)務(wù)邏輯的沙?;纸?,產(chǎn)生大量瑣碎的小能力,這反而會增加系統(tǒng)的開發(fā)及維護(hù)成本。另外,由于同一個模塊的參數(shù)校驗及業(yè)務(wù)處理邏輯往往會依賴相同的底層數(shù)據(jù),沙礫化的能力劃分會急劇增加能力間數(shù)據(jù)傳遞和共享的負(fù)擔(dān),稍有不慎就會造成數(shù)據(jù)的碎片化讀寫,進(jìn)而對系統(tǒng)性能產(chǎn)生影響。因此我們最終選擇回歸業(yè)務(wù)本質(zhì),以最小原子業(yè)務(wù)邊界作為能力劃分的準(zhǔn)則,將同一模塊內(nèi)關(guān)聯(lián)緊密的參數(shù)校驗、上下文初始化、上下文校驗及業(yè)務(wù)處理邏輯封裝到同一個領(lǐng)域能力執(zhí)行器中。但是這種封裝規(guī)則卻帶來了新的問題:領(lǐng)域服務(wù)由領(lǐng)域能力組合而成,如果我們直接依次串行調(diào)用每個領(lǐng)域能力內(nèi)的各個標(biāo)準(zhǔn)步驟,將無法實現(xiàn)領(lǐng)域能力與領(lǐng)域服務(wù)標(biāo)準(zhǔn)步驟之間的協(xié)調(diào)執(zhí)行,另外由于調(diào)用后置能力時前置能力所有標(biāo)準(zhǔn)步驟都已執(zhí)行完畢,如果后置能力的參數(shù)校驗失敗而前置能力在業(yè)務(wù)邏輯處理步驟已經(jīng)與外部系統(tǒng)產(chǎn)生了數(shù)據(jù)交互,此時就會產(chǎn)生臟數(shù)據(jù)等問題。然而標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行模版的抽象則為我們帶來了該問題的解決方案:將領(lǐng)域能力執(zhí)行器中的各個標(biāo)準(zhǔn)步驟拆散到領(lǐng)域服務(wù)執(zhí)行器相應(yīng)的標(biāo)準(zhǔn)步驟中重組執(zhí)行,而不是依次觸發(fā)每個領(lǐng)域能力的全部標(biāo)準(zhǔn)步驟,如下圖所示,在領(lǐng)域服務(wù)的參數(shù)預(yù)校驗標(biāo)準(zhǔn)步驟中會按照能力執(zhí)行圖依次觸發(fā)各個領(lǐng)域能力的參數(shù)預(yù)校驗步驟,而在領(lǐng)域服務(wù)的上下文初始化步驟中則會依次觸發(fā)各個領(lǐng)域能力執(zhí)行器中的上下文初始化步驟。這種重組執(zhí)行機(jī)制確保了服務(wù)請求流程能夠整體按照參數(shù)預(yù)校驗、上下文初始化、上下文校驗、業(yè)務(wù)邏輯處理、聚合根持久化、發(fā)布領(lǐng)域事件、構(gòu)造返回值的標(biāo)準(zhǔn)流程執(zhí)行下去,實現(xiàn)fail-fast特性,避免由于后置操作校驗失敗而前置操作已執(zhí)行導(dǎo)致的IO資源浪費(fèi)及臟數(shù)據(jù)問題,另外這種運(yùn)行機(jī)制帶來的額外收益是讓我們能夠利用現(xiàn)有服務(wù)快速實現(xiàn)請求預(yù)校驗接口,這一點我們在本文第四章的PICASO框架開發(fā)流程示例中將有專門的呈現(xiàn)。

上述過程也是辯證法的生動詮釋,事物之間存在普遍聯(lián)系,在對立統(tǒng)一中不斷發(fā)展。PICASO框架中也是在一次次提出方案、引發(fā)新問題、解決新問題的過程逐漸成型的,框架中各個組件互相支撐,互為因果,共同實現(xiàn)整潔架構(gòu)的最終目標(biāo)。我們也希望各位讀者在閱讀本文時能夠始終將本文介紹的各項組件聯(lián)系到一起來理解框架的指導(dǎo)思想和設(shè)計動機(jī),這對未來我們能否在實際業(yè)務(wù)用好PICASO框架來說十分重要。

wKgZoWc7BI6AT9T9AAffngV0LKc232.png

標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎的重組執(zhí)行流程

下面我們將結(jié)合著上圖所示的能力標(biāo)準(zhǔn)步驟重組執(zhí)行流程圖逐步解析標(biāo)準(zhǔn)業(yè)務(wù)流程模版執(zhí)行引擎的運(yùn)行機(jī)制:

1. 參數(shù)預(yù)校驗

當(dāng)請求到來時,PICASO框架會首先通過領(lǐng)域服務(wù)門面定位到具體的領(lǐng)域服務(wù)執(zhí)行器實例,然后調(diào)用其參數(shù)預(yù)校驗標(biāo)準(zhǔn)步驟(preValidate),在該方法中會首先執(zhí)行領(lǐng)域服務(wù)執(zhí)行器直接定義的參數(shù)預(yù)校驗邏輯(當(dāng)然也可以根據(jù)開發(fā)者的設(shè)計意圖調(diào)整為先觸發(fā)各個領(lǐng)域能力的參數(shù)預(yù)校驗邏輯),然后再觸發(fā)領(lǐng)域能力執(zhí)行圖中各個領(lǐng)域能力的參數(shù)預(yù)校驗邏輯,需要注意的是由于領(lǐng)域能力執(zhí)行器有自己專屬的參數(shù)及上下文對象,因此在調(diào)用各個能力參數(shù)預(yù)校驗方法之前,PICASO會自動將領(lǐng)域服務(wù)入?yún)ο笾械膶傩钥截惖筋I(lǐng)域能力入?yún)ο笾型愋偷膶傩陨希ㄅcSpring框架中BeanUtils.copyProperties的邏輯相同)。

2. 上下文初始化

完成參數(shù)預(yù)校驗邏輯之后,PICASO會開始執(zhí)行領(lǐng)域服務(wù)的上下文初始化邏輯。我們鼓勵開發(fā)者將各個領(lǐng)域能力所依賴的底層數(shù)據(jù)集中到領(lǐng)域服務(wù)的上下文初始化邏輯中批量查詢好,因為領(lǐng)域服務(wù)作為整個業(yè)務(wù)流程的全局把控者,擁有最全面的數(shù)據(jù)視角,可以進(jìn)行最徹底的性能優(yōu)化。完成領(lǐng)域服務(wù)直接定義的上下文初始化邏輯之后,PICASO將調(diào)用能力執(zhí)行圖中各個領(lǐng)域能力的上下文初始化步驟,但是在此之前,與領(lǐng)域服務(wù)與領(lǐng)域能力之間的參數(shù)傳遞邏輯類似,PICASO框架會先將領(lǐng)域服務(wù)上下文對象中的屬性拷貝到領(lǐng)域能力上下文對象中同名同類型的屬性上。

有些讀者可能會對本小節(jié)的論述有些疑惑,封裝領(lǐng)域能力的目的之一是為了邏輯復(fù)用,然而我們卻要將其依賴數(shù)據(jù)的初始化邏輯代理到領(lǐng)域服務(wù)中,那么當(dāng)一個領(lǐng)域能力被不同的領(lǐng)域服務(wù)引用時,是否會造成重復(fù)編碼呢?這個問題的答案是肯定的,但是絕大多數(shù)場景下領(lǐng)域能力依賴的底層實體通常不多,一方面我們可以通過接下來將要介紹的“聚合與資源庫”機(jī)制簡化這些底層實體的查詢邏輯,與由此收獲的性能提升收益相比,重復(fù)編碼所付出的輕微代價是完全值得的。另一方面我們其實并不建議在領(lǐng)域能力內(nèi)部實現(xiàn)依賴數(shù)據(jù)的初始化查詢操作,因為能夠被編排到同一個領(lǐng)域服務(wù)中的領(lǐng)域能力通常都會依賴相同的業(yè)務(wù)實體,如果要在每一個領(lǐng)域能力內(nèi)都實現(xiàn)一遍實體查詢邏輯同樣會造成重復(fù)編碼。因此我們建議還是統(tǒng)一由領(lǐng)域服務(wù)完成上下文的初始化,然后通過上下文傳遞機(jī)制拷貝到領(lǐng)域能力上下文中,領(lǐng)域能力僅在上下文校驗標(biāo)準(zhǔn)步驟中做好上下文參數(shù)的非空校驗,確保領(lǐng)域服務(wù)傳遞過來了正確的數(shù)據(jù)。更多關(guān)于領(lǐng)域服務(wù)及能力上下文數(shù)據(jù)傳遞方案設(shè)計的技巧請參考《PICASO框架最佳實踐——上下文機(jī)制》。

3. 上下文校驗

完成領(lǐng)域服務(wù)及各個領(lǐng)域能力的上下文初始化邏輯之后,PICASO會繼續(xù)執(zhí)行領(lǐng)域服務(wù)及各個領(lǐng)域能力的上下文校驗邏輯。該標(biāo)準(zhǔn)步驟內(nèi)執(zhí)行的是需要結(jié)合上下文中的底層數(shù)據(jù)才能進(jìn)行的校驗邏輯,如調(diào)整預(yù)算時要求新預(yù)算與歷史預(yù)算差值必須大于5%且必須大于當(dāng)前消耗,該邏輯依賴歷史預(yù)算及物料當(dāng)前消耗詳情,就可以在上下文初始化步驟完成這兩部分底層數(shù)據(jù)的查詢,然后在上下文校驗步驟中直接從上下文中取出詳情數(shù)據(jù)執(zhí)行相關(guān)的校驗規(guī)則。默認(rèn)情況下領(lǐng)域服務(wù)與領(lǐng)域能力在上下文校驗步驟不需要執(zhí)行任何的參數(shù)或上下文傳遞操作。

4. 業(yè)務(wù)邏輯處理

基于上下文的業(yè)務(wù)校驗通過之后,PICASO框架會繼續(xù)觸發(fā)領(lǐng)域服務(wù)及能力執(zhí)行圖中各個領(lǐng)域能力的業(yè)務(wù)處理邏輯。需要注意的是由于領(lǐng)域能力的業(yè)務(wù)邏輯處理過程中可能會對上下文中已有的實體進(jìn)行了修改,也可能會構(gòu)建出新的業(yè)務(wù)實體對象,這些變更最終都需要被持久化到存儲介質(zhì)或者外部系統(tǒng)中。因此在默認(rèn)情況下,PICASO會在能每一個領(lǐng)域能力的業(yè)務(wù)邏輯處理標(biāo)準(zhǔn)步驟執(zhí)行完成之后執(zhí)行一次領(lǐng)域能力上下文到領(lǐng)域服務(wù)上下文的數(shù)據(jù)回傳操作,將領(lǐng)域能力上下文中的屬性拷貝到領(lǐng)域服務(wù)上下文中同名同類型的屬性上。這些數(shù)據(jù)將在領(lǐng)域服務(wù)的后續(xù)步驟中被持久化到存儲介質(zhì)中,或者被用于構(gòu)造領(lǐng)域事件及請求響應(yīng)結(jié)果。

6. 聚合根持久化、發(fā)布領(lǐng)域事件、構(gòu)造響應(yīng)結(jié)果

由于標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行器中的剩余的幾個標(biāo)準(zhǔn)步驟承載的都是請求維度的邏輯,領(lǐng)域能力執(zhí)行器標(biāo)準(zhǔn)模版中對這幾個方法也做了屏蔽,因此在這幾個標(biāo)準(zhǔn)步驟的執(zhí)行流程中就不需要再調(diào)用領(lǐng)域能力執(zhí)行圖了。需要特別說明的是,當(dāng)執(zhí)行到聚合根持久化標(biāo)準(zhǔn)步驟時,定義在領(lǐng)域服務(wù)及領(lǐng)域能力中的業(yè)務(wù)規(guī)則對實體的變更以及構(gòu)建出的新業(yè)務(wù)實體都已經(jīng)寫入到了上下文中,開發(fā)者可以充分利用領(lǐng)域服務(wù)對數(shù)據(jù)操作全局把控的職責(zé)定位,積極采用批量、異步、并行等手段進(jìn)行極致地性能優(yōu)化。

7. 定制化執(zhí)行流程

前五步內(nèi)容介紹了領(lǐng)域服務(wù)及領(lǐng)域能力標(biāo)準(zhǔn)執(zhí)行模版默認(rèn)的串聯(lián)執(zhí)行邏輯,PICASO框架也遵循約定大于配置(convention over configuration)的基本原則,如果默認(rèn)的執(zhí)行邏輯能夠滿足開發(fā)者的訴求,開發(fā)者不需要實現(xiàn)過多的流程控制,但是要更靈活地適配各類業(yè)務(wù)場景,PICASO框架也支持開發(fā)者對上述標(biāo)準(zhǔn)串聯(lián)執(zhí)行邏輯進(jìn)行定制化的修改:

?首先,PICASO允許開發(fā)者指定僅執(zhí)行領(lǐng)域服務(wù)的部分標(biāo)準(zhǔn)步驟,如前臺業(yè)務(wù)方期望能夠在實際調(diào)用系統(tǒng)的單元創(chuàng)建接口之前先對其構(gòu)造出來的請求參數(shù)進(jìn)行校驗提前發(fā)現(xiàn)問題,因此希望系統(tǒng)為其提供一個預(yù)校驗接口(注意這里的“預(yù)校驗”不是標(biāo)準(zhǔn)執(zhí)行模版中的參數(shù)預(yù)校驗步驟),該場景就可以直接復(fù)用物單元新建領(lǐng)域服務(wù)執(zhí)行器,并且在觸發(fā)領(lǐng)域服務(wù)執(zhí)行器時指定間僅執(zhí)行該領(lǐng)域服務(wù)的參數(shù)預(yù)校驗、上下文初始化及上下文校驗邏輯,領(lǐng)域服務(wù)完成前三個標(biāo)準(zhǔn)步驟的執(zhí)行之后就會立即返回前三步的執(zhí)行結(jié)果,從而快速實現(xiàn)業(yè)務(wù)方訴求,這其實也是標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行模版抽象帶來的額外收益。

?其次,PICASO框架支持開發(fā)者對領(lǐng)域能力執(zhí)行圖內(nèi)的參數(shù)及上下文傳遞的時機(jī)與具體映射邏輯、能力調(diào)用的失敗與異常處理以及各個領(lǐng)域能力的觸發(fā)時機(jī)等行為進(jìn)行定制化的修改,如出價設(shè)置能力與預(yù)算設(shè)置能力都依賴物料當(dāng)前的消耗數(shù)據(jù),除了在領(lǐng)域服務(wù)的上下文初始化步驟完成查詢的常規(guī)設(shè)計之外,也可以讓出價設(shè)置能力完成消耗數(shù)據(jù)查詢,然后以領(lǐng)域服務(wù)上下文作為媒介,將物料消耗數(shù)據(jù)從出價設(shè)置能力傳遞到預(yù)算設(shè)置能力中。這個時候就可以指定PICASO框架在完成出價設(shè)置能力的上下文初始化步驟調(diào)用之后立即執(zhí)行一次從能力到領(lǐng)域服務(wù)上下文的數(shù)據(jù)回傳操作,而不必等到默認(rèn)的業(yè)務(wù)邏輯處理步驟完成之后。而這些定制化的串聯(lián)執(zhí)行配置都可以通過接下來將要介紹的能力編排領(lǐng)域特定語言來實現(xiàn)。

能力編排執(zhí)行圖

在前一小節(jié)中我們介紹了PICASO框架內(nèi)部各個原子模塊與標(biāo)準(zhǔn)步驟的串聯(lián)執(zhí)行流程,PICASO框架通過內(nèi)置的標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎將各個模塊的串聯(lián)執(zhí)行職責(zé)從開發(fā)者手中轉(zhuǎn)移到了框架內(nèi)部,從而讓開發(fā)者專注于業(yè)務(wù)規(guī)則設(shè)計,實現(xiàn)填空式開發(fā)。這里說的“業(yè)務(wù)規(guī)則”一方面是指領(lǐng)域服務(wù)與領(lǐng)域能力各個標(biāo)準(zhǔn)步驟內(nèi)具體的業(yè)務(wù)邏輯,另一方面是要明確當(dāng)前業(yè)務(wù)流程需要按照什么樣的順序執(zhí)行哪些領(lǐng)域能力、能力執(zhí)行的前置條件、對默認(rèn)串聯(lián)規(guī)則的定制化配置(包括參數(shù)傳遞規(guī)則、上下文傳遞規(guī)則、錯誤及異常處理邏輯等),這些信息將以領(lǐng)域能力執(zhí)行圖的形式提供給PICASO框架,之后框架就可以按照開發(fā)者的意圖完成對各個領(lǐng)域能力的串聯(lián)調(diào)用,而能力編排指的就是構(gòu)建領(lǐng)域能力執(zhí)行圖的過程。

PICASO能力編排框架的核心職責(zé)有兩個,首先是在編碼階段讓開發(fā)者能夠以易用、簡潔、直白的方式快速定義出業(yè)務(wù)流程對應(yīng)的領(lǐng)域能力執(zhí)行圖,其次是在請求處理階段將能力執(zhí)行圖解析為可以被標(biāo)準(zhǔn)模版執(zhí)行引擎理解的執(zhí)行計劃,讓其能夠根據(jù)開發(fā)者意圖完成業(yè)務(wù)邏輯的處理。我們可以通過下圖所示的框架內(nèi)部實體關(guān)系圖對上述兩項職責(zé)進(jìn)行詳細(xì)闡述,圖中藍(lán)色線條標(biāo)記的是領(lǐng)域能力執(zhí)行圖的構(gòu)建過程,紅色線條標(biāo)記的是請求到來時領(lǐng)域能力執(zhí)行圖的執(zhí)行流程。

wKgaoWc7BI-AYG6OAAH447HlPwM876.png

PICASO能力編排框架內(nèi)部實體關(guān)系圖

上圖其實不算是標(biāo)準(zhǔn)的實體關(guān)系圖,它更像是實體關(guān)系圖與流程圖的結(jié)合,其中不同顏色的線表示不同執(zhí)行流程。事實上我們認(rèn)為這種呈現(xiàn)方式更加符合現(xiàn)實,實體之間的關(guān)系本就是復(fù)雜的,在不同的場景和流程下實體之間的關(guān)系和相互作用往往也是不同的。

從圖中我們可以看到,每一個領(lǐng)域服務(wù)執(zhí)行器內(nèi)部都集成了一個領(lǐng)域能力編排器,在編碼階段,開發(fā)者可以通過它提供的領(lǐng)域特定語言(Domain Specific Language, DSL)以直白的方式構(gòu)建領(lǐng)域能力執(zhí)行圖。能力執(zhí)行圖由多個領(lǐng)域能力編排節(jié)點構(gòu)成,每一個領(lǐng)域能力編排節(jié)點內(nèi)部都封裝著一個領(lǐng)域能力門面執(zhí)行器。當(dāng)請求到來時,業(yè)務(wù)模版執(zhí)行引擎會首先對能力編排執(zhí)行圖進(jìn)行解析,根據(jù)本次請求的參數(shù)及上下文信息將執(zhí)行圖中各個能力編排節(jié)點解析為零到多個領(lǐng)域能力執(zhí)行要素,所謂的領(lǐng)域能力執(zhí)行要素就是一個[領(lǐng)域能力執(zhí)行器、參數(shù)對象、上下文對象]三元組,它是一個有狀態(tài)的、會話級生命周期的實體,除了核心的能力執(zhí)行三要素之外,其內(nèi)部還維護(hù)著在請求處理過程中執(zhí)行引擎產(chǎn)生的一些控制中間狀態(tài),如當(dāng)前已執(zhí)行到哪個標(biāo)準(zhǔn)步驟、調(diào)用過程中是否發(fā)生了異常、本次調(diào)用是否已經(jīng)提前終止等。執(zhí)行圖中解析出來的所有領(lǐng)域能力執(zhí)行要素將被構(gòu)造成一個領(lǐng)域能力執(zhí)行要素鏈調(diào)用器,它用來控制各個領(lǐng)域能力執(zhí)行要素的觸發(fā)行為,包括能力執(zhí)行器各個標(biāo)準(zhǔn)步驟的逐步調(diào)用、能力編排節(jié)點的延遲解析、不同執(zhí)行要素之間的并行調(diào)用、能力執(zhí)行要器的提前終止等。在領(lǐng)域能力執(zhí)行要素鏈調(diào)用器的控制之下,能力執(zhí)行圖中的各個能力門面執(zhí)行器被依次觸發(fā),通過標(biāo)準(zhǔn)可執(zhí)行實體發(fā)現(xiàn)與路由機(jī)制定位到當(dāng)前請求應(yīng)該使用的能力實例,調(diào)用其各個標(biāo)準(zhǔn)步驟完成業(yè)務(wù)邏輯處理。由于本文旨在介紹PICASO框架中各項組件的基本原理和運(yùn)行機(jī)制,并沒有對能力編排框架的實現(xiàn)細(xì)節(jié)做過多探討,有關(guān)能力編排框架各項特性的詳細(xì)介紹請參考《PICASO框架最佳實踐——能力編排》。

能力編排領(lǐng)域特定語言

在PICASO框架設(shè)計之初,我們也曾想直接引入一些開源的流程編排框架來實現(xiàn)領(lǐng)域能力之間的串聯(lián)調(diào)用。但是正如本章節(jié)最開始論述的那樣,現(xiàn)有的開源解決方案并沒有滿足我們的核心關(guān)切:以直白、簡潔、輕量、易用的方式實現(xiàn)能力組裝,解決為了應(yīng)對業(yè)務(wù)本質(zhì)復(fù)雜度而采取的各項實體拆分與路由機(jī)制帶來的編碼繁瑣、模塊組裝邏輯復(fù)雜等副作用,減少膠水代碼和開發(fā)者的編碼負(fù)擔(dān),提高關(guān)鍵業(yè)務(wù)信息密度。圖形化、配置化的流程編排框架雖然能夠直觀的呈現(xiàn)業(yè)務(wù)處理流程,但是也造成靈活度差、普適性低、開發(fā)流程割裂、業(yè)務(wù)知識分散、模塊串聯(lián)配置繁瑣等問題,無法達(dá)成整體熵減的設(shè)計目標(biāo),而PICASO框架解決這些問題的方案則是自定義能力編排領(lǐng)域特定語言。

領(lǐng)域特定語言(Domain Specific Language, DSL)是專門針對特定應(yīng)用領(lǐng)域的計算機(jī)語言,與C++、Java等通用計算機(jī)語言(General Purpose Language,GPL)相比,領(lǐng)域特定語言的功能及普適性十分有限,但是在特定領(lǐng)域之內(nèi)它卻具有強(qiáng)大的表達(dá)能力。領(lǐng)域特定語言的核心吸引力在于它提供了一種更清晰地傳達(dá)系統(tǒng)各部分意圖的方法,提高代碼的可讀性(雖然我們總是有意或者無意地低估了代碼可讀性對生產(chǎn)力的影響),降低開發(fā)者與領(lǐng)域?qū)<遥óa(chǎn)品、測試甚至是用戶)的溝通難度。它能夠讓使用者輕松實現(xiàn)聲明式編程(Declarative Program),對業(yè)務(wù)層開發(fā)者來說,這意味著他們可以直接告訴框架他們想做什么,而不必編寫要想達(dá)成目的而需要執(zhí)行的具體操作步驟(Martin Fowler, Domain Specific Language, 2013),也正是這一點讓PICASO框架能夠?qū)⑶拔乃龅母黜棙I(yè)務(wù)復(fù)雜度應(yīng)對措施帶來的系統(tǒng)偶然復(fù)雜度屏蔽在框架內(nèi)部,將PICASO框架內(nèi)部的各個功能模塊有機(jī)結(jié)合到一起,共同實現(xiàn)整體熵減的設(shè)計目標(biāo)。

軟件工程領(lǐng)域的大師Martin Fowler將領(lǐng)域特定語言分為外部DSL(External DSL)和內(nèi)部DSL(Internal DSL)兩大類。外部DSL往往擁有自定義語法、需要宿主應(yīng)用的代碼執(zhí)行文本解析,基于該類DSL編寫的業(yè)務(wù)規(guī)則通常以腳本或配置的形式存在于系統(tǒng)代碼之外,典型的案例是正則表達(dá)式。而內(nèi)部DSL是通用編程語言的子集,它對外提供一組特定的API,利用內(nèi)部DSL編寫的業(yè)務(wù)規(guī)則往往是一段合法的代碼,典型的例子就是JDK8之后提供的Java Stream API。與外部DSL相比,內(nèi)部DSL不需要專門的語法解析器和開發(fā)平臺,可以直接與宿主應(yīng)用代碼無縫銜接,也能直接復(fù)用普通IDE的代碼提示與自動補(bǔ)全功能,也正因為此,為了向業(yè)務(wù)開發(fā)者提供集中、連貫的開發(fā)體驗,我們最終選擇為PICASO能力編排框架開發(fā)一套內(nèi)部領(lǐng)域特定語言。

為了盡可能靈活地適配所有的業(yè)務(wù)流程構(gòu)建場景,我們在PICASO框架的能力編排DSL中定義了順序、條件和循環(huán)三套能力編排邏輯,分別對應(yīng)順序執(zhí)行、if...else判斷、循環(huán)三種流程控制方式。其中每一類能力編排節(jié)點的配置都遵循約定大于配置的原則,按照標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎的默認(rèn)執(zhí)行邏輯提供了全部缺省配置,同時開發(fā)者也可以通過能力編排API定制自定義的執(zhí)行邏輯,如循環(huán)規(guī)則、分支判斷條件、觸發(fā)能力時的參數(shù)及上下文傳遞邏輯、失敗及異常處理邏輯、能力節(jié)點解析步驟等。由于本文旨在介紹PICASO框架的設(shè)計思想和各模塊的底層運(yùn)行機(jī)制,因此我們不會對能力編排框架所有DSL API進(jìn)行詳細(xì)論述,這部分內(nèi)容將在《PICASO框架最佳實踐——能力編排》一文進(jìn)行詳細(xì)論述。本文僅通過一個實際的能力執(zhí)行圖構(gòu)建案例讓大家對能力編排DSL有一個具象的感知。

下圖給出的是一個站外字節(jié)廣告計劃創(chuàng)建請求處理流程對應(yīng)的領(lǐng)域能力執(zhí)行圖構(gòu)建邏輯,可以看出能力編排DSL僅用數(shù)十行代碼以一種近乎白話文形式描述出了完整的計劃構(gòu)建過程:首先對用戶已創(chuàng)建計劃數(shù)量進(jìn)行上限檢查(CampaignUpperLimitCheckAbility);上限校驗通過后會構(gòu)造出一個空的計劃對象并為其填充用戶ID、計劃類型等基礎(chǔ)信息(CampaignBaseInfoAssembleAbility);然后判斷本次計劃創(chuàng)建請求是否在參數(shù)中設(shè)置了聯(lián)合活動ID,如果聯(lián)合活動ID不為空則需要執(zhí)行聯(lián)合活動信息設(shè)置相關(guān)的業(yè)務(wù)邏輯(CampaignJointActivityAbility);完成聯(lián)合活動信息設(shè)置之后就要依次設(shè)置計劃的投放周期(CampaignScheduleConfigAbility)、計劃名稱(CampaignNameConfigAbility)、營銷目標(biāo)(CampaignMarketTypeConfigAbility)、應(yīng)用集(TrafficStrategyConfigAbiliy)等模塊的相關(guān)屬性,需要注意的是在上述幾個能力的編排邏輯中,由于領(lǐng)域能力的參數(shù)或上下文對象中的屬性名稱與領(lǐng)域服務(wù)的參數(shù)及上下文中相應(yīng)屬性并不匹配,默認(rèn)的參數(shù)及上下文數(shù)據(jù)傳遞機(jī)制將無法為這幾個屬性設(shè)值,因此開發(fā)者對營銷目標(biāo)設(shè)置能力的參數(shù)傳遞(cmdTransfer)、上下文傳遞(ctxTransfer)、應(yīng)用集設(shè)置能力的上下文傳遞(ctxTransfer)邏輯進(jìn)行了自定義拓展,手動實現(xiàn)了參數(shù)及上下文中特殊屬性的數(shù)據(jù)映射邏輯;在完成這些業(yè)務(wù)模塊的屬性設(shè)置之后,如果請求參數(shù)中設(shè)置的標(biāo)的物類型為“商品庫”,那么接下來就要執(zhí)行與站外DPA廣告業(yè)務(wù)相關(guān)的特殊業(yè)務(wù)環(huán)節(jié):推廣SKU的校驗(SkuValidateAbility)以及SKU跟單信息的配置(TraceOrderSkuConfigAbility)邏輯......

wKgaoWc7BJKAYZniAAeB1kuLdEQ286.png

順序及條件能力編排DSL示例

上圖計劃新建流程的能力編排邏輯中只用到了順序和條件編排這兩種最常用的編排模式,但是在一些批量操作請求處理流程中通常還會用到循環(huán)編排模式,它允許標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎重復(fù)調(diào)用同一個能力門面實現(xiàn)批量請求的處理。下圖通過批量創(chuàng)意綁定接口給出了循環(huán)能力編排模式的使用示例,在該流程的最后一個環(huán)節(jié)通過registerFlatMapped能力編排API注冊了一個創(chuàng)意綁定能力(CreativeBindAbility)。該能力被設(shè)計為處理單個創(chuàng)意的綁定操作,而批量創(chuàng)意綁定領(lǐng)域服務(wù)卻需要在一次請求中完成多個創(chuàng)意的綁定操作,因此我們通過cmdFlatMap能力編排API定義了將領(lǐng)域服務(wù)參數(shù)中的待綁定創(chuàng)意列表展開成多個單創(chuàng)意綁定能力參數(shù)的規(guī)則,標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎將據(jù)此遍歷每一個單創(chuàng)意綁定參數(shù)并調(diào)用單創(chuàng)意綁定能力進(jìn)而實現(xiàn)批量創(chuàng)意綁定。從示例中我們還可以看到,開發(fā)者在編排創(chuàng)意綁定能力時還通過transferCtxBeforeStep能力編排API指定在調(diào)用領(lǐng)域能力的上下文初始化和業(yè)務(wù)邏輯處理兩個標(biāo)準(zhǔn)步驟前都執(zhí)行一次領(lǐng)域服務(wù)到領(lǐng)域能力的上下文傳遞操作,而標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎的默認(rèn)行為是僅在領(lǐng)域能力上下文初始化標(biāo)準(zhǔn)步驟調(diào)用前執(zhí)行該操作。除此之外,開發(fā)者還通過onFailure/onException能力編排API定制了失敗及異常處理措施,確保在循環(huán)過程中單個創(chuàng)意綁定失敗或異常不會中斷整個業(yè)務(wù)處理流程,而是在處理完所有待綁定創(chuàng)意之后,將綁定失敗的創(chuàng)意信息及失敗原因返回給調(diào)用方。

wKgZoWc7BJWAGJz0ABWPCC6Apns874.png

循環(huán)能力編排DSL示例

有些讀者可能會疑惑為什么不把創(chuàng)意綁定能力設(shè)計為直接在能力粒度上就支持批量綁定邏輯,這是因為當(dāng)前的設(shè)計是綜合考慮能力劃分原則、創(chuàng)意綁定業(yè)務(wù)實際與拆分規(guī)則收益等因素之后決定的。我們曾在前文中提到,領(lǐng)域能力封裝的是最小原子業(yè)務(wù)模塊,而批量處理實際上屬于流程控制邏輯,因此從職責(zé)劃分的角度考慮,領(lǐng)域能力沉淀單個創(chuàng)意綁定的具體業(yè)務(wù)規(guī)則、領(lǐng)域服務(wù)負(fù)責(zé)循環(huán)流程控制的設(shè)計更符合PICASO框架的底層設(shè)計邏輯;另外從業(yè)務(wù)實際來分析,由于不同創(chuàng)意類型對應(yīng)的創(chuàng)意綁定邏輯也有差異,因此創(chuàng)意綁定能力會按照創(chuàng)意類型拆分為不同的能力實例,而且廣告主有可能通過批量綁定接口一次性綁定類型不同的多個創(chuàng)意。在這樣的業(yè)務(wù)背景下,單創(chuàng)意綁定的能力拆分邏輯可以讓領(lǐng)域服務(wù)直接遍歷每一個待綁定創(chuàng)意,然后通過能力門面將單個創(chuàng)意綁定請求路由到與待綁定創(chuàng)意類型相適配的能力實例上進(jìn)行處理,而不需要執(zhí)行按創(chuàng)意類型分組等預(yù)處理邏輯;最后從拆分收益上看,單個創(chuàng)意綁定的拆分邏輯能夠給我們提供更多的編排靈活性,通過定制化的失敗及異常處理邏輯,不同的領(lǐng)域服務(wù)可以靈活地支持快速失敗或允許部分失敗等異常處理規(guī)則。綜上考慮我們最終采用了單創(chuàng)意綁定的能力拆分規(guī)則,而這個決策過程其實也是我們進(jìn)行能力拆分的一般流程。從這個例子中我們可以發(fā)現(xiàn),能力拆分其實是一個主觀性很強(qiáng)的行為,盡管我們?yōu)槠渲贫艘幌盗械闹笇?dǎo)準(zhǔn)則,但是在實際需求中開發(fā)者依然需要充分發(fā)揮主觀能動性,在充分理解新架構(gòu)設(shè)計思想的前提下,緊貼業(yè)務(wù)實際,在系統(tǒng)性能、架構(gòu)整潔程度以及實現(xiàn)成本之間找到最佳的平衡點。

截止到目前,PICASO領(lǐng)域能力編排框架已經(jīng)經(jīng)歷了兩輪功能迭代,前文所述的順序、條件及循環(huán)編排是第一代能力編排框架提供的特性,這一代能力編排框架僅支持領(lǐng)域能力之間編排串聯(lián),而領(lǐng)域服務(wù)各個標(biāo)準(zhǔn)步驟內(nèi)的業(yè)務(wù)邏輯和能力執(zhí)行圖之間的串聯(lián)依然需要手動硬編碼實現(xiàn)。下圖給出了一個使用第一代能力編排框架的領(lǐng)域服務(wù)案例,這個案例也清晰地呈現(xiàn)了領(lǐng)域能力執(zhí)行圖與領(lǐng)域服務(wù)各個標(biāo)準(zhǔn)步驟之間的關(guān)系??梢钥吹筋I(lǐng)域服務(wù)執(zhí)行器提供了一個標(biāo)準(zhǔn)的領(lǐng)域能力編排入口registerDomainAbilities,該模板方法通過參數(shù)提供了一個領(lǐng)域能力編排器(DomainAbilityOrchestrator),開發(fā)者可以通過該模板方法完成領(lǐng)域能力執(zhí)行圖的構(gòu)建。然而當(dāng)請求到來時,標(biāo)準(zhǔn)業(yè)務(wù)模版執(zhí)行引擎并不會直接觸發(fā)領(lǐng)域能力執(zhí)行圖的調(diào)用,它僅會按照標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行流程依次調(diào)用領(lǐng)域服務(wù)執(zhí)行器的各個標(biāo)準(zhǔn)步驟,而領(lǐng)域能力執(zhí)行圖的調(diào)用則必須由開發(fā)者硬編碼到領(lǐng)域服務(wù)執(zhí)行器的各個標(biāo)準(zhǔn)步驟中。當(dāng)然框架已經(jīng)將領(lǐng)域能力執(zhí)行圖的觸發(fā)邏輯封裝成了相對易用的API(詳見下面截圖中doPreValidate方法中被標(biāo)注的代碼片段),因此這種設(shè)計的實現(xiàn)成本不算太高。但是隨著系統(tǒng)中領(lǐng)域服務(wù)執(zhí)行器數(shù)量的增長,它也的確在系統(tǒng)中引入了大量重復(fù)的膠水代碼,并且造成了領(lǐng)域服務(wù)中業(yè)務(wù)邏輯與領(lǐng)域能力值執(zhí)行圖之間的割裂,無法提供徹底的連貫開發(fā)體驗。

wKgaoWc7BJiAVkOiAAcXGoy291o573.png

第一代能力編排框架依然存在一些問題

近年來,為了給廣告主提供簡潔易用的投放體驗,系統(tǒng)正越來越多地向著智能化和集成化的方向發(fā)展,讓廣告主少操作、少輸入成為UI交互設(shè)計重要原則。在這樣的業(yè)務(wù)背景下,我們收到了越來越多的跨層級接口合并需求,這讓我們的業(yè)務(wù)在模塊化的基礎(chǔ)上又呈現(xiàn)出集成化特征,而且很多情況下,這種集成是“跨層級”的。如過去我們的廣告物料一直遵循經(jīng)典的計劃、單元、創(chuàng)意三層結(jié)構(gòu),計劃下可創(chuàng)建多個單元、單元下可綁定多個創(chuàng),創(chuàng)建物料時需要依次調(diào)用三個接口。然而在近期全站推廣、一頁投放等需求都有一鍵創(chuàng)建全層級物料的訴求。但是由于子層級物料的創(chuàng)編流程都依賴父層級的物料對象創(chuàng)建完成,這與第一代能力編排框架中的重組執(zhí)行機(jī)制底層邏輯相悖;另外,由于父層級物料對象與子層級物料對象之間都是一對多的關(guān)系,在一鍵創(chuàng)建全套物料的場景中,我們需要循環(huán)調(diào)用子層級物料的構(gòu)建流程,然而第一代能力編排框架中循環(huán)能力編排特性僅支持循環(huán)調(diào)用單個領(lǐng)域能力,而不支持對多個領(lǐng)域能力執(zhí)行鏈的重復(fù)觸發(fā)。

為了更好地適配業(yè)務(wù)發(fā)展趨勢,同時解決上文提到的第一代能力編排框架中由于領(lǐng)域服務(wù)與領(lǐng)域能力執(zhí)行圖之間邏輯分離定義、需要手動硬編碼完成串聯(lián)造成的業(yè)務(wù)邏輯割裂、會引入大量重復(fù)的膠水代碼等問題,我們在第一代能力編排框架的基礎(chǔ)上進(jìn)行了升級,引入了子流程編排機(jī)制。子流程編排最重要的升級是增加了分階段集成的特性,這也與當(dāng)下多接口集成的業(yè)務(wù)發(fā)展趨勢相符,一個完整的請求處理流程可以被拆分為不同的執(zhí)行階段,后置階段的執(zhí)行邏輯可能依賴于前置階段的執(zhí)行結(jié)果。PICASO框架允許開發(fā)者通過子流程編排DSL將一個完整的業(yè)務(wù)流程拆分定義為多個業(yè)務(wù)處理階段,其中每一個業(yè)務(wù)處理階段被稱為一個子流程,每一個子流程可以看做是一個小的領(lǐng)域服務(wù),有各自專屬的標(biāo)準(zhǔn)處理步驟及領(lǐng)域能力執(zhí)行圖。除了可以像第一代能力編排框架那樣為每一個子流程定義專屬的領(lǐng)域能力執(zhí)行圖之外,第二代子流程編排框架還讓開發(fā)者不需要再去重寫領(lǐng)域服務(wù)執(zhí)行器標(biāo)準(zhǔn)模板中的各個標(biāo)準(zhǔn)步驟,而是通過子流程編排DSL將每個子流程專屬的參數(shù)預(yù)校驗、上下文初始化、上下文校驗及業(yè)務(wù)邏輯處理四個標(biāo)準(zhǔn)步驟與領(lǐng)域能力執(zhí)行圖一起直接定義到子流程執(zhí)行圖中,從而徹底解決了第一代能力編排框架中領(lǐng)域服務(wù)維度的業(yè)務(wù)邏輯與能力執(zhí)行圖開發(fā)割裂的問題。也正因為如此我們在第二代子流程編排DSL中將第一代編排框架中的領(lǐng)域能力編排器(DomainAbilityOrchestrator)升級成了領(lǐng)域服務(wù)構(gòu)造器(DomainServiceBuilder)。

下圖給出了一個使用子流程編排DSL構(gòu)建領(lǐng)域服務(wù)執(zhí)行圖的例子,可以看到,與第一代能力編排框架類似,第二代子流程編排DSL為開發(fā)者提供了順序、條件、循環(huán)、捆綁及包裝五種子流程編排節(jié)點。其中循環(huán)子流編排節(jié)點支持子流程維度的循環(huán)調(diào)用,解決了第一代能力編排框架中FlatMap編排節(jié)點僅支持對單個能力進(jìn)行循環(huán)調(diào)用的問題,因此在一對多的子層級物料創(chuàng)編場景中有廣泛的應(yīng)用,如下面例子中的在單元下綁定關(guān)鍵詞列表的場景:一個單元下可以綁定多個關(guān)鍵詞,而單個關(guān)鍵詞的綁定操作需要串聯(lián)執(zhí)行多個領(lǐng)域能力,此時我們可以將單個關(guān)鍵詞的構(gòu)建過程定義為一個基礎(chǔ)順序子流程,然后再通過循環(huán)子流程編排節(jié)點將這個基礎(chǔ)子流程循環(huán)集成到單元創(chuàng)建領(lǐng)域服務(wù)執(zhí)行圖中,這樣就可以實現(xiàn)在單元下批量綁定關(guān)鍵詞的功能;而捆綁子流程的作用類似于通用編程語言中的‘{}’,它能夠?qū)⒍鄠€子流程包裝成一個邏輯子流程節(jié)點,這一特性讓子流程編排能夠支持任意不同子流程之間的組合及嵌套,進(jìn)一步提升了編排框架對各類業(yè)務(wù)場景的普適性;包裝子流程節(jié)點則是專門為已有領(lǐng)域服務(wù)執(zhí)行器的組合復(fù)用而設(shè)計的,它可以將一個現(xiàn)有的領(lǐng)域服務(wù)執(zhí)行器包裝成一個子流程編排節(jié)點添加到子流程編排執(zhí)行圖中,這一特性在復(fù)用現(xiàn)有接口進(jìn)行集成化改造或?qū)崿F(xiàn)批量操作等需求中會為開發(fā)者提供極大的便利。

wKgZoWc7BJqAG4zPAAcvgL_PGlg969.png

第二代子流程及能力編排框架示例

通過上面的幾個例子我們能感受到,領(lǐng)域能力編排DSL能夠以極高的信息密度描述業(yè)務(wù)流程的關(guān)鍵信息,通過構(gòu)建領(lǐng)域能力/子流程執(zhí)行圖的方式定義業(yè)務(wù)流程的措施不僅能夠減少膠水代碼、降低開發(fā)負(fù)擔(dān),還可以協(xié)助開發(fā)者快速建立起對業(yè)務(wù)的全景認(rèn)知,同時,領(lǐng)域能力/子流程執(zhí)行圖也能作為詳細(xì)業(yè)務(wù)規(guī)則的目錄或索引,在進(jìn)行業(yè)務(wù)梳理或問題排查時讓開發(fā)者可以按圖索驥快速定位目標(biāo)代碼的大致位置,提升業(yè)務(wù)知識傳承及問題定位的效率。

盡管我們在PICASO框架中舍棄了圖形化流程編排框架的設(shè)計,但是我們并沒有否定它存在的意義,這種編排方式在低代碼編程領(lǐng)域占有重要的地位。但是它更適合在一些節(jié)點類型有限或者流程相對穩(wěn)定的業(yè)務(wù)場景下使用,比如審批流程,雖然審批流可能有很多,但是每個審批流程中的節(jié)點種類往往不多,可以用相對固定的模式進(jìn)行串聯(lián);或者廣告播放流程,盡管其業(yè)務(wù)流程同樣節(jié)點眾多、冗長復(fù)雜,但是流程數(shù)量相對固定,基本上可以分為展示、搜索、推薦、站外、合約這五大核心流程,很多變更是對流程的微調(diào)或節(jié)點內(nèi)部的邏輯升級。與這些業(yè)務(wù)相比,廣告投放系統(tǒng)則維護(hù)了近300個能力節(jié)點、400多條請求處理流程,每次需求迭代都會涉及數(shù)十條業(yè)務(wù)流程的變更。在這樣的業(yè)務(wù)特點下,簡潔、靈活、集中、連貫的開發(fā)體驗要比一個炫酷的UI交互界面重要的多。當(dāng)然,我們依然十分認(rèn)可圖形化界面強(qiáng)大的呈現(xiàn)能力,事實上我們也在規(guī)劃為PICASO框架開發(fā)內(nèi)置的代碼元數(shù)據(jù)管理平臺,其中一項重要的功能就是把代碼中基于能力編排DSL構(gòu)建出來的領(lǐng)域能力執(zhí)行圖解析為業(yè)務(wù)流程圖回顯到交互界面中,讓開發(fā)者可以直觀地查看所有領(lǐng)域服務(wù)的處理流程。但是該平臺只做能力編排邏輯的圖形化展示,領(lǐng)域能力執(zhí)行圖的構(gòu)造依然是通過代碼中的能力編排API實現(xiàn)的(當(dāng)然該平臺也可以提供各個能力編排節(jié)點配置屬性的動態(tài)修改功能,但是這僅作為緊急情況下的應(yīng)急處理措施)。

PICASO的愿景:構(gòu)建圖書館式代碼架構(gòu)

至此我們已經(jīng)完成了對PICASO框架全部核心模塊的介紹,此刻讓我們再次回首PICASO框架的設(shè)次初衷——竭盡所能地提升團(tuán)隊的研發(fā)效率,這也是一線業(yè)務(wù)開發(fā)團(tuán)隊的核心價值所在。

提到研發(fā)效率,有很多同學(xué)認(rèn)為少寫代碼就是研發(fā)效率高,也有同學(xué)認(rèn)為支持了配置化、使用了拓展點就能實現(xiàn)研發(fā)效率的提升。然而事實上代碼行數(shù)從來就是不是制約研發(fā)效率提升的核心要素,對業(yè)務(wù)問題的抽象和封裝有時的確會導(dǎo)致我們多寫一些代碼,但是與多寫幾行代碼花費(fèi)的幾十分鐘相比,在實際工作中我們會把更多的時間和精力消耗在確定分工時與合作團(tuán)隊的扯皮上、需求設(shè)計時各團(tuán)隊方案對齊及歷史業(yè)務(wù)邏輯的確認(rèn)上、出現(xiàn)問題時原因的定位與影響范圍的評估上、由于自己或者上下游系統(tǒng)的技術(shù)或架構(gòu)限制而不得不進(jìn)行的妥協(xié)方案開發(fā)上......解決這些問題所花費(fèi)的時間才是阻礙研發(fā)效率提升和耗盡業(yè)務(wù)方對研發(fā)團(tuán)隊信任的根源。至于配置化和拓展點,不可否認(rèn)的是它們的確是包括PICASO框架在內(nèi)的很多效率提升解決方案中經(jīng)常采用的措施,但是如果上述問題得不到解決,這些措施也只能是治標(biāo)不治本。正如“鮑勃大叔”Robert C.Martin在其著作《架構(gòu)整潔之道》中所說:“不管你多敬業(yè)、加多少班,在面對爛系統(tǒng)時你仍然會寸步難行,因為你大部分的精力是在應(yīng)對混亂。”而我們之所以要為分工扯皮、之所以不敢升級系統(tǒng)以適配新的業(yè)態(tài)、之所以無法快速梳理出業(yè)務(wù)規(guī)則,都是因為過去大泥團(tuán)式的代碼架構(gòu)讓我們看不懂、不敢動、動不了。

為了解決上述問題,PICASO框架在設(shè)計之初就確立了框架即規(guī)范、代碼即文檔的基本原則,從設(shè)計階段就開始引導(dǎo)開發(fā)者以統(tǒng)一的思想和方法論對業(yè)務(wù)進(jìn)行拆解分析,然后將業(yè)務(wù)規(guī)則淀到標(biāo)準(zhǔn)業(yè)務(wù)執(zhí)行模版或拓展點接口中,確保每一處業(yè)務(wù)規(guī)則都有可檢索、可引用的實體邊界;接著通過能力編排框架以近乎白話文的方式將這些原子業(yè)務(wù)實體組裝起來,以極高的信息密度清晰地描述完整的業(yè)務(wù)流程;最后通過通用可執(zhí)行實體發(fā)現(xiàn)及路由機(jī)制對各層實體進(jìn)行分類及分組,對上層實體暴露統(tǒng)一的調(diào)用門面,除了起到逐層向上屏蔽分組內(nèi)部場景復(fù)雜度的作用之外,PICASO框架維護(hù)的可執(zhí)行實體路由表也可以作為業(yè)務(wù)細(xì)節(jié)邏輯的速查索引。有了這些措施,開發(fā)者進(jìn)行業(yè)務(wù)邏輯梳理時就可以先通過領(lǐng)域服務(wù)維度的路由表定位到目標(biāo)場景對應(yīng)的領(lǐng)域服務(wù)實例,然后通過其領(lǐng)域能力執(zhí)行圖快速建立起對務(wù)流程的全景認(rèn)知,接著選擇執(zhí)行圖中感興趣的業(yè)務(wù)節(jié)點,通過領(lǐng)域能力維度的路由表快速定位到具體的能力實例,最后到相應(yīng)的的標(biāo)準(zhǔn)步驟中定位具體的實現(xiàn)細(xì)節(jié)。這種由粗及細(xì)、逐層按索引查找的過程類似于圖書館的管理模式:借閱圖書時,我們需要大體推斷目標(biāo)書籍所屬的類目,然后通過類目確定書籍所在的書架,在書架上找到目標(biāo)書籍后再通過其目錄快速概覽全書,最后通過目錄定位到感興趣的內(nèi)容,這就是圖書館式代碼架構(gòu)的由來。我們希望這種代碼架構(gòu)能夠讓業(yè)務(wù)知識通過系統(tǒng)代碼清晰、準(zhǔn)確、完整地表達(dá)出來并能流暢地傳承下去,進(jìn)而讓團(tuán)隊在面對業(yè)態(tài)更迭時能夠更加從容地承接業(yè)務(wù)方提出的各項訴求;在進(jìn)行多團(tuán)隊協(xié)作時能夠擁有足夠的底氣承擔(dān)更多的責(zé)任;在遇到線上問題時能夠更加快速地定位問題并制定解決方案;在接手新系統(tǒng)時也能夠快速梳理出業(yè)務(wù)主線并上手開發(fā),最終實現(xiàn)團(tuán)隊整體研發(fā)效率的提升。

(二)聚合與資源庫:拒絕魔法邏輯,讓代碼直接表達(dá)業(yè)務(wù)規(guī)則

如果說前文介紹的PICASO框架讓新架構(gòu)擁有了領(lǐng)域驅(qū)動設(shè)計之形,那么接下來將要介紹的聚合與資源庫機(jī)制將讓新架構(gòu)真正具備領(lǐng)域驅(qū)動設(shè)計的靈魂。

長期以來,我們的開發(fā)行為中業(yè)務(wù)設(shè)計與代碼設(shè)計是分離的。接到需求后研發(fā)人員會和產(chǎn)品及業(yè)務(wù)方進(jìn)行大量的溝通,確認(rèn)業(yè)務(wù)流程及各項細(xì)節(jié)規(guī)則,這是業(yè)務(wù)設(shè)計的過程。但是在編碼階段,開發(fā)人員又會對業(yè)務(wù)設(shè)計結(jié)果重新進(jìn)行抽象,轉(zhuǎn)化為代碼實現(xiàn)方案。而此時傳統(tǒng)的三層架構(gòu)過于簡單的層次劃分很容易將開發(fā)者的大部分注意力引導(dǎo)到數(shù)據(jù)庫設(shè)計上,代碼設(shè)計就變成了對數(shù)據(jù)庫增刪改查操作的設(shè)計。在這個過程中前期業(yè)務(wù)設(shè)計沉淀的大量領(lǐng)域知識往往會被丟棄,實現(xiàn)出來的代碼也失去了對業(yè)務(wù)規(guī)則的直接表達(dá)。而缺乏基礎(chǔ)模型設(shè)計的軟件充其量也只是一種機(jī)械化的產(chǎn)品,雖能實現(xiàn)功能卻無法解釋這樣操作的原因。更嚴(yán)重的是,如果底層數(shù)據(jù)庫存在表或字段的復(fù)用,那么業(yè)務(wù)規(guī)則被直接翻譯成庫表增刪改查操作邏輯之后,代碼甚至?xí)磉_(dá)出與業(yè)務(wù)規(guī)則完全不相符的含義出來。這些代碼就成了只有開發(fā)者自己才能看懂的魔法邏輯,甚至經(jīng)過一段時間之后開發(fā)者本人也會忘記這些代碼背后真正的業(yè)務(wù)含義......

如果整個程序設(shè)計或者其核心部分沒有與領(lǐng)域模型相對應(yīng),那么該模型就是沒有價值的,軟件的正確性也值得懷疑。(Eric Evans, Domain-Driven Design: Trackling Complexity in the Heart of Software, 2003)

面向數(shù)據(jù)庫編程的設(shè)計思維還容易引導(dǎo)我們最終以事務(wù)腳本(Transaction Script)的形式實現(xiàn)業(yè)務(wù)流程的處理,將業(yè)務(wù)規(guī)則與數(shù)據(jù)庫表操作邏輯糅合到一起,業(yè)務(wù)實體之間的關(guān)系也因此分散和隱藏到了整個工程中不同的接口實現(xiàn)里,這會給數(shù)據(jù)模型全景認(rèn)知的建立帶來極大的障礙。領(lǐng)域驅(qū)動設(shè)計思想的祖師爺及布道者Eric Evans曾提到自己項目組的成員曾花費(fèi)數(shù)月時間才梳理出一個完整的數(shù)據(jù)模型,而在我們自己的記憶里,似乎也沒有哪位同學(xué)有底氣敢宣稱自己掌握了完整的數(shù)據(jù)模型(我們可能清楚數(shù)據(jù)庫中有哪些庫表,但是由于底層庫表存在不同業(yè)務(wù)場景復(fù)用的情況,導(dǎo)致表中數(shù)據(jù)對應(yīng)的業(yè)務(wù)含義并不統(tǒng)一。這種復(fù)用表結(jié)構(gòu)的設(shè)計無可厚非,在很多情況下我們甚至都鼓勵這種縱向拓展方式,但這也的確是造成我們對數(shù)據(jù)模型認(rèn)識模糊和不完整的主要原因)。數(shù)據(jù)模型全景認(rèn)知的缺失讓開發(fā)者很難進(jìn)行統(tǒng)一的模型頂層設(shè)計,在多需求并行推進(jìn)的開發(fā)模式下很容易在不同項目組之間形成信息孤島,無法實現(xiàn)模型復(fù)用與合并,導(dǎo)致數(shù)據(jù)模型野蠻膨脹,這反過來又進(jìn)一步加劇了數(shù)據(jù)模型全景認(rèn)知的構(gòu)建難度,從而陷入到惡性循環(huán)中無法自拔。除此之外,業(yè)務(wù)流程中不同的業(yè)務(wù)環(huán)節(jié)可能會依賴相同的底層數(shù)據(jù),事務(wù)腳本式的業(yè)務(wù)處理邏輯會將這些底層數(shù)據(jù)的讀寫操作分散到不同的接口或業(yè)務(wù)模塊的實現(xiàn)中,除了會造成重復(fù)編碼之外,還有可能在運(yùn)行時造成重復(fù)和碎片化的數(shù)據(jù)讀寫操作,進(jìn)而影響系統(tǒng)性能。

聚合與資源庫機(jī)制就是專門為解決上述問題而生的。

聚合與資源庫機(jī)制實現(xiàn)數(shù)據(jù)模型與業(yè)務(wù)邏輯分離

聚合(Aggregate)是領(lǐng)域驅(qū)動設(shè)計思想中重要概念,它是一組相關(guān)對象的集合,這些對象之間關(guān)聯(lián)密切,彼此之間已經(jīng)按照對象之間的關(guān)系以父子屬性的方式組裝好。每個聚合都有明確的邊界(boundary)和一個聚合根(root),其中邊界定義了這個聚合中都有哪些對象,而聚合根則是這些對象中的一個特定實體。聚合根是聚合內(nèi)唯一個允許被外部對象引用的實體,也是聚合中的所有實體的最頂層父級對象,因此通過聚合根可以訪問到聚合內(nèi)所有對象,這會給上層業(yè)務(wù)規(guī)則的實現(xiàn)帶來了極大的便利。但是實際業(yè)務(wù)中聚合內(nèi)的實體關(guān)系通常都十分復(fù)雜,常常存在多級嵌套關(guān)系。比如廣告投放業(yè)務(wù)中計劃聚合內(nèi)就存在著“計劃->單元->創(chuàng)意->子創(chuàng)意->子創(chuàng)意審核記錄”這種多達(dá)5層的嵌套實體結(jié)構(gòu)。如果每次使用聚合時都需要開發(fā)者手動實現(xiàn)聚合內(nèi)實體的組裝邏輯顯然會帶來大量的重復(fù)代碼及編碼負(fù)擔(dān),對系統(tǒng)未來的維護(hù)來說也將是一場災(zāi)難,而資源庫就是為了解決這個問題而設(shè)計的。

資源庫(Repository)是系統(tǒng)中所有聚合根對象的邏輯集合,當(dāng)然這并不意味著資源庫對象會直接加載和維護(hù)系統(tǒng)中所有的聚合根實例,它只是邏輯上的集合。事實上在實現(xiàn)層面業(yè)務(wù)數(shù)據(jù)始終保存在數(shù)據(jù)庫等存儲介質(zhì)中,資源庫只是定義了針對聚合根或聚合根集合的增刪改查操接口,并且維護(hù)了底層存儲介質(zhì)中的數(shù)據(jù)記錄與聚合實體對象之間的映射關(guān)系以及聚合中各個實體之間的關(guān)聯(lián)組合邏輯。因此開發(fā)者可以通過資源庫定義的標(biāo)準(zhǔn)接口一鍵獲取到一個組裝好的聚合根對象,就好像是從一個集合中取出一個元素那樣容易。如下圖所示,與傳統(tǒng)架構(gòu)業(yè)務(wù)層代碼在不同業(yè)務(wù)環(huán)節(jié)中直接訪問數(shù)據(jù)庫的實現(xiàn)方式相比,新架構(gòu)在上層業(yè)務(wù)(領(lǐng)域服務(wù))與底層數(shù)據(jù)庫訪問層中間插入了一層資源庫,上層業(yè)務(wù)需要獲取聚合數(shù)據(jù)時,不需要自己實現(xiàn)聚合中各個業(yè)務(wù)實體的查詢、映射和組裝邏輯,而是通過資源庫提供的標(biāo)準(zhǔn)接口直接獲取已經(jīng)組裝好的聚合根對象。這種設(shè)計將聚合內(nèi)各個實體的查詢、映射及組裝邏輯收口屏蔽在資源庫內(nèi)部,讓上層業(yè)務(wù)聚焦在業(yè)務(wù)規(guī)則上,從而實現(xiàn)了數(shù)據(jù)模型與業(yè)務(wù)邏輯的分離。這樣不僅能夠避免在業(yè)務(wù)邏輯中頻繁穿插繁瑣的數(shù)據(jù)查詢和組裝邏輯,防止在運(yùn)行時出現(xiàn)重復(fù)及碎片化的數(shù)據(jù)讀寫操作,資源庫集中維護(hù)的各個聚合實體的查詢、映射和關(guān)聯(lián)邏輯也是一份重要的業(yè)務(wù)知識,能夠輔助開發(fā)者快速建立對完整數(shù)據(jù)模型的全景認(rèn)知。

wKgZomc7BJuAYRM2AAQV10X_Rt8558.png

聚合與資源庫的引入實現(xiàn)了數(shù)據(jù)模型與上層業(yè)務(wù)模型的分離

六邊形架構(gòu)讓代碼從業(yè)務(wù)中來到業(yè)務(wù)中去

在上一個小節(jié)我們介紹了聚合和資源庫的定義及實現(xiàn)準(zhǔn)則,這其實有些本末倒置,因為聚合和資源庫機(jī)制的關(guān)鍵不是如何實現(xiàn)聚合實體的查詢或者持久化,而是如何設(shè)計一套有價值的聚合(當(dāng)然,先了解聚合的實現(xiàn)準(zhǔn)則對理解聚合的設(shè)計準(zhǔn)則是有幫助的),而聚合的設(shè)計原則正是領(lǐng)域驅(qū)動設(shè)計思想核心理念的直接體現(xiàn)。

網(wǎng)絡(luò)上很多介紹DDD思想的文章都是從統(tǒng)一語言、領(lǐng)域、界限上下文等術(shù)語和概念開始的,但是由于中英文語境的差異和國內(nèi)外軟件開發(fā)生態(tài)的不同,這些文章很容易讓初學(xué)者陷入到各種概念的泥沼中無法自拔。然而那些概念只是領(lǐng)域驅(qū)動設(shè)計思想的外在表現(xiàn)形式,其背后的思想內(nèi)核才是我們應(yīng)該優(yōu)先掌握的內(nèi)容。領(lǐng)域驅(qū)動設(shè)計思想的核心理念就是保持業(yè)務(wù)、領(lǐng)域模型和代碼三者之間的統(tǒng)一,而六邊形架構(gòu)就是為了保障系統(tǒng)能夠達(dá)成這一目標(biāo)而設(shè)計的,它的核心邏輯在于保護(hù)領(lǐng)域模型。

六邊形架構(gòu)實際上是在分層架構(gòu)的基礎(chǔ)上升級而來。領(lǐng)域驅(qū)動設(shè)計思想作為一種設(shè)計的指導(dǎo)思想其實并不會限制使用某種特定的架構(gòu),在傳統(tǒng)的分層架構(gòu)上也可以實現(xiàn)領(lǐng)域驅(qū)動設(shè)計思想的落地。下圖中最左側(cè)給出了應(yīng)用了領(lǐng)域驅(qū)動設(shè)計思想之后的分層架構(gòu),它看上去就是將傳統(tǒng)三層架構(gòu)中的業(yè)務(wù)層拆分為應(yīng)用層與領(lǐng)域?qū)?。其中領(lǐng)域?qū)迂?fù)責(zé)維護(hù)領(lǐng)域模型,所謂的領(lǐng)域模型就是由當(dāng)前領(lǐng)域內(nèi)的全部聚合、資源庫以及運(yùn)行在這些聚合實體上領(lǐng)域能力和領(lǐng)域服務(wù)構(gòu)成的;應(yīng)用層則從業(yè)務(wù)視角定義了系統(tǒng)應(yīng)該對外提供多少服務(wù)(也就是所謂用例(use case)和用戶故事(user story),如果你特別執(zhí)著于那些術(shù)語的話),這些服務(wù)接口最終都會調(diào)用領(lǐng)域?qū)又械念I(lǐng)域服務(wù)執(zhí)行器來實現(xiàn)接口功能;應(yīng)用層關(guān)注的是在系統(tǒng)應(yīng)該對外提供哪些服務(wù),但是并不關(guān)心這些服務(wù)的請求來源是RPC調(diào)用還是MQ通知,這是用戶接口層的職責(zé),它負(fù)責(zé)根據(jù)與調(diào)用方達(dá)成的約定將應(yīng)用層接口暴露給不同的接口協(xié)議:Http、RPC、MQ、事件通知等等;基礎(chǔ)設(shè)施層則負(fù)責(zé)維護(hù)系統(tǒng)中使用的眾多中間件、工具以及底層存儲介質(zhì)的訪問邏輯。

wKgaomc7BJyAEPELAAH8YarQCrE467.png

多層架構(gòu)向六邊形架構(gòu)的演進(jìn)歷程

通過領(lǐng)域?qū)雍蛻?yīng)用層的抽象,我們讓分層架構(gòu)具備了實施領(lǐng)域驅(qū)動設(shè)計思想的可能。但是分層架構(gòu)天然會引導(dǎo)開發(fā)者自上而下地以數(shù)據(jù)流的視角審視系統(tǒng)。而人腦的天性使我們更容易關(guān)注流程的始末,而容易輕視流程的中間環(huán)節(jié)。因此分層架構(gòu)容易讓開發(fā)者更多的關(guān)注底層數(shù)據(jù)的存儲邏輯,進(jìn)而再次陷入面向數(shù)據(jù)庫編程的思維,設(shè)計出與實際業(yè)務(wù)脫節(jié)的領(lǐng)域模型。當(dāng)然,我們自然可以通過不斷的宣貫和世界觀輸出來呼吁開發(fā)者緊貼業(yè)務(wù)實際設(shè)計代碼實體,避免在需求伊始就陷入到底層庫表結(jié)構(gòu)中無法自拔,但我們還是希望找到一種能夠在架構(gòu)模式上就凸顯領(lǐng)域模型的重要性,引導(dǎo)開發(fā)者從業(yè)務(wù)實際出發(fā)設(shè)計領(lǐng)域模型的架構(gòu),這就是六邊形架構(gòu)誕生的背景。在對六邊形架構(gòu)進(jìn)行詳細(xì)介紹之前,我們先回過頭來再次審視分層架構(gòu)中用戶接口層和基礎(chǔ)設(shè)施層的職責(zé)差異,這對理解六邊形架構(gòu)的本質(zhì)十分重要:用戶接口層將系統(tǒng)服務(wù)暴露成不同的協(xié)議接口,因此其內(nèi)部的代碼主要在執(zhí)行接口參數(shù)轉(zhuǎn)換和應(yīng)用層接口調(diào)用的邏輯;在領(lǐng)域服務(wù)返回處理結(jié)果之后,用戶接口層還需要將領(lǐng)域服務(wù)返回的響應(yīng)結(jié)果包裝成符合對外接口協(xié)議的響應(yīng)對象。而基礎(chǔ)設(shè)施層主要維護(hù)的是底層數(shù)據(jù)的訪問邏輯,似乎與用戶接口層的職責(zé)千差萬別。但是如果我們把數(shù)據(jù)庫看做是一個特殊的外部服務(wù),基礎(chǔ)設(shè)施層的代碼執(zhí)行邏輯就與用戶接口層幾乎一致了:基礎(chǔ)設(shè)施層負(fù)責(zé)的就是聚合實體與底層存儲PO對象之間的轉(zhuǎn)換及存儲介質(zhì)數(shù)據(jù)傳輸協(xié)議的調(diào)用。我們不難發(fā)現(xiàn)用戶接口層與基礎(chǔ)設(shè)施層都在針對領(lǐng)域模型做防腐處理,從這個視角看,用戶接口層與基礎(chǔ)設(shè)施層在架構(gòu)中的地位是相同的。因此,如上圖中間位置的架構(gòu)圖所示,在架構(gòu)模式上我們嘗試將用戶接口層與基礎(chǔ)設(shè)施層“掰”到一個同一個層級中合為一體,于是我們就能得到一個六邊形的對稱架構(gòu)(從上圖的呈現(xiàn)方式上看,將用戶接口層與基礎(chǔ)設(shè)施層合并后可能還需要再旋轉(zhuǎn)45°才能呈現(xiàn)出與上圖右側(cè)一致的六邊形架構(gòu))。

六邊形架構(gòu)本質(zhì)上還是一個分層架構(gòu),只是在呈現(xiàn)方式上(注意不是實現(xiàn)方式)將用戶接口層與基礎(chǔ)設(shè)施層合二為一,讓他們共同作為防腐層保護(hù)位于架構(gòu)中心的領(lǐng)域模型不被調(diào)用方的請求協(xié)議以及底層數(shù)據(jù)庫的特殊設(shè)計所污染。而人腦天然具備的找中心的特性能夠讓開發(fā)者將更多的注意力放到位于架構(gòu)中心的領(lǐng)域模型上,暫時忘記底層數(shù)據(jù)庫的存儲規(guī)則,進(jìn)而能夠緊貼業(yè)務(wù)實際設(shè)計聚合中的各個實體及值對象,讓代碼直接表達(dá)業(yè)務(wù)規(guī)則,最后通過資源庫實現(xiàn)聚合實體與底層存儲介質(zhì)PO對象之間的轉(zhuǎn)化。

下圖給出了一個廣告投放業(yè)務(wù)中聚合實體與底層庫表結(jié)構(gòu)設(shè)計的例子,投放系統(tǒng)作為一個業(yè)務(wù)集成平臺需要不斷地對接各種垂直業(yè)務(wù)系統(tǒng),在廣告物料中也需要不斷集成各類業(yè)務(wù)實體,這些數(shù)據(jù)也需要保存到底層的廣告物料數(shù)據(jù)中。在設(shè)計單元聚合時,我們會緊貼業(yè)務(wù)實際為各類外部關(guān)聯(lián)對象定義含義明確的聚合實體:商品庫(ProductCategory)、抖音賬號信息(AweneAccount)、展示錨點信息(AnchorInfo)、直播信息(LiveInfo)等,從而確保領(lǐng)域模型與實際業(yè)務(wù)的統(tǒng)一。但是在設(shè)計底層庫表結(jié)構(gòu)時為了避免庫表結(jié)構(gòu)膨脹以及表字段稀疏化,我們采用了通用化的存儲結(jié)構(gòu):綁定到廣告物料上的不同外部業(yè)務(wù)對象都保存到同一張外部關(guān)聯(lián)對象表中,該表中的字段采用泛化設(shè)計,不與任何一種特定的外部業(yè)務(wù)對象相綁定,而是通過type字段確定本條記錄對應(yīng)的外部對象類型以及表中其他字段的實際業(yè)務(wù)含義,業(yè)務(wù)層對這些外部業(yè)務(wù)對象讀寫操作都要通過資源庫集中維護(hù)的底層數(shù)據(jù)記錄與領(lǐng)域?qū)泳酆蠈嶓w之間的映射邏輯做轉(zhuǎn)換。需要特別說明的是,在新架構(gòu)中我們將PICASO框架中的拓展點機(jī)制延伸到了基礎(chǔ)設(shè)施層,引入了模型管理拓展點(Model Manage Extension, MME)來實現(xiàn)聚合組裝主流程與底層數(shù)據(jù)對象轉(zhuǎn)換邏輯的解耦。在本例中,我們將外部關(guān)聯(lián)對象表對應(yīng)的PO對象與領(lǐng)域?qū)泳酆蠈嶓w之間的映射邏輯抽離成了一個模型管理拓展點,以外部關(guān)聯(lián)對象類型作為路由鍵,通過PICASO框架的通用可執(zhí)行實體發(fā)現(xiàn)與路由機(jī)制實現(xiàn)具體拓展點實現(xiàn)的自動定位??梢钥闯鲑Y源庫的存在不僅讓開發(fā)者可以聚焦業(yè)務(wù)實際設(shè)計領(lǐng)域模型,讓代碼直接反映業(yè)務(wù)實際,同時還讓通用化的底層數(shù)據(jù)存儲結(jié)構(gòu)得到更加廣泛的應(yīng):庫表結(jié)構(gòu)的通用化設(shè)計雖然能夠解決數(shù)據(jù)庫模型膨脹的問題,但是也的確降低了數(shù)據(jù)模型對業(yè)務(wù)的表達(dá)能力,而且如果讓上層業(yè)務(wù)直接操作這些庫表,勢必會造成代碼邏輯不明、編碼繁瑣的問題。然而資源庫卻通過收口底層數(shù)據(jù)對象與上層業(yè)務(wù)實體之間的轉(zhuǎn)化邏輯很好地解決了這些問題,讓我們在采納這種通用化的數(shù)據(jù)存儲結(jié)構(gòu)設(shè)計時少了很多顧慮。

wKgZomc7BKCACZ7nAAJtThWOwI0104.png

底層通用化存儲結(jié)構(gòu)(K-V模式)在資源庫中通過模型管理拓展點被映射為領(lǐng)域模型中業(yè)務(wù)含義明確的實體對象

在框架實現(xiàn)層面六邊形架構(gòu)與分層架構(gòu)其實并沒有太多的差異,頂多就是在六邊形中領(lǐng)域模型僅負(fù)責(zé)定義資源庫接口,而將資源庫的實現(xiàn)放到了基礎(chǔ)設(shè)施層中。在使用了Spring等控制反轉(zhuǎn)容器的項目中,一些宣稱采用了分層架構(gòu)的系統(tǒng)可能已經(jīng)在無意間實現(xiàn)了六邊形架構(gòu)了。那么六邊形架構(gòu)的意義又是什么呢?我們在前文中曾引用了IEEE對“架構(gòu)”的定義:組織、組件以及指導(dǎo)思想,然而很多時候我們都忽略了指導(dǎo)思想對軟件開發(fā)行為的重要影響。一個好的架構(gòu)一定是包涵人性的,架構(gòu)思想直接決定了開發(fā)者分析業(yè)務(wù)的世界觀和方法論??蚣苤皇禽o助工具,基于框架思想對業(yè)務(wù)進(jìn)行抽象和設(shè)計而產(chǎn)出的代碼才是一個軟件系統(tǒng)的主要構(gòu)成部分。即使采用相同的框架,在不同架構(gòu)思想的引導(dǎo)之下,系統(tǒng)中的業(yè)務(wù)代碼也可能會走向全然不同的迭代路線。而六邊形架構(gòu)與分層架構(gòu)的差異正體現(xiàn)在二者對開發(fā)行為的指導(dǎo)思想上,六邊形架構(gòu)以領(lǐng)域模型為中心,引導(dǎo)開發(fā)者始終緊貼業(yè)務(wù)實際進(jìn)行模型設(shè)計。它與PICASO框架相輔相成,讓系統(tǒng)在應(yīng)對高復(fù)雜度業(yè)務(wù)時依然能保持對業(yè)務(wù)規(guī)則的清晰表達(dá)。

聲明式數(shù)據(jù)操作既要規(guī)范又要靈活

如前文所述,一個聚合會包含業(yè)務(wù)子域內(nèi)的全部實體與值對象,資源庫會維護(hù)這些業(yè)務(wù)實體的查詢、映射及組裝邏輯。但是并不是每一個領(lǐng)域服務(wù)都會用到聚合中的全部實體,如果每次獲取聚合根時都將聚合內(nèi)所有實體都查詢出來,勢必會造成極大性能損耗。然而資源庫作為基礎(chǔ)設(shè)施層中的底層組件,也不可能為每一個領(lǐng)域服務(wù)提供專用的聚合查詢或者數(shù)據(jù)持久化接口。為了調(diào)和這兩者之間的矛盾,我們在資源庫實現(xiàn)上采用了聲明式數(shù)據(jù)操作設(shè)計。

在《能力編排領(lǐng)域特定語言》章節(jié)中我們已經(jīng)對聲明式編程有了比較具象的體會,但聲明式編程并不限定于領(lǐng)域特定語言這一種實現(xiàn)形式,在資源庫的接口設(shè)計上我們采用了一種更加樸素的聲明式編程實現(xiàn)方法。如下圖所示,我們計劃聚合的查詢以及創(chuàng)意聚合的更新接口為例來闡述聲明式數(shù)據(jù)操作的設(shè)計細(xì)節(jié)。當(dāng)需要從資源庫中獲取聚合根對象時,默認(rèn)情況下資源庫庫會自動查詢聚合下所有子實體并完成組裝,但是開發(fā)者可以再提供一個額外的輔助查詢參數(shù)DomainQuery,并通過該對象的addSubEntityQuery方法聲明希望獲取哪些特定的子實體。默認(rèn)情況下資源庫會根據(jù)聚合根對應(yīng)的主表記錄ID做子實體查詢,如果開發(fā)者希望在子實體查詢時使用額外的匹配條件,則可以通過addSubEntityQuery方法同時聲明希望獲取的子實體類型以及執(zhí)行該類型子實體查詢時使用的額外匹配參數(shù),這一特性在與聚合根存在一對多的子實體查詢場景中會發(fā)揮重要作用。上述聲明式接口在資源庫實現(xiàn)中并不復(fù)雜,只需要在執(zhí)行每個子實體的查詢或修改操作之前,判斷一下上層調(diào)用方是否限定了僅對部分子實體進(jìn)行操作,如果設(shè)置了則進(jìn)一步判斷當(dāng)前子實體是否在上層調(diào)用方指定的子實體范圍之內(nèi),如果當(dāng)前子實體業(yè)務(wù)上允許調(diào)用方指定自定義的匹配邏輯,還需要嘗試從輔助查詢/修改參數(shù)中提取調(diào)用方為該子實體指定的自定義匹配邏輯。需要特別說明的是,我們不推薦在新架構(gòu)內(nèi)部各個模塊之間采用任何形式的黑盒調(diào)用模式,不管是能力編排還是資源庫調(diào)用,上層調(diào)用方都有責(zé)任和義務(wù)理清底層模塊的內(nèi)部執(zhí)行邏輯,確保編排配置或調(diào)用參數(shù)設(shè)置正確。

wKgaomc7BKGAOfQ5AAWhkCGo63w963.png

基于資源庫進(jìn)行聲明式聚合數(shù)據(jù)查詢

四、新架構(gòu)下的業(yè)務(wù)開發(fā)流程速覽

業(yè)務(wù)建模

新架構(gòu)作為領(lǐng)域驅(qū)動設(shè)計思想的戰(zhàn)術(shù)落地框架,能夠讓其發(fā)揮出最大價值的前提是對業(yè)務(wù)進(jìn)行良好的領(lǐng)域建模,下圖給出了廣告投放平臺中競價及合約廣告投放服務(wù)的業(yè)務(wù)架構(gòu)。由于本文的主題聚焦在如何腳踏實地地實現(xiàn)一個基于領(lǐng)域驅(qū)動設(shè)計思想而設(shè)計的系統(tǒng),因此關(guān)于DDD思想戰(zhàn)略設(shè)計相關(guān)的內(nèi)容本文將不做過多闡述,相關(guān)內(nèi)容我們將在后續(xù)《領(lǐng)域驅(qū)動設(shè)計與PICASO框架》一文中進(jìn)行詳細(xì)闡述。需要說明的是,圖中的聚合服務(wù)層、領(lǐng)域能力層及數(shù)據(jù)模型層共同構(gòu)成了廣告投放平臺和核心領(lǐng)域模型。

wKgZomc7BKOAcvHsAArWhlqeZiw375.png

競價及合約廣告投放服務(wù)分層業(yè)務(wù)架構(gòu)

工程結(jié)構(gòu)

下圖給出了新架構(gòu)思想落地時工程結(jié)構(gòu)的最佳實踐案例,工程中各module的職責(zé)以及與上文中業(yè)務(wù)分層架構(gòu)圖中各層的對應(yīng)關(guān)系依次為:

rtbad-framework:框架包,承載架構(gòu)標(biāo)準(zhǔn)規(guī)約及分層架構(gòu)圖中基礎(chǔ)設(shè)施層中的各項基礎(chǔ)組件。

rtbad-module/rtbad-support-module:模型包,對應(yīng)的是分層架構(gòu)圖中的模型層,承載領(lǐng)域模型中各個聚合及聚合實體對象的定義,另外用于實現(xiàn)底層存儲介質(zhì)中持久化數(shù)據(jù)與領(lǐng)域模型中實體對象映射邏輯的資源庫也定義在此類module中。其中support-module定義的是支撐域中的實體對象,該module下會按照業(yè)務(wù)子域進(jìn)一步進(jìn)行子module劃分。

rtbad-event:事件包,屬于特殊的數(shù)據(jù)模型層,承載這領(lǐng)域事件對象的定義,新架構(gòu)底層融合了事件驅(qū)動架構(gòu)(篇幅的關(guān)系我們會在其他文章中進(jìn)行專門地介紹),因此事件體系建設(shè)也被納入到統(tǒng)一建模的工作中。

rtbad-composite/rtbad-support-compoaite:聚合服務(wù)包,對應(yīng)分層架構(gòu)中的領(lǐng)域能力及聚合服務(wù)層,承載領(lǐng)域能力及領(lǐng)域服務(wù)執(zhí)行器的實現(xiàn),其中support-Composite用于承載支撐域領(lǐng)域能力及領(lǐng)域服務(wù)執(zhí)行器的實現(xiàn),該module下會按照業(yè)務(wù)子域進(jìn)一步進(jìn)行子module劃分。

rtbad-app:部署層,內(nèi)部分為不同的子module,每一個子module對應(yīng)一個部署應(yīng)用,其實現(xiàn)邏輯就是根據(jù)應(yīng)用職責(zé)組裝底層各個子module,進(jìn)而實現(xiàn)不同應(yīng)用下能力及模型共享。

rtbad-api:對外接口SDK包,承載了對外提供的API接口定義。

wKgaomc7BKWASYayAAaI6h5NmFQ064.png

代碼開發(fā)流程示例

以下內(nèi)容以一次計劃新建請求為例,通過從流量入口到數(shù)據(jù)落庫的完整請求流程展示使用新架構(gòu)實現(xiàn)業(yè)務(wù)需求的全部過程,我們希望通過這個例子讓大家建立對新架構(gòu)的直觀感受。

領(lǐng)域服務(wù)統(tǒng)一入口

領(lǐng)域服務(wù)統(tǒng)一入口(Domain Service Faced)的作用是為HTTP、RPC、MQ等上層不同的請求流量暴露統(tǒng)一的服務(wù)入口。他將同一個業(yè)務(wù)子域內(nèi)領(lǐng)域服務(wù)執(zhí)行器集中到一起,便于流量介入層調(diào)用,同時也可以集中進(jìn)行方法性能監(jiān)控、調(diào)用量統(tǒng)計、請求日志記錄等通用功能。

wKgZomc7BKaABFDeAAJdCw-cgPo681.png

領(lǐng)域服務(wù)門面執(zhí)行器

領(lǐng)域服務(wù)統(tǒng)一入口引用的是各個領(lǐng)域服務(wù)門面執(zhí)行器,它負(fù)責(zé)從參數(shù)中提取業(yè)務(wù)標(biāo)識并定位到具體的領(lǐng)域服務(wù)實例。如下圖所示,所有具體的領(lǐng)域服務(wù)實例都繼承自領(lǐng)域服務(wù)門面執(zhí)行器,請求到來時領(lǐng)域服務(wù)門面先通過generateRouteKey方法從當(dāng)前參數(shù)中提取出本次請求的業(yè)務(wù)標(biāo)識,然后與各個領(lǐng)域服務(wù)實例能夠支持的業(yè)務(wù)標(biāo)識做匹配,從而定位到應(yīng)該處理本次請求的服務(wù)實例。

wKgaomc7BKeAURU4AAPIDEJWJeA994.png

領(lǐng)域服務(wù)實例執(zhí)行器

能力執(zhí)行圖的作用是定義業(yè)務(wù)執(zhí)行流程,框架提供了豐富的API及大量的語法糖和默認(rèn)規(guī)則,配合鏈?zhǔn)秸{(diào)用的風(fēng)格,在支持靈活編排的同時減少了開發(fā)者的負(fù)擔(dān)。

wKgZomc7BKuARYsFAB9Nis5QbY0534.png

領(lǐng)域能力門面執(zhí)行器

負(fù)責(zé)從參數(shù)中提取場景標(biāo)識并定位具體的領(lǐng)域能力實例。

wKgaomc7BKyAGQZGAAI1IMWlhzQ653.png

領(lǐng)域能力實例執(zhí)行器

能力內(nèi)主要負(fù)責(zé)實現(xiàn)具體的業(yè)務(wù)規(guī)則,并對聚合根中相關(guān)屬性進(jìn)行設(shè)置/修改,一個領(lǐng)域服務(wù)編排的所有領(lǐng)域能力執(zhí)行完成之后,就能獲取一個完整的、全新的聚合根對象。

wKgZomc7BK2AdRd4AAEYXbj3iwU805.png

拓展點

拓展點的作用是作為任意維度的差異點補(bǔ)充分離工具。需要注意的是在新架構(gòu)中拓展點不是唯一的差異分離工具,在通用對象發(fā)現(xiàn)及路由機(jī)制下,領(lǐng)域服務(wù)、領(lǐng)域能力和拓展點都在不同維度上起著差異點分離的作用。相對與前兩者拓展點更加靈活,通常用來承接領(lǐng)域服務(wù)及能力自身路由維度之外的邏輯差異。比如出價設(shè)置能力已經(jīng)按照出價方式做了能力維度的差異分離,但是在相同的出價方式下,京準(zhǔn)通與流量貨幣化還存在一些細(xì)微的邏輯差異,那么這個時候就可以在該能力實例中通過拓展點來補(bǔ)充實現(xiàn)平臺維度的差異邏輯分離。

wKgaomc7BK-Aed3VAAJBxg6hF6c374.png

上面的例子是在計劃名稱設(shè)置能力中獲取不同產(chǎn)品線計劃名稱長度上下限配置的拓展點,計劃名稱設(shè)置能力自身按照產(chǎn)品線類型進(jìn)行業(yè)務(wù)模式路由,但是站內(nèi)計劃名稱設(shè)置主要業(yè)務(wù)邏輯基本一致,僅在部分校驗邏輯上不同產(chǎn)品線有各自的要求,此時就可以使用一個名稱設(shè)置能力實例服務(wù)所有的站內(nèi)產(chǎn)品線,定義名稱設(shè)置主體業(yè)務(wù)規(guī)則,而把不同產(chǎn)品線的細(xì)微差異抽象成一個拓展點接口。

資源庫

上層業(yè)務(wù)通過聲明式接口實現(xiàn)聚合實體讀寫操作。

wKgZomc7BLCAb0g1AAIybkGeybI247.png

wKgaomc7BLGAKR8WAAHSu84Tghg241.png


五、結(jié)語

很多同學(xué)對業(yè)務(wù)開發(fā)一直存在一種偏見,認(rèn)為業(yè)務(wù)開發(fā)很簡單,甚至有業(yè)務(wù)開發(fā)同學(xué)自己也時常調(diào)侃自己是CRUD工程師,認(rèn)為自己的工作沒什么技術(shù)含量。但其實業(yè)務(wù)開發(fā)一點都不簡單,只是過去我們一直把它做簡單了。如今業(yè)務(wù)形態(tài)復(fù)雜多變,商機(jī)轉(zhuǎn)瞬即逝,如何在快速變化著的復(fù)雜業(yè)務(wù)需求中維持系統(tǒng)健康、穩(wěn)定、持續(xù)迭代,要做到這一點的難度其實一點都不比底層技術(shù)差。程序員應(yīng)該是一門充滿學(xué)術(shù)性與創(chuàng)造性的職業(yè),我們唯有堅守初心,不斷夯實自己的技術(shù)功底,沉淀提升抽象與建模能力,培養(yǎng)自己的系統(tǒng)化思維,不斷學(xué)習(xí)精進(jìn),追求極致編碼,這才是我們無法被AI替代的核心競爭力與價值所在。

有同學(xué)可能會關(guān)注本文介紹PICASO框架未來是否可以對外共享,關(guān)于這個問題我們的答案是肯定的。在PICASO框架開發(fā)之初我們的野心就沒有局限在京東廣告投放平臺這一個業(yè)務(wù)場景上,而是希望它可以走出廣告部,甚至走出京東,接受全社會開發(fā)者的檢驗,成為一個被業(yè)界認(rèn)可的復(fù)雜B端業(yè)務(wù)通用解決方案。然而作為一個一線業(yè)務(wù)團(tuán)隊,快速支持業(yè)務(wù)方需求是我們的首要職責(zé)。盡管我們在進(jìn)行PICASO底層框架開發(fā)時盡力維持與具體業(yè)務(wù)分離的開發(fā)原則,但是在需求排期比較緊張的時候,為了快速支持業(yè)務(wù)需求的開發(fā),還是存在將與廣告投放業(yè)務(wù)相關(guān)的邏輯耦合到了PICASO框架底層源碼中的情況。如果大家有興趣閱讀或者試玩PICASO的源碼,請聯(lián)系筆者為您開放一個示例版本的框架源碼權(quán)限,該版本框架的功能與筆者負(fù)責(zé)的線上系統(tǒng)使用的框架功能完全相同,只是去除了廣告投放業(yè)務(wù)相關(guān)的邏輯。您可以在該版本源碼上執(zhí)行任意的功能及性能測試,但是在我們對外發(fā)布正式的共享版本之前,我們并不建議您直接將該示例版本的源碼應(yīng)用到線上系統(tǒng)。目前框架功能已趨于穩(wěn)定,我們也將PICASO框架的開源化改造提上日程,也歡迎感興趣或者有開源社區(qū)維護(hù)經(jīng)驗的同學(xué)一起交流共建。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)據(jù)模型
    +關(guān)注

    關(guān)注

    0

    文章

    52

    瀏覽量

    10179
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4900

    瀏覽量

    70733
  • 架構(gòu)
    +關(guān)注

    關(guān)注

    1

    文章

    528

    瀏覽量

    25984
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    京東廣告投放平臺整潔架構(gòu)演進(jìn)之路

    設(shè)計思想到落地框架都進(jìn)行了徹底的革新,涉及內(nèi)容比較多,因此我們希望通過一系列文章循序漸進(jìn)地闡述本次架構(gòu)升級的始末。新架構(gòu)并不是一日而成的,而是經(jīng)過了多次架構(gòu)升級的演進(jìn),因此我們將本文作
    的頭像 發(fā)表于 09-18 10:26 ?1225次閱讀
    京東廣告投放平臺<b class='flag-5'>整潔</b><b class='flag-5'>架構(gòu)</b>演進(jìn)之路

    【「大模型時代的基礎(chǔ)架構(gòu)」閱讀體驗】+ 未知領(lǐng)域的感受

    再到大模型云平臺的構(gòu)建,此書都有提及和講解,循序漸進(jìn),讓讀者可以由點及面,由面到體的來認(rèn)識大數(shù)據(jù)模型的體系架構(gòu)。 前言中,作者通過提出幾個問題來引導(dǎo)讀者閱讀思考——分布式AI計算依賴哪些硬件特性
    發(fā)表于 10-08 10:40

    大學(xué)生循序漸進(jìn)的學(xué)習(xí)歷程如何開啟

    本帖最后由 gk320830 于 2015-3-9 01:50 編輯 作為一個本科電子專業(yè)大二的學(xué)生,剛接觸專業(yè)課的我,對于如何更好的掌握專業(yè)知識,同時從更多的渠道豐富自己感到有些茫然。剛接觸電子專業(yè)知識感到十分生澀。如何開啟我大學(xué)的電子之路,更好借鑒大家的經(jīng)驗,達(dá)到博而精的效果請教各位前輩。我會時刻駐足論壇,誠懇接受諸位的建議!謝謝!
    發(fā)表于 09-29 20:20

    關(guān)于電子電路學(xué)習(xí)的

    請問電子電路有哪些經(jīng)典的教材?以后想學(xué)習(xí)模擬電路的設(shè)計,應(yīng)該按照什么樣的方式循序漸進(jìn)的學(xué)習(xí)?麻煩推薦一些心得或者參考教材~
    發(fā)表于 07-01 15:59

    《鴻蒙設(shè)備學(xué)習(xí)菜鳥指南》之 【五、搭建開發(fā)環(huán)境】

    ,然后再配置復(fù)雜的方案,循序漸進(jìn)。最簡化方案:? Windows系統(tǒng):安裝VSCode就好了,就是這么簡單? MacOS系統(tǒng):同上? Linux系統(tǒng)
    發(fā)表于 10-30 13:59

    如何系統(tǒng)的學(xué)習(xí)嵌入式?

    都說嵌入式很難,即使去嵌入式培訓(xùn)機(jī)構(gòu)做系統(tǒng)訓(xùn)練,其實只是沒有掌握正確的學(xué)習(xí)嵌入式的方法,學(xué)習(xí)講究的是一個循序漸進(jìn)的過程,誰也不能一口吃出一個大胖子,從基礎(chǔ)到專業(yè),從簡單到高深,下面達(dá)內(nèi)講解一下系統(tǒng)學(xué)習(xí)嵌入式培訓(xùn)的基本步驟:
    發(fā)表于 03-09 06:23

    【電子書】Linux命令速查手冊PDF

    `本書以目前最熱門的Linux發(fā)行版——Ubuntu為平臺,從零開始循序漸進(jìn)地介紹了Linux系統(tǒng)的基礎(chǔ)知識和實用操作。`
    發(fā)表于 04-02 14:05

    Linux嵌入式學(xué)習(xí)過程 精選資料推薦

    Linux嵌入式學(xué)習(xí)過程循序漸進(jìn)學(xué)習(xí)嵌入式開發(fā)技術(shù)一、練好基本功二、嵌入式Linux應(yīng)用開發(fā)誤區(qū)一、全身投入學(xué)習(xí)桌面或服務(wù)器版本linux系統(tǒng)誤區(qū)二、直接閱讀linux內(nèi)核源代碼如何正確的嵌入式
    發(fā)表于 07-19 09:07

    VB6循序漸進(jìn)教程

    VB6循序漸進(jìn)教程
    發(fā)表于 02-06 16:52 ?50次下載

    操作系統(tǒng)原理及應(yīng)用(linux)_王紅

    本書在內(nèi)容編排上力求由淺入深,循序漸進(jìn),舉一反三,突出重點,通俗易懂。采用模塊化結(jié)構(gòu),兼顧不同層次的需求。
    發(fā)表于 12-12 14:58 ?0次下載
    操作<b class='flag-5'>系統(tǒng)</b>原理及應(yīng)用(linux)_王紅

    PERL編程24學(xué)時教程(完整版)

    perl語言的學(xué)習(xí)資料,由淺入深。循序漸進(jìn)
    發(fā)表于 11-17 10:21 ?0次下載

    Linux系統(tǒng)的保護(hù),四個步驟循序漸進(jìn)有奇效

    如今,互連設(shè)備比以往更易受到安全威脅,但是錯綜復(fù)雜的平臺讓開發(fā)人員很難顧及到每一個潛在的入口點。為幫助減少基于 Linux 的系統(tǒng)所遭受到的威脅,英特爾?公司旗下的 Wind River 提出了 OEM 應(yīng)該遵循的四步流程:監(jiān)控、評估、通知和修復(fù)。
    發(fā)表于 09-11 16:32 ?8次下載
    Linux<b class='flag-5'>系統(tǒng)</b>的保護(hù),四個步驟<b class='flag-5'>循序漸進(jìn)</b>有奇效

    2021 年問世!蘋果自研 5G 芯片

    預(yù)計將先從 iPad 等「次級」產(chǎn)品循序漸進(jìn)采用自家芯片。
    的頭像 發(fā)表于 08-01 17:48 ?2992次閱讀

    人工智能在發(fā)展的路上怎樣避免陷阱

    為了更好的推動其發(fā)展,人工智能的落地與應(yīng)用必然會是一個循序漸進(jìn)的過程。
    發(fā)表于 11-12 14:31 ?660次閱讀

    電力電子技術(shù)從基礎(chǔ)到運(yùn)用

    想學(xué)習(xí)電力電子技術(shù)的 這本書可以啊 從簡單到基礎(chǔ) 循序漸進(jìn)
    發(fā)表于 03-11 14:54 ?0次下載