<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          新來個技術(shù)總監(jiān),把DDD落地的那叫一個高級,服氣!

          共 40958字,需瀏覽 82分鐘

           ·

          2022-06-21 02:54

          二哥編程知識星球 (戳鏈接加入)正式上線了,來和 270 多名 小伙伴一起打怪升級吧!這是一個 Java 學(xué)習(xí)指南 + 編程實戰(zhàn)的私密圈子,你可以向二哥提問、幫你制定學(xué)習(xí)計劃、跟著二哥一起做實戰(zhàn)項目,沖沖沖。

          Java 程序員進階之路網(wǎng)址:https://tobebetterjavaer.com

          歡迎關(guān)注三劍客之一樓仔,我們一起要搞件大事。不 BB,直接上思維導(dǎo)圖!

          1. 走進 DDD

          1.1 為什么要用 DDD ?

          • 面向?qū)ο笤O(shè)計,數(shù)據(jù)行為綁定,告別貧血模型;
          • 降低復(fù)雜度,分而治之;
          • 優(yōu)先考慮領(lǐng)域模型,而不是切割數(shù)據(jù)和行為;
          • 準確傳達業(yè)務(wù)規(guī)則,業(yè)務(wù)優(yōu)先;
          • 代碼即設(shè)計;
          • 它通過邊界劃分將復(fù)雜業(yè)務(wù)領(lǐng)域簡單化,幫我們設(shè)計出清晰的領(lǐng)域和應(yīng)用邊界,可以很容易地實現(xiàn)業(yè)務(wù)和技術(shù)統(tǒng)一的架構(gòu)演進;
          • 領(lǐng)域知識共享,提升協(xié)助效率;
          • 增加可維護性和可讀性,延長軟件生命周期;
          • 中臺化的基石。

          1.2 DDD 作用

          說到 DDD,繞不開 MVC,在 MVC 三層架構(gòu)中,我們進行功能開發(fā)的之前,拿到需求,解讀需求。往往最先做的一步就是先設(shè)計表結(jié)構(gòu),在逐層設(shè)計上層 dao,service,controller。對于產(chǎn)品或者用戶的需求都做了一層自我理解的轉(zhuǎn)化。

          用戶需求在被提出之后經(jīng)過這么多層的轉(zhuǎn)化后,特別是研發(fā)需求在數(shù)據(jù)庫結(jié)構(gòu)這一層轉(zhuǎn)化后,將業(yè)務(wù)以主觀臆斷行為進行了轉(zhuǎn)化。一旦業(yè)務(wù)邊界劃分模糊,考慮不全,大量的邏輯補充堆積到了代碼層實現(xiàn),變得越來越難維護。

          假如我們現(xiàn)在要做一個電商訂單下單的需求,涉及到用戶選定商品,下訂單、支付訂單、對用戶下單時的訂單發(fā)貨:

          • MVC 架構(gòu):我們常見的做法是在分析好業(yè)務(wù)需求之后,就開始設(shè)計表結(jié)構(gòu)了,訂單表,支付表,商品表等等。然后編寫業(yè)務(wù)邏輯。這是第一個版本的需求,功能迭代餓了,訂單支付后我可以取消,下單的商品我們退換貨,是不是又需要進行加表,緊跟著對于的實現(xiàn)邏輯也進行修改。功能不斷迭代,代碼就不斷的層層往上疊。
          • DDD 架構(gòu):我們先進行劃分業(yè)務(wù)邊界。這里面核心是訂單。那么訂單就是這個業(yè)務(wù)領(lǐng)域里面的聚合邏輯體現(xiàn)。支付,商品信息,地址等等都是圍繞著訂單實體。訂單本身的屬性決定之后,類似于地址只是一個屬性的體現(xiàn)。當你將訂單的領(lǐng)域模型構(gòu)建好之后,后續(xù)的邏輯邊界與倉儲設(shè)計也就隨之而來了。

          DDD 整體作用總結(jié)如下:

          • 消除信息不對稱;
          • 常規(guī) MVC 三層架構(gòu)中自底向上的設(shè)計方式做一個反轉(zhuǎn),以業(yè)務(wù)為主導(dǎo),自頂向下的進行業(yè)務(wù)領(lǐng)域劃分;
          • 將大的業(yè)務(wù)需求進行拆分,分而治之。

          2. DDD 架構(gòu)

          2.1 DDD 分層架構(gòu)

          嚴格分層架構(gòu):某層只能與直接位于的下層發(fā)生耦合。

          松散分層架構(gòu):允許上層與任意下層發(fā)生耦合。

          在領(lǐng)域驅(qū)動設(shè)計(DDD)中采用的是松散分層架構(gòu),層間關(guān)系不那么嚴格。每層都可能使用它下面所有層的服務(wù),而不僅僅是下一層的服務(wù)。每層都可能是半透明的,這意味著有些服務(wù)只對上一層可見,而有些服務(wù)對上面的所有層都可見。

          分層的作用,從上往下:

          • 用戶交互層:web 請求,rpc 請求,mq 消息等外部輸入均被視為外部輸入的請求,可能修改到內(nèi)部的業(yè)務(wù)數(shù)據(jù)。
          • 業(yè)務(wù)應(yīng)用層:與 MVC 中的 service 不同的不是,service 中存儲著大量業(yè)務(wù)邏輯。但在應(yīng)用服務(wù)的實現(xiàn)中,它負責(zé)編排、轉(zhuǎn)發(fā)、校驗等。
          • 領(lǐng)域?qū)?/strong>:或稱為模型層,系統(tǒng)的核心,負責(zé)表達業(yè)務(wù)概念,業(yè)務(wù)狀態(tài)信息以及業(yè)務(wù)規(guī)則。即包含了該領(lǐng)域所有復(fù)雜的業(yè)務(wù)知識抽象和規(guī)則定義。該層主要精力要放在領(lǐng)域?qū)ο蠓治錾希梢詮膶嶓w,值對象,聚合(聚合根),領(lǐng)域服務(wù),領(lǐng)域事件,倉儲,工廠等方面入手。
          • 基礎(chǔ)設(shè)施層:主要有 2 方面內(nèi)容,一是為領(lǐng)域模型提供持久化機制,當軟件需要持久化能力時候才需要進行規(guī)劃;一是對其他層提供通用的技術(shù)支持能力,如消息通信,通用工具,配置等的實現(xiàn)。

          在設(shè)計和開發(fā)時,不要將本該放在領(lǐng)域?qū)拥臉I(yè)務(wù)邏輯放到應(yīng)用層中實現(xiàn),因為龐大的應(yīng)用層會使領(lǐng)域模型失焦,時間一長你的服務(wù)就會演化為傳統(tǒng)的三層架構(gòu),業(yè)務(wù)邏輯會變得混亂。

          2.2 各層數(shù)據(jù)轉(zhuǎn)換

          每一層都有自己特定的數(shù)據(jù),可以做如下區(qū)分:

          • VO(View Object):視圖對象,主要對應(yīng)界面顯示的數(shù)據(jù)對象。對于一個 WEB 頁面,或者 SWT、SWING 的一個界面,用一個 VO 對象對應(yīng)整個界面的值。
          • DTO(Data Transfer Object):數(shù)據(jù)傳輸對象,主要用于遠程調(diào)用等需要大量傳輸對象的地方。比如我們一張表有 100 個字段,那么對應(yīng)的 PO 就有 100 個屬性。但是我們界面上只要顯示 10 個字段,客戶端用 WEB service 來獲取數(shù)據(jù),沒有必要把整個 PO 對象傳遞到客戶端,這時我們就可以用只有這 10 個屬性的 DTO 來傳遞結(jié)果到客戶端,這樣也不會暴露服務(wù)端表結(jié)構(gòu)。到達客戶端以后,如果用這個對象來對應(yīng)界面顯示,那此時它的身份就轉(zhuǎn)為 VO。在這里,我泛指用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對象。
          • DO(Domain Object):領(lǐng)域?qū)ο?,就是從現(xiàn)實世界中抽象出來的有形或無形的業(yè)務(wù)實體。
          • PO(Persistent Object):持久化對象,它跟持久層(通常是關(guān)系型數(shù)據(jù)庫)的數(shù)據(jù)結(jié)構(gòu)形成一一對應(yīng)的映射關(guān)系,如果持久層是關(guān)系型數(shù)據(jù)庫,那么,數(shù)據(jù)表中的每個字段(或若干個)就對應(yīng) PO 的一個(或若干個)屬性。最形象的理解就是一個 PO 就是數(shù)據(jù)庫中的一條記錄,好處是可以把一條記錄作為一個對象處理,可以方便的轉(zhuǎn)為其它對象。

          3. DDD 基礎(chǔ)

          學(xué)習(xí) DDD 前,有很多基礎(chǔ)概念需要掌握,這幅圖總結(jié)的很全,他把 DDD 劃分不同的層級:

          • 最里層是值、屬性、唯一標識等,這個是最基本的數(shù)據(jù)單位,但不能直接使用。
          • 然后是實體,這個把基礎(chǔ)的數(shù)據(jù)進行封裝,可以直接使用,在代碼中就是封裝好的一個個實體對象。
          • 之后就是領(lǐng)域?qū)?,它按照業(yè)務(wù)劃分為不同的領(lǐng)域,比如訂單領(lǐng)域、商品領(lǐng)域、支付領(lǐng)域等。
          • 最后是應(yīng)用服務(wù),它對業(yè)務(wù)邏輯進行編排,也可以理解為業(yè)務(wù)層。

          3.1 領(lǐng)域和子域

          在研究和解決業(yè)務(wù)問題時,DDD 會按照一定的規(guī)則將業(yè)務(wù)領(lǐng)域進行細分,當領(lǐng)域細分到一定的程度后,DDD 會將問題范圍限定在特定的邊界內(nèi),在這個邊界內(nèi)建立領(lǐng)域模型,進而用代碼實現(xiàn)該領(lǐng)域模型,解決相應(yīng)的業(yè)務(wù)問題。簡言之,DDD 的領(lǐng)域就是這個邊界內(nèi)要解決的業(yè)務(wù)問題域。

          領(lǐng)域可以進一步劃分為子領(lǐng)域。我們把劃分出來的多個子領(lǐng)域稱為子域,每個子域?qū)?yīng)一個更小的問題域或更小的業(yè)務(wù)范圍。

          領(lǐng)域的核心思想就是將問題域逐級細分,來降低業(yè)務(wù)理解和系統(tǒng)實現(xiàn)的復(fù)雜度。通過領(lǐng)域細分,逐步縮小服務(wù)需要解決的問題域,構(gòu)建合適的領(lǐng)域模型。

          舉個簡單的例子,對于保險領(lǐng)域,我們可以把保險細分為承保、收付、再保以及理賠等子域,而承保子域還可以繼續(xù)細分為投保、保全(壽險)、批改(財險)等子子域。

          3.2 核心域、通用域和支撐域

          子域可以根據(jù)重要程度和功能屬性劃分為如下:

          • 核心域:決定產(chǎn)品和公司核心競爭力的子域,它是業(yè)務(wù)成功的主要因素和公司的核心競爭力。
          • 通用域:沒有太多個性化的訴求,同時被多個子域使用的通用功能的子域。
          • 支撐域:但既不包含決定產(chǎn)品和公司核心競爭力的功能,也不包含通用功能的子域。

          核心域、支撐域和通用域的主要目標:通過領(lǐng)域劃分,區(qū)分不同子域在公司內(nèi)的不同功能屬性和重要性,從而公司可對不同子域采取不同的資源投入和建設(shè)策略,其關(guān)注度也會不一樣。

          很多公司的業(yè)務(wù),表面看上去相似,但商業(yè)模式和戰(zhàn)略方向是存在很大差異的,因此公司的關(guān)注點會不一樣,在劃分核心域、通用域和支撐域時,其結(jié)果也會出現(xiàn)非常大的差異。

          比如同樣都是電商平臺的淘寶、天貓、京東和蘇寧易購,他們的商業(yè)模式是不同的。淘寶是 C2C 網(wǎng)站,個人賣家對個人買家,而天貓、京東和蘇寧易購則是 B2C 網(wǎng)站,是公司賣家對個人買家。即便是蘇寧易購與京東都是 B2C 的模式,蘇寧易購是典型的傳統(tǒng)線下賣場轉(zhuǎn)型成為電商,京東則是直營加部分平臺模式。

          因此,在公司建立領(lǐng)域模型時,我們就要結(jié)合公司戰(zhàn)略重點和商業(yè)模式,重點關(guān)注核心域。

          3.3 通用語言和限界上下文

          • 通用語言:就是能夠簡單、清晰、準確描述業(yè)務(wù)涵義和規(guī)則的語言。
          • 限界上下文:用來封裝通用語言和領(lǐng)域?qū)ο?,提供上下文環(huán)境,保證在領(lǐng)域之內(nèi)的一些術(shù)語、業(yè)務(wù)相關(guān)對象等(通用語言)有一個確切的含義,沒有二義性。
          3.3.1 通用語言

          通用語言是團隊統(tǒng)一的語言,不管你在團隊中承擔什么角色,在同一個領(lǐng)域的軟件生命周期里都使用統(tǒng)一的語言進行交流。那么,通用語言的價值也就很明了,它可以解決交流障礙這個問題,使領(lǐng)域?qū)<液烷_發(fā)人員能夠協(xié)同合作,從而確保業(yè)務(wù)需求的正確表達。

          這個通用語言到場景落地,大家可能還很模糊,其實就是把領(lǐng)域?qū)ο?、屬性、代碼模型對象等,通過代碼和文字建立映射關(guān)系,可以通過 Excel 記錄這個關(guān)系,這樣研發(fā)可以通過代碼知道這個含義,產(chǎn)品或者業(yè)務(wù)方可以通過文字知道這個含義,溝通起來就不會有歧義,說的簡單一點,其實就是統(tǒng)一產(chǎn)品和研發(fā)的話術(shù)。

          直接看下面這幅圖(來源于極客時間歐創(chuàng)新的 DDD 實戰(zhàn)課):

          3.3.2 限界上下文

          通用語言也有它的上下文環(huán)境,為了避免同樣的概念或語義在不同的上下文環(huán)境中產(chǎn)生歧義,DDD 在戰(zhàn)略設(shè)計上提出了“限界上下文”這個概念,用來確定語義所在的領(lǐng)域邊界。

          限界上下文是一個顯式的語義和語境上的邊界,領(lǐng)域模型便存在于邊界之內(nèi)。邊界內(nèi),通用語言中的所有術(shù)語和詞組都有特定的含義。把限界上下文拆解開看,限界就是領(lǐng)域的邊界,而上下文則是語義環(huán)境。

          通過領(lǐng)域的限界上下文,我們就可以在統(tǒng)一的領(lǐng)域邊界內(nèi)用統(tǒng)一的語言進行交流。

          3.4 實體和值對象

          3.4.1 實體

          實體 = 唯一身份標識 + 可變性【狀態(tài) + 行為】

          DDD 中要求實體是唯一的且可持續(xù)變化的。意思是說在實體的生命周期內(nèi),無論其如何變化,其仍舊是同一個實體。唯一性由唯一的身份標識來決定的??勺冃砸舱从沉藢嶓w本身的狀態(tài)和行為。

          實體以 DO(領(lǐng)域?qū)ο螅┑男问酱嬖?/strong>,每個實體對象都有唯一的 ID。我們可以對一個實體對象進行多次修改,修改后的數(shù)據(jù)和原來的數(shù)據(jù)可能會大不相同。

          但是,由于它們擁有相同的 ID,它們依然是同一個實體。比如商品是商品上下文的一個實體,通過唯一的商品 ID 來標識,不管這個商品的數(shù)據(jù)如何變化,商品的 ID 一直保持不變,它始終是同一個商品。

          3.4.2 值對象

          值對象 = 將一個值用對象的方式進行表述,來表達一個具體的固定不變的概念。

          當你只關(guān)心某個對象的屬性時,該對象便可作為一個值對象。我們需要將值對象看成不變對象,不要給它任何身份標識,還應(yīng)該盡量避免像實體對象一樣的復(fù)雜性。

          還是舉個訂單的例子,訂單是一個實體,里面包含地址,這個地址可以只通過屬性嵌入的方式形成的訂單實體對象,也可以將地址通過 json 序列化一個 string 類型的數(shù)據(jù),存到 DB 的一個字段中,那么這個 Json 串就是一個值對象,是不是很好理解?

          下面給個簡單的圖(同樣是源于極客時間歐創(chuàng)新的 DDD 實戰(zhàn)課):

          3.5 聚合和聚合根

          3.5.1 聚合

          聚合:我們把一些關(guān)聯(lián)性極強、生命周期一致的實體、值對象放到一個聚合里。聚合是領(lǐng)域?qū)ο蟮娘@式分組,旨在支持領(lǐng)域模型的行為和不變性,同時充當一致性和事務(wù)性邊界。

          聚合有一個聚合根和上下文邊界,這個邊界根據(jù)業(yè)務(wù)單一職責(zé)和高內(nèi)聚原則,定義了聚合內(nèi)部應(yīng)該包含哪些實體和值對象,而聚合之間的邊界是松耦合的。按照這種方式設(shè)計出來的服務(wù)很自然就是“高內(nèi)聚、低耦合”的。

          聚合在 DDD 分層架構(gòu)里屬于領(lǐng)域?qū)樱I(lǐng)域?qū)影硕鄠€聚合,共同實現(xiàn)核心業(yè)務(wù)邏輯??缍鄠€實體的業(yè)務(wù)邏輯通過領(lǐng)域服務(wù)來實現(xiàn),跨多個聚合的業(yè)務(wù)邏輯通過應(yīng)用服務(wù)來實現(xiàn)。

          比如有的業(yè)務(wù)場景需要同一個聚合的 A 和 B 兩個實體來共同完成,我們就可以將這段業(yè)務(wù)邏輯用領(lǐng)域服務(wù)來實現(xiàn);而有的業(yè)務(wù)邏輯需要聚合 C 和聚合 D 中的兩個服務(wù)共同完成,這時你就可以用應(yīng)用服務(wù)來組合這兩個服務(wù)。

          3.5.2 聚合根

          如果把聚合比作組織,那聚合根就是這個組織的負責(zé)人。聚合根也稱為根實體,它不僅是實體,還是聚合的管理者。

          • 首先它作為實體本身,擁有實體的屬性和業(yè)務(wù)行為,實現(xiàn)自身的業(yè)務(wù)邏輯。
          • 其次它作為聚合的管理者,在聚合內(nèi)部負責(zé)協(xié)調(diào)實體和值對象按照固定的業(yè)務(wù)規(guī)則協(xié)同完成共同的業(yè)務(wù)邏輯。
          • 最后在聚合之間,它還是聚合對外的接口人,以聚合根 ID 關(guān)聯(lián)的方式接受外部任務(wù)和請求,在上下文內(nèi)實現(xiàn)聚合之間的業(yè)務(wù)協(xié)同。也就是說,聚合之間通過聚合根 ID 關(guān)聯(lián)引用,如果需要訪問其它聚合的實體,就要先訪問聚合根,再導(dǎo)航到聚合內(nèi)部實體,外部對象不能直接訪問聚合內(nèi)實體。

          上面講的還是有些抽象,下面看一個圖就能很好理解(同樣是源于極客時間歐創(chuàng)新的 DDD 實戰(zhàn)課):

          簡單概括一下:

          • 通過事件風(fēng)暴(我理解就是頭腦風(fēng)暴,不過我們一般都是先通過個人理解,然后再和相關(guān)核心同學(xué)進行溝通),得到實體和值對象;
          • 將這些實體和值對象聚合為“投保聚合”和“客戶聚合”,其中“投保單”和“客戶”是兩者的聚合根;
          • 找出與聚合根“投保單”和“客戶”關(guān)聯(lián)的所有緊密依賴的實體和值對象;
          • 在聚合內(nèi)根據(jù)聚合根、實體和值對象的依賴關(guān)系,畫出對象的引用和依賴模型。

          3.6 領(lǐng)域服務(wù)和應(yīng)用服務(wù)

          3.6.1 領(lǐng)域服務(wù)

          當一些邏輯不屬于某個實體時,可以把這些邏輯單獨拿出來放到領(lǐng)域服務(wù)中,理想的情況是沒有領(lǐng)域服務(wù),如果領(lǐng)域服務(wù)使用不恰當,慢慢又演化回了以前邏輯都在 service 層的局面。

          可以使用領(lǐng)域服務(wù)的情況:

          • 執(zhí)行一個顯著的業(yè)務(wù)操作
          • 對領(lǐng)域?qū)ο筮M行轉(zhuǎn)換
          • 以多個領(lǐng)域?qū)ο笞鳛檩斎雲(yún)?shù)進行計算,結(jié)果產(chǎn)生一個值對象
          3.6.2 應(yīng)用服務(wù)

          應(yīng)用層作為展現(xiàn)層與領(lǐng)域?qū)拥臉蛄?,是用來表達用例和用戶故事的主要手段。

          應(yīng)用層通過應(yīng)用服務(wù)接口來暴露系統(tǒng)的全部功能。在應(yīng)用服務(wù)的實現(xiàn)中,它負責(zé)編排和轉(zhuǎn)發(fā),它將要實現(xiàn)的功能委托給一個或多個領(lǐng)域?qū)ο髞韺崿F(xiàn),它本身只負責(zé)處理業(yè)務(wù)用例的執(zhí)行順序以及結(jié)果的拼裝。通過這樣一種方式,它隱藏了領(lǐng)域?qū)拥膹?fù)雜性及其內(nèi)部實現(xiàn)機制。

          應(yīng)用層相對來說是較“薄”的一層,除了定義應(yīng)用服務(wù)之外,在該層我們可以進行安全認證,權(quán)限校驗,持久化事務(wù)控制,或者向其他系統(tǒng)發(fā)生基于事件的消息通知,另外還可以用于創(chuàng)建郵件以發(fā)送給客戶等。

          3.7 領(lǐng)域事件

          領(lǐng)域事件 = 事件發(fā)布 + 事件存儲 + 事件分發(fā) + 事件處理。

          領(lǐng)域事件是一個領(lǐng)域模型中極其重要的部分,用來表示領(lǐng)域中發(fā)生的事件。忽略不相關(guān)的領(lǐng)域活動,同時明確領(lǐng)域?qū)<乙櫥蛳M煌ㄖ氖虑椋蚺c其他模型對象中的狀態(tài)更改相關(guān)聯(lián)。

          下面簡單說明領(lǐng)域事件:

          • 事件發(fā)布:構(gòu)建一個事件,需要唯一標識,然后發(fā)布;
          • 事件存儲:發(fā)布事件前需要存儲,因為接收后的事建也會存儲,可用于重試或?qū)~等;
          • 事件分發(fā):服務(wù)內(nèi)直接發(fā)布給訂閱者,服務(wù)外需要借助消息中間件,比如 Kafka,RabbitMQ 等;
          • 事件處理:先將事件存儲,然后再處理。

          比如下訂單后,給用戶增長積分與贈送優(yōu)惠券的需求。如果使用瀑布流的方式寫代碼。一個個邏輯調(diào)用,那么不同用戶,贈送的東西不同,邏輯就會變得又臭又長。

          這里的比較好的方式是,用戶下訂單成功后,發(fā)布領(lǐng)域事件,積分聚合與優(yōu)惠券聚合監(jiān)聽訂單發(fā)布的領(lǐng)域事件進行處理。

          3.8 資源庫【倉儲】

          倉儲介于領(lǐng)域模型和數(shù)據(jù)模型之間,主要用于聚合的持久化和檢索。它隔離了領(lǐng)域模型和數(shù)據(jù)模型,以便我們關(guān)注于領(lǐng)域模型而不需要考慮如何進行持久化。

          我們將暫時不使用的領(lǐng)域?qū)ο髲膬?nèi)存中持久化存儲到磁盤中。當日后需要再次使用這個領(lǐng)域?qū)ο髸r,根據(jù) key 值到數(shù)據(jù)庫查找到這條記錄,然后將其恢復(fù)成領(lǐng)域?qū)ο?,?yīng)用程序就可以繼續(xù)使用它了,這就是領(lǐng)域?qū)ο蟪志没鎯Φ脑O(shè)計思想。

          是不是感覺這塊內(nèi)容比較抽象?直接對著 Demo 學(xué)習(xí)吧,很多東西你就會豁然開朗。

          4. DDD 實戰(zhàn)

          4.1 項目介紹

          • 主要是圍繞用戶、角色和兩者的關(guān)系,構(gòu)建權(quán)限分配領(lǐng)域模型。
          • 采用 DDD 4 層架構(gòu),包括用戶接口層、應(yīng)用層、領(lǐng)域?qū)雍突A(chǔ)服務(wù)層。
          • 數(shù)據(jù)通過 VO、DTO、DO、PO 轉(zhuǎn)換,進行分層隔離。
          • 采用 SpringBoot + MyBatis Plus 框架,存儲用 MySQL。

          4.2 工程目錄

          項目劃分為用戶接口層、應(yīng)用層、領(lǐng)域?qū)雍突A(chǔ)服務(wù)層,每一層的代碼結(jié)構(gòu)都非常清晰,包括每一層 VO、DTO、DO、PO 的數(shù)據(jù)定義,對于每一層的公共代碼,比如常量、接口等,都抽離到 ddd-common 中。

          ./ddd-application  // 應(yīng)用層
          ├── pom.xml
          └── src
              └── main
                  └── java
                      └── com
                          └── ddd
                              └── applicaiton
                                  ├── converter
                                  │   └── UserApplicationConverter.java // 類型轉(zhuǎn)換器
                                  └── impl
                                      └── AuthrizeApplicationServiceImpl.java // 業(yè)務(wù)邏輯
          ./ddd-common
          ├── ddd-common // 通用類庫
          │   ├── pom.xml
          │   └── src
          │       └── main
          │           └── java
          │               └── com
          │                   └── ddd
          │                       └── common
          │                           ├── exception // 異常
          │                           │   ├── ServiceException.java
          │                           │   └── ValidationException.java
          │                           ├── result // 返回結(jié)果集
          │                           │   ├── BaseResult.javar
          │                           │   ├── Page.java
          │                           │   ├── PageResult.java
          │                           │   └── Result.java
          │                           └── util // 通用工具
          │                               ├── GsonUtil.java
          │                               └── ValidationUtil.java
          ├── ddd-common-application // 業(yè)務(wù)層通用模塊
          │   ├── pom.xml
          │   └── src
          │       └── main
          │           └── java
          │               └── com
          │                   └── ddd
          │                       └── applicaiton
          │                           ├── dto // DTO
          │                           │   ├── RoleInfoDTO.java
          │                           │   └── UserRoleDTO.java
          │                           └── servic // 業(yè)務(wù)接口
          │                               └── AuthrizeApplicationService.java
          ├── ddd-common-domain
          │   ├── pom.xml
          │   └── src
          │       └── main
          │           └── java
          │               └── com
          │                   └── ddd
          │                       └── domain
          │                           ├── event // 領(lǐng)域事件
          │                           │   ├── BaseDomainEvent.java
          │                           │   └── DomainEventPublisher.java
          │                           └── service // 領(lǐng)域接口
          │                               └── AuthorizeDomainService.java
          └── ddd-common-infra
              ├── pom.xml
              └── src
                  └── main
                      └── java
                          └── com
                              └── ddd
                                  └── infra
                                      ├── domain // DO
                                      │   └── AuthorizeDO.java
                                      ├── dto 
                                      │   ├── AddressDTO.java
                                      │   ├── RoleDTO.java
                                      │   ├── UnitDTO.java
                                      │   └── UserRoleDTO.java
                                      └── repository
                                          ├── UserRepository.java // 領(lǐng)域倉庫
                                          └── mybatis
                                              └── entity // PO
                                                  ├── BaseUuidEntity.java
                                                  ├── RolePO.java
                                                  ├── UserPO.java
                                                  └── UserRolePO.java
          ./ddd-domian  // 領(lǐng)域?qū)?br>├── pom.xml
          └── src
              └── main
                  └── java
                      └── com
                          └── ddd
                              └── domain
                                  ├── event // 領(lǐng)域事件
                                  │   ├── DomainEventPublisherImpl.java
                                  │   ├── UserCreateEvent.java
                                  │   ├── UserDeleteEvent.java
                                  │   └── UserUpdateEvent.java
                                  └── impl // 領(lǐng)域邏輯
                                      └── AuthorizeDomainServiceImpl.java
          ./ddd-infra  // 基礎(chǔ)服務(wù)層
          ├── pom.xml
          └── src
              └── main
                  └── java
                      └── com
                          └── ddd
                              └── infra
                                  ├── config
                                  │   └── InfraCoreConfig.java  // 掃描Mapper文件
                                  └── repository
                                      ├── converter
                                      │   └── UserConverter.java // 類型轉(zhuǎn)換器
                                      ├── impl
                                      │   └── UserRepositoryImpl.java
                                      └── mapper
                                          ├── RoleMapper.java
                                          ├── UserMapper.java
                                          └── UserRoleMapper.java
          ./ddd-interface
          ├── ddd-api  // 用戶接口層
          │   ├── pom.xml
          │   └── src
          │       └── main
          │           ├── java
          │           │   └── com
          │           │       └── ddd
          │           │           └── api
          │           │               ├── DDDFrameworkApiApplication.java // 啟動入口
          │           │               ├── converter
          │           │               │   └── AuthorizeConverter.java // 類型轉(zhuǎn)換器
          │           │               ├── model
          │           │               │   ├── req // 入?yún)?nbsp;req
          │           │               │   │   ├── AuthorizeCreateReq.java
          │           │               │   │   └── AuthorizeUpdateReq.java
          │           │               │   └── vo  // 輸出 VO
          │           │               │       └── UserAuthorizeVO.java
          │           │               └── web     // API
          │           │                   └── AuthorizeController.java
          │           └── resources // 系統(tǒng)配置
          │               ├── application.yml
          │           └── resources // Sql文件
          │               └── init.sql
          └── ddd-task
              └── pom.xml
          ./pom.xml

          4.3 數(shù)據(jù)庫

          包括 3 張表,分別為用戶、角色和用戶角色表,一個用戶可以擁有多個角色,一個角色可以分配給多個用戶。

          create table t_user
          (
              id           bigint auto_increment comment '主鍵' primary key,
              user_name    varchar(64)                        null comment '用戶名',
              password     varchar(255)                       null comment '密碼',
              real_name    varchar(64)                        null comment '真實姓名',
              phone        bigint                             null comment '手機號',
              province     varchar(64)                        null comment '用戶名',
              city         varchar(64)                        null comment '用戶名',
              county       varchar(64)                        null comment '用戶名',
              unit_id      bigint                             null comment '單位id',
              unit_name    varchar(64)                        null comment '單位名稱',
              gmt_create   datetime default CURRENT_TIMESTAMP not null comment '創(chuàng)建時間',
              gmt_modified datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '修改時間',
              deleted      bigint   default 0                 not null comment '是否刪除,非0為已刪除'
          )comment '用戶表' collate = utf8_bin;
          create table t_role
          (
              id           bigint auto_increment comment '主鍵' primary key,
              name         varchar(256)                       not null comment '名稱',
              code         varchar(64)                        null comment '角色code',
              gmt_create   datetime default CURRENT_TIMESTAMP not null comment '創(chuàng)建時間',
              gmt_modified datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '修改時間',
              deleted      bigint   default 0                 not null comment '是否已刪除'
          )comment '角色表' charset = utf8;
          create table t_user_role (
              id           bigint auto_increment comment '主鍵id' primary key,
              user_id      bigint                             not null comment '用戶id',
              role_id      bigint                             not null comment '角色id',
              gmt_create   datetime default CURRENT_TIMESTAMP not null comment '創(chuàng)建時間',
              gmt_modified datetime default CURRENT_TIMESTAMP not null comment '修改時間',
              deleted      bigint   default 0                 not null comment '是否已刪除'
          )comment '用戶角色關(guān)聯(lián)表' charset = utf8;

          4.4 基礎(chǔ)服務(wù)層

          倉儲(資源庫)介于領(lǐng)域模型和數(shù)據(jù)模型之間,主要用于聚合的持久化和檢索。它隔離了領(lǐng)域模型和數(shù)據(jù)模型,以便我們關(guān)注于領(lǐng)域模型而不需要考慮如何進行持久化。

          比如保存用戶,需要將用戶和角色一起保存,也就是創(chuàng)建用戶的同時,需要新建用戶的角色權(quán)限,這個可以直接全部放到倉儲中:

          public AuthorizeDO save(AuthorizeDO user) {
              UserPO userPo = userConverter.toUserPo(user);
              if(Objects.isNull(user.getUserId())){
                  userMapper.insert(userPo);
                  user.setUserId(userPo.getId());
              } else {
                  userMapper.updateById(userPo);
                  userRoleMapper.delete(Wrappers.<UserRolePO>lambdaQuery()
                          .eq(UserRolePO::getUserId, user.getUserId()));
              }
              List<UserRolePO> userRolePos = userConverter.toUserRolePo(user);
              userRolePos.forEach(userRoleMapper::insert);
              return this.query(user.getUserId());
          }

          倉儲對外暴露的接口如下:

          // 用戶領(lǐng)域倉儲
          public interface UserRepository {
              // 刪除
              void delete(Long userId);
              // 查詢
              AuthorizeDO query(Long userId);
              // 保存
              AuthorizeDO save(AuthorizeDO user);
          }

          基礎(chǔ)服務(wù)層不僅僅包括資源庫,與第三方的調(diào)用,都需要放到該層,Demo 中沒有該示例,我們可以看一個小米內(nèi)部具體的實際項目,他把第三方的調(diào)用放到了 remote 目錄中:

          4.5 領(lǐng)域?qū)?span style="display: none;">

          4.5.1 聚合&聚合根

          我們有用戶和角色兩個實體,可以將用戶、角色和兩者關(guān)系進行聚合,然后用戶就是聚合根,聚合之后的屬性,我們稱之為“權(quán)限”。

          對于地址 Address,目前是作為字段屬性存儲到 DB 中,如果對地址無需進行檢索,可以把地址作為“值對象”進行存儲,即把地址序列化為 Json 存,存儲到 DB 的一個字段中。

          public class AuthorizeDO {
              // 用戶ID
              private Long userId;
              // 用戶名
              private String userName;
              // 真實姓名
              private String realName;
              // 手機號
              private String phone;
              // 密碼
              private String password;
              // 用戶單位
              private UnitDTO unit;
              // 用戶地址
              private AddressDTO address;
              // 用戶角色
              private List<RoleDTO> roles;
          }
          4.5.2 領(lǐng)域服務(wù)

          Demo 中的領(lǐng)域服務(wù)比較薄,通過單位 ID 后去獲取單位名稱,構(gòu)建單位信息:

          @Service
          public class AuthorizeDomainServiceImpl implements AuthorizeDomainService {
              @Override
              // 設(shè)置單位信息
              public void associatedUnit(AuthorizeDO authorizeDO) {
                  String unitName = "武漢小米";// TODO: 通過第三方獲取
                  authorizeDO.getUnit().setUnitName(unitName);
              }
          }

          我們其實可以把領(lǐng)域服務(wù)再進一步抽象,可以抽象出領(lǐng)域能力,通過這些領(lǐng)域能力去構(gòu)建應(yīng)用層邏輯,比如賬號相關(guān)的領(lǐng)域能力可以包括授權(quán)領(lǐng)域能力、身份認證領(lǐng)域能力等,這樣每個領(lǐng)域能力相對獨立,就不會全部揉到一個文件中,下面是實際項目的領(lǐng)域?qū)咏貓D:

          4.5.3 領(lǐng)域事件

          領(lǐng)域事件 = 事件發(fā)布 + 事件存儲 + 事件分發(fā) + 事件處理。

          這個 Demo 中,對領(lǐng)域事件的處理非常簡單,還是一個應(yīng)用內(nèi)部的領(lǐng)域事件,就是每次執(zhí)行一次具體的操作時,把行為記錄下來。Demo 中沒有記錄事件的庫表,事件的分發(fā)還是同步的方式,所以 Demo 中的領(lǐng)域事件還不完善,后面我會再繼續(xù)完善 Demo 中的領(lǐng)域事件,通過 Java 消息機制實現(xiàn)解耦,甚至可以借助消息隊列,實現(xiàn)異步。

          /**
           * 領(lǐng)域事件基類
           *
           * @author louzai
           * @since 2021/11/22
           */
          @Getter
          @Setter
          @NoArgsConstructor
          public abstract class BaseDomainEvent<T> implements Serializable {
              private static final long serialVersionUID = 1465328245048581896L;
              /**
               * 發(fā)生時間
               */
              private LocalDateTime occurredOn;
              /**
               * 領(lǐng)域事件數(shù)據(jù)
               */
              private T data;
              public BaseDomainEvent(T data) {
                  this.data = data;
                  this.occurredOn = LocalDateTime.now();
              }
          }
          /**
           * 用戶新增領(lǐng)域事件
           *
           * @author louzai
           * @since 2021/11/20
           */
          public class UserCreateEvent extends BaseDomainEvent<AuthorizeDO> {
              public UserCreateEvent(AuthorizeDO user) {
                  super(user);
              }
          }
          /**
           * 領(lǐng)域事件發(fā)布實現(xiàn)類
           *
           * @author louzai
           * @since 2021/11/20
           */
          @Component
          @Slf4j
          public class DomainEventPublisherImpl implements DomainEventPublisher {
              @Autowired
              private ApplicationEventPublisher applicationEventPublisher;
              @Override
              public void publishEvent(BaseDomainEvent event) {
                  log.debug("發(fā)布事件,event:{}", GsonUtil.gsonToString(event));
                  applicationEventPublisher.publishEvent(event);
              }
          }

          4.4 應(yīng)用層

          應(yīng)用層就非常好理解了,只負責(zé)簡單的邏輯編排,比如創(chuàng)建用戶授權(quán):

          @Transactional(rollbackFor = Exception.class)
          public void createUserAuthorize(UserRoleDTO userRoleDTO){
              // DTO轉(zhuǎn)為DO
              AuthorizeDO authorizeDO = userApplicationConverter.toAuthorizeDo(userRoleDTO);
              // 關(guān)聯(lián)單位單位信息
              authorizeDomainService.associatedUnit(authorizeDO);
              // 存儲用戶
              AuthorizeDO saveAuthorizeDO = userRepository.save(authorizeDO);
              // 發(fā)布用戶新建的領(lǐng)域事件
              domainEventPublisher.publishEvent(new UserCreateEvent(saveAuthorizeDO));
          }

          查詢用戶授權(quán)信息:

          @Override
            public UserRoleDTO queryUserAuthorize(Long userId) {
                // 查詢用戶授權(quán)領(lǐng)域數(shù)據(jù)
                AuthorizeDO authorizeDO = userRepository.query(userId);
                if (Objects.isNull(authorizeDO)) {
                    throw ValidationException.of("UserId is not exist.", null);
                }
                // DO轉(zhuǎn)DTO
                return userApplicationConverter.toAuthorizeDTO(authorizeDO);
            }

          細心的同學(xué)可以發(fā)現(xiàn),我們應(yīng)用層和領(lǐng)域?qū)?,通過 DTO 和 DO 進行數(shù)據(jù)轉(zhuǎn)換。

          4.5 用戶接口層

          最后就是提供 API 接口:

          @GetMapping("/query")
          public Result<UserAuthorizeVO> query(@RequestParam("userId") Long userId){
              UserRoleDTO userRoleDTO = authrizeApplicationService.queryUserAuthorize(userId);
              Result<UserAuthorizeVO> result = new Result<>();
              result.setData(authorizeConverter.toVO(userRoleDTO));
              result.setCode(BaseResult.CODE_SUCCESS);
              return result;
          }
          @PostMapping("/save")
          public Result<Object> create(@RequestBody AuthorizeCreateReq authorizeCreateReq){
              authrizeApplicationService.createUserAuthorize(authorizeConverter.toDTO(authorizeCreateReq));
              return Result.ok(BaseResult.INSERT_SUCCESS);
          }

          數(shù)據(jù)的交互,包括入?yún)?、DTO 和 VO,都需要對數(shù)據(jù)進行轉(zhuǎn)換。

          4.6 項目運行

          • 新建庫表:通過文件 "ddd-interface/ddd-api/src/main/resources/init.sql" 新建庫表。

          • 修改 SQL 配置:修改 "ddd-interface/ddd-api/src/main/resources/application.yml" 的數(shù)據(jù)庫配置。

          • 啟動服務(wù):直接啟動服務(wù)即可。

          • 測試用例:

          • 請求 URL:http://127.0.0.1:8087/api/user/save

          • Post body:{"userName":"louzai","realName":"樓","phone":13123676844,"password":"***","unitId":2,"province":"湖北省","city":"鄂州市","county":"葛店開發(fā)區(qū)","roles":[{"roleId":2}]}

          4.7 項目地址

          DDD Demo 代碼已經(jīng)上傳到 GitHub 中:

          https://github.com/lml200701158/ddd-framework

          或者通過下面命令直接獲?。?/p>

          git clone [email protected]:lml200701158/ddd-framework.git

          5. 結(jié)語

          最后,談?wù)劧鐚?DDD 的理解,我覺得 DDD 不像一門技術(shù),我理解的技術(shù)比如高并發(fā)、緩存、消息隊列等,DDD 更像是一項軟技能,一種方法論,包含了很多設(shè)計理念。

          大家不要認為,掌握了一些概念,以及 DDD 的基本思想,就掌握了 DDD,然后做項目時,照葫蘆畫瓢,這樣你會死的很慘!

          只掌握 DDD 表面的東西,其實是不夠的,我覺得 DDD 最復(fù)雜的地方,其實是在它的領(lǐng)域設(shè)計部分,項目啟動前,你一定要設(shè)計各個領(lǐng)域?qū)ο?,以及它們直接的交互關(guān)系。

          比如我們之前做過一個項目,因為這塊沒有做好,大家一邊寫代碼,一邊還在思考,這個領(lǐng)域?qū)ο笤撊绾螛?gòu)造,嚴重影響開發(fā)效率,最后又不得不回退到 MVC 的模式。

          不要為了炫技,啥都要搞個 DDD,兩者如何選擇:

          • MVC:上來就可以開干,短平快,前期用起來很香,整體開發(fā)效率也更高,所以對于緊急,或者不那么重要的項目,我會直接用 MVC 懟,不好的地方就是,后面會越來越復(fù)雜,可能最后就是一坨屎山,但是很多時候,比如老板進度催的緊,我哪想到那么多以后呢?
          • DDD:前期需要花大量時間設(shè)計好領(lǐng)域模型,對于一些基礎(chǔ)組件,或者一些核心服務(wù),如果對象模型非常復(fù)雜,建議采用 DDD,前期可能會稍微痛苦一些,但是后期維護起來會非常方便。

          沒有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧靜的港灣,我是不系之舟。

          推薦閱讀

          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    欧美性爱首页大导航 | 99热在线精品播放 | 亚洲无圣光豆花 | 青青草手机在线免费激情视频欧美精华 | 国产吞精 |