微服务架构设计基础之领域驱动设计
背景
微服務(wù)現(xiàn)在可以說是軟件研發(fā)領(lǐng)域無人不提的話題,然而業(yè)界流行的對比多數(shù)都是所謂的Monolithic(單體應(yīng)用),而大量的系統(tǒng)在十幾年前都已經(jīng)是以SOA(面向服務(wù)架構(gòu))為基礎(chǔ)的分布式系統(tǒng)了,那么微服務(wù)作為新的架構(gòu)標(biāo)準(zhǔn)與SOA有什么差異點呢?其本質(zhì)區(qū)別在于設(shè)計原理,微服務(wù)是去中心化設(shè)計,SOA是「集成」形成中心設(shè)計;
另外,筆者認(rèn)為以下幾點并不是微服務(wù)和SOA的區(qū)別點:
- CI/CD:持續(xù)集成、持續(xù)部署本身與敏捷、DevOps是交織在一起的,CI\CD更傾向于軟件工程的領(lǐng)域,與微服務(wù)無關(guān);
- 基于容器還是虛擬機:Docker、虛擬機、物理機等是物理介質(zhì)的一種實現(xiàn)方式,與微服務(wù)無關(guān);
- 微服務(wù)周邊生態(tài):比如日志平臺、調(diào)用鏈系統(tǒng)?更多的是研發(fā)本身對于效率提高的自驅(qū)力,而與使用何種架構(gòu)方式無關(guān);
- 通訊協(xié)議:微服務(wù)的推薦通訊協(xié)議是RESTful,而傳統(tǒng)的SOA是SOAP。不過基于輕量級的RPC框架Dubbo、Thrift、gRPC來實現(xiàn)微服務(wù)也很多;在Spring Cloud中也有Feign框架將標(biāo)準(zhǔn)RESTful轉(zhuǎn)為代碼的API這種仿RPC的行為,這些通訊協(xié)議不是區(qū)分微服務(wù)架構(gòu)和SOA架構(gòu)的核心差別;
當(dāng)然,軟件工程(DevOps)、基礎(chǔ)設(shè)施(容器化)、軟件開發(fā)模式(敏捷開發(fā))的變革有利的推進了微服務(wù)架構(gòu)的大行其道。而微服務(wù)架構(gòu)是一種架構(gòu)風(fēng)格、架構(gòu)理念,其中的「微」更體現(xiàn)了它的精髓在切分。在實際微服務(wù)的落地過程中證明,如果切分是錯誤的,你得不到微服務(wù)承諾的「低耦合、自治、易維護」之類的優(yōu)勢,并且還會比單體架構(gòu)擁有更多的麻煩。那么如何切分呢?其實并不是一些新的方法論,而是都提出很多年的架構(gòu)設(shè)計方法,也稱它們?yōu)槲⒎?wù)設(shè)計基礎(chǔ)或架構(gòu)模型:領(lǐng)域驅(qū)動設(shè)計和立方體模型。
領(lǐng)域驅(qū)動設(shè)計
2004年,Eric Evans 發(fā)表了Domain Driven Design(領(lǐng)域驅(qū)動設(shè)計,DDD)。領(lǐng)域驅(qū)動設(shè)計已經(jīng)問世十幾年,從Eric Evans出版的著作「領(lǐng)域驅(qū)動設(shè)計」一書中對領(lǐng)域驅(qū)動做了開創(chuàng)性的理論闡述,在軟件設(shè)計領(lǐng)域中,DDD可以稱得上是步入暮年時期了。遺憾的是,國外軟件圈享有盛譽并行之有效的設(shè)計方法學(xué),國內(nèi)大多數(shù)的技術(shù)人員卻并不了解,也未曾運用到項目實踐中。直到行業(yè)內(nèi)吹起微服務(wù)的熱風(fēng),人們似乎才重新發(fā)現(xiàn)了領(lǐng)域驅(qū)動設(shè)計的價值,并不是微服務(wù)拯救了領(lǐng)域驅(qū)動設(shè)計,是因為領(lǐng)域驅(qū)動設(shè)計一直在頑強的成長,其設(shè)計開放的設(shè)計方法體系,雖然從來不曾在國內(nèi)大行其道,但卻發(fā)揮著巨大的價值。表面上看確實是因為微服務(wù),領(lǐng)域驅(qū)動設(shè)計才又開始出現(xiàn)在大眾視野里。
領(lǐng)域驅(qū)動設(shè)計的意義
當(dāng)然,領(lǐng)域驅(qū)動設(shè)計并非「銀彈」,不是能解決所有疑難雜癥的「靈丹妙藥」,學(xué)習(xí)并應(yīng)用它的意義在于:
- 一套完整的模型驅(qū)動的軟件設(shè)計方法,用于簡化軟件項目的復(fù)雜度,它能帶給你從戰(zhàn)略設(shè)計到戰(zhàn)術(shù)設(shè)計的規(guī)范過程,使得你的設(shè)計思路能夠更加清晰,設(shè)計過程更加規(guī)范;
- 一種思維方式和概念,可以應(yīng)用在處理復(fù)雜業(yè)務(wù)的軟件項目中,加快項目的交付速度;
- 一組提煉出來的原則和模式,可以幫助開發(fā)者開發(fā)優(yōu)雅的軟件系統(tǒng)、促進開發(fā)者對架構(gòu)與模型的精心打磨,尤其善于處理系統(tǒng)架構(gòu)的演進設(shè)計、有助于提高團隊成員的面向?qū)ο笤O(shè)計能力與架構(gòu)設(shè)計能力;
- 領(lǐng)域驅(qū)動設(shè)計與微服務(wù)架構(gòu)天生匹配,無論是在新項目中設(shè)計微服務(wù)架構(gòu),還是將系統(tǒng)從單體架構(gòu)演進到微服務(wù)設(shè)計,都可以遵循領(lǐng)域驅(qū)動設(shè)計的架構(gòu)原則。
當(dāng)然,領(lǐng)域驅(qū)動能給我們帶來很多收獲,但如果你是屬于以下幾種情況的某種,那么你確實不需要學(xué)習(xí)領(lǐng)域驅(qū)動設(shè)計了:
- 如果你是獨當(dāng)一面的架構(gòu)師,并能設(shè)計出優(yōu)雅的軟件架構(gòu)
- 如果你是高效編碼的程序員,并只想踏踏實實的寫代碼
- 如果你是前端的設(shè)計人員,并奉行「用戶體驗至上」的理念
- 如果你負(fù)責(zé)的軟件系統(tǒng)并不復(fù)雜,二三人便可輕松維護
DDD的關(guān)鍵概念
一個軟件系統(tǒng)的誕生,一定是為了解決我們遇到的某個問題。比如一家企業(yè)的一直采用線下銷售產(chǎn)品,耗費大量的財力和物力,希望可以在線上銷售自己的產(chǎn)品,用于實現(xiàn)在線銷售銷售產(chǎn)品的目的,那么就誕生了一個電商系統(tǒng)。通常最初設(shè)立的目標(biāo)或要解決的問題就是一個軟件項目的出發(fā)點,明確我們要做什么。比如一個電商、一個論壇、一個支付平臺等。
下文將從領(lǐng)域、問題域、領(lǐng)域模型、設(shè)計、驅(qū)動這幾個詞語的含義和聯(lián)系的角度去闡述DDD是如何融入到軟件開發(fā)的。要理解什么是領(lǐng)域驅(qū)動設(shè)計,首先要理解什么是領(lǐng)域,什么是設(shè)計,什么是驅(qū)動,什么驅(qū)動什么。
什么是領(lǐng)域/子領(lǐng)域(Domain/Subdomain)
領(lǐng)域是與某個特定問題相關(guān)的知識和行為。比如支付平臺就屬于特定的領(lǐng)域,只要是這個領(lǐng)域,都會有賬戶、會記、收款、付款、風(fēng)控等核心環(huán)節(jié)。所以,同一個領(lǐng)域的系統(tǒng)都具有相同的核心業(yè)務(wù),他們要解決的問題的本質(zhì)是一致的。一個領(lǐng)域本質(zhì)上可以理解為就是一個問題域,只要是同一個領(lǐng)域,那問題域就相同。所以,只要我們確定了系統(tǒng)所屬的領(lǐng)域,那這個系統(tǒng)的核心業(yè)務(wù),即要解決的關(guān)鍵問題、問題的范圍邊界就基本確定了。
在日常開發(fā)中,我們通常會將一個大型的軟件系統(tǒng)拆分成若干個子系統(tǒng)。這種劃分有可能是基于架構(gòu)方面的考慮,也有可能是基于基礎(chǔ)設(shè)施的。在DDD中,我們對系統(tǒng)的劃分是基于領(lǐng)域(基于業(yè)務(wù))的。比如上文提到支付平臺是一個領(lǐng)域,而賬戶、會記、收款、付款等則為子領(lǐng)域。一個領(lǐng)域由眾多子領(lǐng)域聚集而形成。
當(dāng)然,問題隨之而來:
- 哪些概念應(yīng)該建模在哪些子系統(tǒng)里面?
- 有時可能會發(fā)現(xiàn)一個領(lǐng)域概念建模在子系統(tǒng)A中是可以的,而建模在子系統(tǒng)B中也合情合理。
- 各個子系統(tǒng)之間的應(yīng)該如何集成?
- 有人可能會說,這不簡單得就像客戶端調(diào)用服務(wù)端那么簡單嗎?問題在于,兩個系統(tǒng)之間的集成涉及到基礎(chǔ)設(shè)施和不同領(lǐng)域概念在兩個系統(tǒng)之間的翻譯,稍不注意,這些概念就會對我們精心創(chuàng)建好的領(lǐng)域模型造成污染。
DDD中,有標(biāo)準(zhǔn)方法解決上述問題,就是**限界上下文(Bounded Context)和上下文映射圖。**在一個領(lǐng)域/子域中,我們會創(chuàng)建一個概念上的領(lǐng)域邊界,在這個邊界中,任何領(lǐng)域?qū)ο蠖贾槐硎咎囟ㄓ谠撨吔鐑?nèi)部的確切含義。這樣的邊界便稱為限界上下文。限界上下文和領(lǐng)域具有一對一的關(guān)系。從物理層面講,一個限界上下文最終可以是一個Jar/War文件,甚至可以是一個Package中的所有對象。但是,技術(shù)本身并不是用來界分限界上下文。
上圖引自《實現(xiàn)領(lǐng)域驅(qū)動設(shè)計》。通常情況下,一個領(lǐng)域有且只有一個核心問題,我們稱之為該領(lǐng)域的「核心域」。在核心域、通用子域、支撐子域梳理的同時,會定義出子域中的「限界上下文」及其關(guān)系,用它來闡述子域之間的關(guān)系。界限上下文可以簡單理解成一個子系統(tǒng)或組件模塊。
什么是設(shè)計(Design)
DDD中的設(shè)計主要指領(lǐng)域模型的設(shè)計。DDD是一種基于模型驅(qū)動開發(fā)的軟件開發(fā)思想,強調(diào)領(lǐng)域模型是整個系統(tǒng)的核心,領(lǐng)域模型也是整個平臺的核心價值。每一個領(lǐng)域都有一個對應(yīng)的領(lǐng)域模型,領(lǐng)域模型能夠很好的解決負(fù)責(zé)的業(yè)務(wù)問題。所以領(lǐng)域模型的設(shè)計和架構(gòu)設(shè)計同等重要。
什么是驅(qū)動(Driven)
DDD中,總是以領(lǐng)域為邊界,分析領(lǐng)域中的核心問題(核心關(guān)注點)。然后設(shè)計對應(yīng)的領(lǐng)域模型,通過領(lǐng)域模型驅(qū)動代碼的實現(xiàn)。而數(shù)據(jù)庫設(shè)計、持久化技術(shù)這些都不是DDD的核心,屬于外圍的東西。與數(shù)據(jù)庫驅(qū)動開發(fā)的思路形成對比,驅(qū)動中需要記住兩個原則:
- 領(lǐng)域驅(qū)動領(lǐng)域模型設(shè)計
- 領(lǐng)域模型驅(qū)動代碼實現(xiàn)
領(lǐng)域驅(qū)動設(shè)計的最大價值是讓我們告別從面向過程式的思想(天馬星空,想到哪寫到哪)轉(zhuǎn)化為基于系統(tǒng)化的模型驅(qū)動思維。我們腦補一下軟件開發(fā)中的常規(guī)心路歷程:
- 1、設(shè)計表結(jié)構(gòu)
- 2、寫代碼(代碼寫的很冗余,不夠抽象)
- 3、維護代碼(適應(yīng)業(yè)務(wù)變化)
- 4、遇到困難(數(shù)據(jù)結(jié)構(gòu)設(shè)計不合理、代碼到處冗余、改BUG引入新BUG、新人看代碼和無字天書一般)
- 5、愈發(fā)難以維護,開始重構(gòu)(理論上在老基礎(chǔ)上改的技術(shù)債務(wù)堪比重新開發(fā))
- 6、重構(gòu)完成,新系統(tǒng)上線(兼容歷史數(shù)據(jù)、數(shù)據(jù)遷移、新老系統(tǒng)并行,等等出發(fā)點考慮,其實本質(zhì)上只是做了代碼重構(gòu))
- 7、重復(fù)執(zhí)行3-6步......
DDD的分層架構(gòu)
四層架構(gòu)
Eric Evans在《領(lǐng)域驅(qū)動設(shè)計-軟件核心復(fù)雜性應(yīng)對之道》這本書中提出了傳統(tǒng)的四層架構(gòu)模式,在后來演進過程中出現(xiàn)了五層架構(gòu)和六層架構(gòu),,如下圖所示:
- User Interface:用戶界面層/展示層,負(fù)責(zé)與用戶交互。包含顯示信息、解釋用戶命令等;
- Application:應(yīng)用層,用來協(xié)調(diào)用戶與各應(yīng)用以及各應(yīng)用之間的交互。不包含業(yè)務(wù)邏輯、不保存業(yè)務(wù)對象的狀態(tài);
- Domain:領(lǐng)域?qū)?模型層,負(fù)責(zé)表達(dá)業(yè)務(wù)概念,業(yè)務(wù)狀態(tài)信息以及業(yè)務(wù)規(guī)則。包含領(lǐng)域模型、領(lǐng)域信息、業(yè)務(wù)對象的狀態(tài)。領(lǐng)域?qū)邮菢I(yè)務(wù)軟件的核心;
- Infrastructure:基礎(chǔ)設(shè)施層,為其他各層提供技術(shù)能力。包括為應(yīng)用層傳遞消息、為領(lǐng)域?qū)犹峁┏志没瘷C制、為用戶界面層繪制屏幕組件等等。基礎(chǔ)設(shè)施層還能夠通過架構(gòu)框架來支持四個層次間的交互模式。
六邊形架構(gòu)
隨著后續(xù)的演進,出現(xiàn)了一種改進分層架構(gòu)的方法,即Robert C. Martin提出的依賴倒置原則(Dependency Inversion Principle,DIP)。它通過改變不同層之間的依賴關(guān)系達(dá)到改進目的。
- 高層模塊不應(yīng)該依賴于底層模塊,兩者都應(yīng)該依賴于抽象
- 抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象
根據(jù)該原則的定義,DDD分層架構(gòu)中的低層組件應(yīng)該依賴于高層組件提供的接口,即無論高層還是低層都依賴于抽象,整個分層架構(gòu)好像被推平了,再向其中加入了一些對稱性,就出現(xiàn)了一種具有對稱性特征的六邊形架構(gòu)風(fēng)格。六邊形架構(gòu)是Alistair Cockburn在2005年提出的,其本質(zhì)是倡導(dǎo)不同的客戶通過「平等」的方式與系統(tǒng)交互,通過不斷的擴展適配器轉(zhuǎn)化成系統(tǒng)API所理解的參數(shù)來達(dá)到每種特定的輸出,而每種特定的輸出都有適配器完成相應(yīng)的轉(zhuǎn)化功能。
- 聚合:
- 一組具有內(nèi)聚關(guān)系的相關(guān)對象的集合;
- – 是一個修改數(shù)據(jù)的最小原子單元;
- – 聚合通常使用id訪問;
- 實體(Entity):表示具有生命周期并且會在其生命周期中發(fā)生改變的東西。含有VO、具有identity的特性,通常具有生命周期的概念 JPA tag @Entity;
- 值對象(Value Object):表示起描述性作用的并且可以相互替換的概念。類似于pojo,不可變immutable,可在不同模型中傳遞,Spring tag @value;
- 領(lǐng)域事件(Domain Event):所有的領(lǐng)域?qū)ο蟮目缇酆献兏枰允录绞竭M行通知和記錄,聚合內(nèi)的酌情考慮;
- 工廠(Factory):負(fù)責(zé)所有對象的生成和組裝;
- 領(lǐng)域服務(wù)(Domain Service):純技術(shù)層面的服務(wù),例如日志,或者是跨聚合的編排服務(wù),通常是Spring Component;
- 資源層(Repository):類似于DAO層,Spring JPA, Hibernate之類 @CRUDRepository;
- 防腐層:并非是系統(tǒng)間的消息傳遞機制,它的職責(zé)更具體的是指將某個模型或者契約中的概念對象及其行為轉(zhuǎn)換到另一個模型或者契約中;
貧血模型VS充血模型
讀完上面的兩種分層架構(gòu)方式,可能很多人會有疑問,這些是什么?為什么我之前一直都沒聽到過這種分法?確實是這樣,DDD和面向?qū)ο蟆⒃O(shè)計模式等等理論有千絲萬縷的聯(lián)系,如果不熟悉OOA、OOD,那么DDD可能也會理解不了。因為我們大部分從開發(fā)生涯開始之初接觸的都是「Action層、Service層、Dao層、DB層」這樣的MVC分層理論。并且在21中設(shè)計模式中,「行為型」的設(shè)計模式,我們幾乎沒有什么機會使用,導(dǎo)致這些問題的原因是J2EE經(jīng)典分層的開發(fā)方式是「貧血模型」。
Martin Fowler(對,就是提出微服務(wù)的那位大牛)曾經(jīng)提出了兩種開發(fā)方式,即:
- 以「貧血模型」為基礎(chǔ)的「事務(wù)腳本」的開發(fā)方式
- 以「充血模型」為基礎(chǔ)的「領(lǐng)域驅(qū)動」的開發(fā)方式
貧血模型
貧血模型是指對象只用于在各層之間傳輸數(shù)據(jù)使用,只有數(shù)據(jù)字段和Get/Set方法,沒有邏輯在對象中。而「事務(wù)腳本」可以理解為業(yè)務(wù)是由一條條增刪改查的SQL組織而成,是面向過程的編程。
充血模型是面向?qū)ο笤O(shè)計的本質(zhì),一個對象是擁有狀態(tài)和行為的。將大多數(shù)業(yè)務(wù)邏輯和持久化放在領(lǐng)域?qū)ο笾?#xff0c;業(yè)務(wù)邏輯只是完成對業(yè)務(wù)邏輯的封裝、事務(wù)、權(quán)限、校驗等的處理。
舉例,用戶管理模塊大概是這樣的兩種實現(xiàn):
// 貧血模型下的實現(xiàn) public class User{private Integer id;private String name;...// 省略get/set方法}public class UserManager{public void save(User user){// 持久化操作....} }// 保存用戶的操作可能是這樣 userManager.save(user);復(fù)制代碼// 充血模型下的實現(xiàn) public class User{private Integer id;private String name;...// 省略get/set方法public void save(){// 持久化操作....}}// 保存用戶的操作可能是這樣 user.save();復(fù)制代碼Martin Fowler定義的「貧血模型」是反模式,面對簡單的小系統(tǒng)用事務(wù)腳本方式開發(fā)沒問題;稍微大一些的系統(tǒng)使用事務(wù)腳本方式會擴大維護成本,業(yè)務(wù)邏輯、各種狀態(tài)散布在大量的函數(shù)中,哪怕就是要用戶對象中增加一個字段,可能都會涉及到幾個類的調(diào)整......
希望領(lǐng)域?qū)ο竽軌驕?zhǔn)確地表達(dá)出業(yè)務(wù)意圖,但是多數(shù)時候,我們所看到的卻是充滿getter和setter的領(lǐng)域?qū)ο?#xff0c;此時的領(lǐng)域?qū)ο笠呀?jīng)不是領(lǐng)域?qū)ο罅?#xff0c;反模式的貧血對象了。其實在貧血模型和充血模型模型之外,還有失血模型和脹血模型,但后者兩個基本是實際開發(fā)中不會去使用,因為走的是兩個極端。
總結(jié)
本文宏觀角度介紹了領(lǐng)域驅(qū)動設(shè)計,那么微服務(wù)和DDD是什么關(guān)系呢?其實在2015年的一次演講中,DDD的提出者Eric Evans表達(dá)了對微服務(wù)技術(shù)的熱愛與支持,認(rèn)為微服務(wù)是讓DDD落地的好工具。因為DDD和微服務(wù)其本質(zhì)是降低軟件項目的復(fù)雜性,而DDD是一種設(shè)計理念/設(shè)計方法,DDD需要有強制性的原則做保障,否則不同的領(lǐng)域?qū)ο蠼K究會混在一起。而微服務(wù)本身的一些限制,以及大家都能理解微服務(wù)的實施前提和首要條件,會在實現(xiàn)上給DDD增加了一些原則限制。DDD和微服務(wù)的不一定要同時使用落地,但是如果將DDD和微服務(wù)(兩個相差十歲的軟件設(shè)計方法)結(jié)合一起,那么Martin Fowler和Eric Evans兩位布道師是會很贊同的。
作者簡介
本分類文章,與「隨行付研究院」微信號文章同步,第一時間接收公眾號推送,請關(guān)注「隨行付研究院」公眾號。 復(fù)制代碼總結(jié)
以上是生活随笔為你收集整理的微服务架构设计基础之领域驱动设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简易付微信收款提示支付失败
- 下一篇: c++ 带参数的宏定义实现反射机制