摘要:而只需要調用對象完成業務邏輯即可。領域建模的好處面向對象封裝的相關操作都封裝在上,提高了內聚性和可重用性。對于這樣簡單的場景,這個建模就差不多了。這就是模型的統一。其功能性及說明性急速增強,而復雜性卻隨之消失。
你還在用面向對象的語言寫面向過程的代碼嗎?你是否正在被復雜的業務邏輯折磨?是否有時覺得應用開發沒意思、沒挑戰、技術含量低?其實,應用開發一點都不簡單,也不無聊,業務的變化比底層基礎實施的變化要多得多,封裝這些變化需要很好的業務理解力,抽象能力和建模能力。
今天我們邀請阿里高級技術專家張建飛,一起來聊聊為什么需要領域建模,什么是好的模型,又該如何搭建。
為什么要領域建模?軟件的世界里沒有銀彈,是用事務腳本還是領域模型沒有對錯之分,關鍵看是否合適。實際上,CQRS就是對事務腳本和領域模型兩種模式的綜合,因為對于Query和報表的場景,使用領域模型往往會把簡單的事情弄復雜,此時完全可以用奧卡姆剃刀把領域層剃掉,直接訪問Infrastructure。我個人也是堅決反對過度設計的,因此對于簡單業務場景,我強力建議還是使用事務腳本,其優點是簡單、直觀、易上手。但對于復雜的業務場景,你再這么玩就不行了,因為一旦業務變得復雜,事務腳本就很難應對,容易造成代碼的“一鍋粥”,系統的腐化速度和復雜性呈指數級上升。
目前比較有效的治理辦法就是領域建模,因為領域模型是面向對象的,在封裝業務邏輯的同時,提升了對象的內聚性和重用性,因為使用了通用語言(Ubiquitous Language),使得隱藏的業務邏輯得到顯性化表達,使得復雜性治理成為可能。talk is cheap,直接上一個銀行轉賬的例子,對事務腳本和領域模型進行比較,孰優孰劣一目了然。
銀行轉賬事務腳本實現在事務腳本的實現中,關于在兩個賬號之間轉賬的領域業務邏輯都被寫在了MoneyTransferService的實現里面了,而Account僅僅是getters和setters的數據結構,也就是我們說的貧血模式。
上面的代碼大家看起來應該比較眼熟,因為目前大部分系統都是這么寫的。需求評審完,工程師畫幾張UML圖完成設計,就開始向上面這樣懟業務代碼了,這樣寫基本不用太費腦,完全是面向過程的代碼風格。有些同學可能會說,我這樣寫也可以實現系統功能啊,還是那句話“just because you can, doesn"t mean you should”。說句不好聽的,正是有這么多“沒有追求”、“不求上進”的碼農才造成了應用系統的混亂、敗壞了應用開發的名聲。這也是為什么很多應用開發工程師覺得工作沒意思,技術含量低,覺得整天就是寫if-else的業務邏輯代碼,系統又爛,工作繁瑣、無聊、沒有成長、沒有成就感,所以轉向去做中間件啊,去寫JDK啊,覺得那個NB。
實際上,應用開發一點都不簡單也不無聊,業務的變化比底層Infrastructure的變化要多得多,解決的難度也絲毫不比寫底層代碼容易,只是很多人選擇了用無聊的方式去做。其實我們是有辦法做的更優雅的,這種優雅的方式就是領域建模,唯有掌握了這種優雅你才能實現從工程師向應用架構的轉型。同樣的業務邏輯,接下來就讓我們看一下用DDD是怎么做的。
銀行轉賬領域模型實現如果用DDD的方式實現,Account實體除了賬號屬性之外,還包含了行為和業務邏輯,比如debit( )和credit( )方法。
而且透支策略OverdraftPolicy也不僅僅是一個Enum了,而是被抽象成包含了業務規則并采用了策略模式的對象。
而Domain Service只需要調用Domain Entity對象完成業務邏輯即可。
通過上面的DDD重構后,原來在事務腳本中的邏輯,被分散到Domain Service,Domain Entity和OverdraftPolicy三個滿足SOLID的對象中,在繼續閱讀之前,我建議可以自己先體會一下DDD的好處。
領域建模的好處面向對象
封裝:Account的相關操作都封裝在Account Entity上,提高了內聚性和可重用性。
多態:采用策略模式的OverdraftPolicy(多態的典型應用)提高了代碼的可擴展性。
業務語義顯性化
通用語言:“一個團隊,一種語言”,將模型作為語言的支柱。確保團隊在內部的所有交流中,代碼中,畫圖,寫東西,特別是講話的時候都要使用這種語言。例如賬號,轉賬,透支策略,這些都是非常重要的領域概念,如果這些命名都和我們日常討論以及PRD中的描述保持一致,將會極大提升代碼的可讀性,減少認知成本。
顯性化:就是將隱式的業務邏輯從一推if-else里面抽取出來,用通用語言去命名、去寫代碼、去擴展,讓其變成顯示概念,比如“透支策略”這個重要的業務概念,按照事務腳本的寫法,其含義完全淹沒在代碼邏輯中沒有突顯出來,看代碼的人自然也是一臉懵逼,而領域模型里面將其用策略模式抽象出來,不僅提高了代碼的可讀性,可擴展性也好了很多。
如何進行領域建模?建模方法
領域建模這個話題太大,關于此的長篇大論和書籍也很多,比如什么通過語法和句法深入分析法,在我看來這些方法論有些繁瑣了。好的模型應該是建立在對業務深入理解的基礎上,如果業務理解不到位,你再怎么分析句子也不可能產出好的模型。就我自己的經驗而言,建模也是一個不斷迭代的過程,所以一開始可以簡單點來,就采用兩步建模法抓住一些核心概念,然后寫一些代碼驗證一下run一下,看看順不順,如果很順滑,說明沒毛病,否則就要看看是不是需要調整一下模型,隨著項目的進行和對業務理解的不斷深入,這種迭代將持續進行。
那什么是兩步建模法呢?也就是只需要兩個步驟就能建模了,首先從User Story找名詞和動詞,然后用UML類圖畫出領域模型。是不是很簡約?簡約并不意味著簡單,對于業務架構師和系統分析師來說,見功力的地方往往就在于此。
舉個栗子,比如讓你設計一個中介系統,一個典型的User Story可能是“小明去找工作,中介說你留個電話,有工作機會我會通知你”,這里面的關鍵名詞很可能就是我們需要的領域對象,小明是求職者,電話是求職者的屬性,中介包含了中介公司,中介員工兩個關鍵對象;工作機會肯定也是關鍵領域對象;通知這個動詞暗示我們這里用觀察者模式會比較合適。然后再梳理一下領域對象之間的關系,一個求職者可以應聘多個工作機會,一個工作機會也可以被多個求職者應聘,M2M的關系,中介公司可以包含多個員工,O2M的關系。對于這樣簡單的場景,這個建模就差不多了。
當然我們的業務場景往往比這個要復雜,而且不是所有的名詞都是領域對象也可能是屬性,也不是所有的動詞都是方法也可能是領域對象,所以要具體問題具體對待,這個對待的過程需要我們有很好的業務理解力,抽象能力以及建模的經驗(知道為什么公司的job model里那么強調技術人員的業務理解力和抽象能力了吧),比如通常情況下,價格和庫存只是訂單和商品的一個屬性,但是在阿里系電商業務場景下,價格計算和庫存扣減的復雜程度可以讓你懷疑人生,因此作為電商中臺,把價格和庫存多帶帶當成一個域(Domain)去對待是很必要的。
另外,建模不是一個一次性的工作,往往隨著業務的變化以及我們對業務的理解越來越深入才能看清系統的全貌,所以迭代重構是免不了的,也就是要Agile Modelling。
模型統一和模型演化建模的過程很像盲人摸象,不同背景人用不同的視角看同一個東西,其理解也是不一樣的。比如兩個盲人都摸到大象鼻子,一個人認為是像蛇(活的能動),而另一個人認為像消防水管(可以噴水),那么他們將很難集成。雙方都無法接受對方的模型,因為那不符合自己的體驗。事實上,他們需要一個新的抽象,這個抽象需要把蛇的“活著的特性”與消防水管的“噴水功能”合并到一起,而這個抽象還應該排除先前兩個模型中一些不確切的含義和屬性,比如毒牙,或者卷起來放到消防車上去的行為。這就是模型的統一。
世界上唯一不變的就是變化,模型和代碼一樣也需要不斷的重構和精化,每一次的精化之后,開發人員應該對領域知識有了更加清晰的認識。這使得理解上的突破成為可能,之后,一系列快速的改變得到了更符合用戶需要并更加切合實際的模型。其功能性及說明性急速增強,而復雜性卻隨之消失。
這種突破需要我們對業務有更加深刻的領悟和思考,然后再加上重構的勇氣和能力,勇氣是項目工期很緊你敢不敢重構,能力是你有沒有完備的CI保證你的重構不破壞現有的業務邏輯。還是以開篇的轉賬來舉個例子,假如轉賬業務開始變的復雜,要支持現金,信用卡,支付寶,比特幣等多種通道,且沒種通道的約束不一樣,還要支持一對多的轉賬。那么你還是用一個transfer(fromAccount, toAccount)就不合適了,可能需要抽象出一個專門的領域對象Transaction,這樣才能更好的表達業務,其演化過程如下:
什么是領域服務?有些領域中的動作,它們是一些動詞,看上去卻不屬于任何對象。它們代表了領域中的一個重要的行為,所以不能忽略它們或者簡單地把它們合并到某個實體或者值對象中。當這樣的行為從領域中被識別出來時,最佳實踐是將它聲明成一個服務。這樣的對象不再擁有內置的狀態。它的作用僅僅是為領域提供相應的功能。Service往往是以一個活動來命名,而不是Entity來命名。例如開篇轉賬的例子,轉賬(transfer)這個行為是一個非常重要的領域概念,但是它是發生在兩個賬號之間的,歸屬于賬號Entity并不合適,因為一個賬號Entity沒有必要去關聯他需要轉賬的賬號Entity,這種情況下,使用MoneyTransferDomainService就比較合適了。
識別領域服務,主要看它是否滿足以下三個特征:
服務執行的操作代表了一個領域概念,這個領域概念無法自然地隸屬于一個實體或者值對象。
被執行的操作涉及到領域中的其他的對象。
操作是無狀態的。
應用服務和領域服務如何劃分?在領域建模中,我們一般將系統劃分三個大的層次,即應用層(Application Layer),領域層(Domain Layer)和基礎實施層(Infrastructure Layer),關于這三個層次的詳細內容可以參考我的另一篇SOFA框架的分層設計。可以看到在App層和Domain層都有服務(Service),這兩個Service如何劃分呢,什么樣的功能應該放在應用層,什么樣的功能應該放在領域層呢?
決定一個服務(Service)應該歸屬于哪一層是很困難的。如果所執行的操作概念上屬于應用層,那么服務就應該放到這個層。如果操作是關于領域對象的,而且確實是與領域有關的、為領域的需要服務,那么它就應該屬于領域層。總的來說,涉及到重要領域概念的行為應該放在Domain層,而其它非領域邏輯的技術代碼放在App層,例如參數的解析,上下文的組裝,調用領域服務,消息發送等。還是銀行轉賬的case為例,下圖給出了劃分的建議:
業務可視化和可配置化好的領域建模可以降低應用的復雜性,而可視化和可配置化主要是幫助大家(主要是非技術人員,比如產品,業務和客戶)直觀地了解系統和配置系統,提供了一種“code free”的解決方案,也是SaaS軟件的主要賣點。要注意的是可視化和可配置化難免會給系統增加額外的復雜度,必須慎之又慎,最好是能使可視化和配置化的邏輯與業務邏輯盡量少的耦合,否則破壞了原有的架構,把事情搞的更復雜就得不償失了。
在可擴展設計中,我已經介紹了我們SOFA架構是如何通過擴展點的設計來支撐不同業務差異化的需求的,那么可否更進一步,我們將領域的行為(也叫能力)和擴展點用可視化的方式呈現出來,并對于一些不需要編碼實現的擴展點用配置的方式去完成呢。當然是可以的,比如還是開篇轉賬的例子,對于透支策略OverdraftPolicy這個業務擴展點,新來一個業務說透支額度不能超過1000,我們可以完全結合規則引擎進行配置化完成,而不需要編碼。
所以我能想到的一種還比較優雅的方式,是通過Annotation注解的方式對領域能力和擴展點進行標注,然后在系統bootstrap階段,通過代碼掃描的方式,將這些能力點和擴展點收集起來上傳到中心服務器,然后再通過GUI的方式呈現出來,從而做到業務的可視化和可配置化。大概的示意圖如下:
有同學可能會問流程要不要可視化,這里要分清楚兩個概念,業務邏輯流和工作流,很多同學混淆了這兩個概念。業務邏輯流是響應一次用戶請求的業務處理過程,其本身就是業務邏輯,對其編排和可視化的意義并不是很大,無外乎只是把代碼邏輯可視化了,在我們的SOFA框架中,是通過擴展點和策略模式來處理業務的分支情況,而我看到我們阿里很多的內部系統將這種響應一次用戶請求的業務邏輯用很重的工作流引擎來做,美其名曰流程可編排,實質上往往是把簡單的事情復雜化了。而工作流是指完成一項任務所需要不同節點的連接,節點主要分為自動節點和人工節點,其中每個人工節點都需要用戶的參與,也就是響應一次用戶的請求,比如審批流程中的經理審批節點,CRM銷售過程的業務員的處理節點等等。
此時可以考慮使用工作流引擎,特別是當你的系統需要讓用戶自定義流程的時候,那就不得不使用可視化和可配置的工作流引擎了,除此之外,最好不要自找麻煩。當然也不排除有用的特別合適的案例,只是我還沒看見,如果有看見的同學也請告訴我一聲,一起交流學習。
本文作者:張建飛
詳情請閱讀原文
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/11842.html
摘要:而只需要調用對象完成業務邏輯即可。領域建模的好處面向對象封裝的相關操作都封裝在上,提高了內聚性和可重用性。對于這樣簡單的場景,這個建模就差不多了。這就是模型的統一。其功能性及說明性急速增強,而復雜性卻隨之消失。 showImg(https://segmentfault.com/img/bV62vN?w=677&h=393); 云棲君導讀:你還在用面向對象的語言寫面向過程的代碼嗎?你是否...
摘要:以下將分別從五大技術專場維度介紹本屆峰會的部分聯席主席與精選案例。天時間集中分享年最值得學習的個研發案例實踐。 從萬維網到物聯網,從信息傳播到人工智能,20年間軟件研發行業趨勢發生了翻天覆地的變化。大數據、云計算、AI等新興領域逐漸改變我們的生活方式,Devops、容器、深度學習、敏捷等技術方式和工作理念對軟件研發從業者提出更高要求。 由麥思博(msup)有限公司主辦的第六屆全球軟件案...
摘要:在系統正常運行時,可以變更爬蟲的配置,一旦實時監控爬蟲出現異常,可實時修正配置進行干預。從數據庫中實時讀取配置信息,響應業務層的配置請求。處理系統通過服務層,每次去取配置信息可能維護人員在實時修正及待抓取的列表進行處理。 showImg(https://segmentfault.com/img/bVLa4V?w=960&h=540); 一 ?緣起 在我工作的多家公司,有眾多的領域,如房...
摘要:由于文章內容較長,所以我把它分成兩篇小文章,在第一篇優秀架構師必須掌握的架構思維中,我會先介紹抽象分層分治和演化這四種應對復雜性的基本思維。另外,上面的算法是兩路歸并,也可以采用多路歸并,甚至是采用堆排序進行優化,但是總體分治思路沒有變化。 showImg(https://segmentfault.com/img/bVbeYpP?w=642&h=400); 介紹 架構的本質是管理復雜性...
閱讀 5738·2021-11-24 10:25
閱讀 2689·2021-11-16 11:44
閱讀 3843·2021-10-11 11:09
閱讀 3171·2021-09-02 15:41
閱讀 3256·2019-08-30 14:14
閱讀 2271·2019-08-29 14:10
閱讀 2344·2019-08-29 11:03
閱讀 1125·2019-08-26 13:47