日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

如何运用领域驱动设计 - 领域事件

發(fā)布時(shí)間:2023/12/4 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何运用领域驱动设计 - 领域事件 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

開(kāi)篇

距離發(fā)布上一篇該系列的文章好像已經(jīng)過(guò)了快一個(gè)半月了,好吧,我托更了????。一晃就已經(jīng)到了3月份,在這櫻花????盛開(kāi)的季節(jié),終于得重新連載該系列了。在停更的期間時(shí)不時(shí)會(huì)收到大家關(guān)于DDD的留言和問(wèn)題,一旦我有時(shí)間一定會(huì)回復(fù)大家的問(wèn)題。在此,衷心感謝大家對(duì)本系列文章的支持????。

概述

在實(shí)踐領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)的過(guò)程中,我們往往會(huì)遇到多個(gè)領(lǐng)域?qū)ο笙嗷ソ换サ那闆r。比如聚合根A在執(zhí)行某操作之前需要得到聚合根B的某個(gè)信號(hào)(或某些數(shù)據(jù))。如果在單體應(yīng)用程序中,我們有條件和機(jī)會(huì)使得兩者進(jìn)行強(qiáng)引用來(lái)完成操作,但是這將直接打破領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的規(guī)范,從而使得項(xiàng)目不可控,再次回到大泥球的開(kāi)發(fā)。

現(xiàn)在,咱們可以選取一種更純凈的方式來(lái)解決這類問(wèn)題,并且還能夠更清晰的描述領(lǐng)域?qū)ο蟮幕顒?dòng)跡象。這就是咱們今天的主題 ————?“領(lǐng)域事件”。那么到底什么是領(lǐng)域事件呢?引入領(lǐng)域事件會(huì)為我們已有的DDD項(xiàng)目帶來(lái)哪些益處?是否一定要使用領(lǐng)域事件呢?本文將從不同的角度來(lái)帶大家重新認(rèn)識(shí)一下“領(lǐng)域事件”這個(gè)概念,并且給出相應(yīng)的代碼片段(本教程的代碼片段都使用的是C#,當(dāng)然思想是跨越任何編程語(yǔ)言的????)。

什么是領(lǐng)域事件

在原著?《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道》?其實(shí)并沒(méi)有直接提及到關(guān)于領(lǐng)域事件的介紹。領(lǐng)域?qū)ο笫窃诤笃诓疟蛔髡?strong>Evans提出,經(jīng)過(guò)Udi Dahan(Nservicebus作者)和Jimmy Bogard(MetdiaR、AutoMapper作者)等專家后期的不斷實(shí)踐和演變才有了今天的領(lǐng)域事件版本。

此處我摘錄了《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》書(shū)中對(duì)領(lǐng)域事件的描述:

領(lǐng)域?qū)<宜P(guān)心的發(fā)生在領(lǐng)域中的一些事件。
將領(lǐng)域中所發(fā)生的活動(dòng)建模成一系列的離散事件。每個(gè)事件都用領(lǐng)域?qū)ο髞?lái)表示,領(lǐng)域事件是領(lǐng)域模型的組成部分,表示領(lǐng)域中所發(fā)生的事情。

如何使用領(lǐng)域事件

當(dāng)您一看到“事件”這個(gè)詞語(yǔ)的時(shí)候,您可能會(huì)一下聯(lián)系到 C# 中的事件,那個(gè)基于委托的事件。確實(shí),它們之間有著共性,就比如:“當(dāng)事件發(fā)生的時(shí)候,與該事件相關(guān)聯(lián)的對(duì)象都將受到波及。” 所以,如果您了解C#中的事件,那將幫助您更好的理解“領(lǐng)域事件”。

由此我們可以推導(dǎo)出:在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)建模過(guò)程中,如果發(fā)現(xiàn)有一項(xiàng)動(dòng)作發(fā)生了之后,與之關(guān)聯(lián)的其他領(lǐng)域?qū)ο髮?huì)受到波及。?那么該動(dòng)作可能就是“領(lǐng)域事件”。

光從概念上來(lái)講些許有些讓人頭暈,我們來(lái)看看實(shí)際的一個(gè)例子:“當(dāng)用戶將商品添加到購(gòu)物車的時(shí)候,下方的推薦商品將為他推薦同類型的商品”。這是一個(gè)有前后發(fā)生關(guān)系的典型案例,商品被添加到了購(gòu)物車就會(huì)引發(fā)推薦同類商品。所以咱們仔細(xì)來(lái)感受一下這一個(gè)過(guò)程,抓一抓里面的關(guān)鍵詞。“商品加入購(gòu)物車” 就會(huì)導(dǎo)致 “推薦同類商品”。是不是和咱們上面那一段的描述有些類似了?所以仔細(xì)觀察之后,我們可以捕獲出一個(gè)領(lǐng)域?qū)ο髞?lái),該對(duì)象您可能將它命名為(ProductAddedEvent)。

為什么我們要將它命名為過(guò)去時(shí)呢?這也是印證了開(kāi)頭那句話“動(dòng)作發(fā)生了之后”。當(dāng)該事件被捕獲了之后,就會(huì)將事件信息傳遞給“推薦商品”聚合根,執(zhí)行相應(yīng)處理邏輯。

那么事件的來(lái)源是哪里呢?“用戶點(diǎn)擊”,“網(wǎng)頁(yè)響應(yīng)” 這些都不是哦!記住,我們要深刻關(guān)心領(lǐng)域?qū)ο?#xff0c;剛才所說(shuō)的情況顯然與咱們的領(lǐng)域?qū)ο笠稽c(diǎn)兒關(guān)系也沒(méi)有。所以我們可以很自然的將目光轉(zhuǎn)向到“購(gòu)物車”,“購(gòu)物車”可能就是一個(gè)聚合根,它會(huì)有一個(gè)叫做“添加商品”的行為,當(dāng)該行為完成之后就會(huì)引發(fā)一個(gè)“商品添加完成”的事件。

經(jīng)過(guò)整理之后我們可能會(huì)得到一個(gè)這樣的流程:

所以您會(huì)發(fā)現(xiàn),領(lǐng)域事件一方面充當(dāng)了描述領(lǐng)域信息的作用,一方面承接了不同聚合根之間的交互。當(dāng)然事件不一定只有一個(gè),被影響的領(lǐng)域?qū)ο笠膊灰欢ㄖ挥幸粋€(gè)。就好比“推薦商品”受到了“商品添加完成”事件之后,它自己也能產(chǎn)生一個(gè)另外的領(lǐng)域事件傳遞給下游。

思維的轉(zhuǎn)換

到這里您或許會(huì)感到使用領(lǐng)域事件和以往咱們捕獲其他對(duì)象不太一樣,比如捕獲值對(duì)象、實(shí)體等。因?yàn)閷?duì)于領(lǐng)域事件來(lái)說(shuō),它可能是“隱式”,我們沒(méi)有直觀的感受它的存在。

所以,請(qǐng)仔細(xì)的考慮這一點(diǎn):當(dāng)您要使用領(lǐng)域事件時(shí),您將認(rèn)同您的項(xiàng)目需要以事件作為中心。而項(xiàng)目中的各個(gè)領(lǐng)域?qū)ο蠖紝⒁援a(chǎn)生、發(fā)布領(lǐng)域事件完成一系列的交互流程。

這里我摘錄了《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)模式、原理與實(shí)踐》中的一段話分享給大家:“領(lǐng)域事件將會(huì)在領(lǐng)域?qū)<乙黄疬M(jìn)行的知識(shí)提煉環(huán)節(jié)中揭示出來(lái)。揭示領(lǐng)域事件是如此有價(jià)值,DDD實(shí)踐者都擁有創(chuàng)新的知識(shí)提煉技術(shù)來(lái)進(jìn)行實(shí)踐以便讓其更專注于事件,比如事件風(fēng)暴。不過(guò),使用這些創(chuàng)新技術(shù)會(huì)帶來(lái)新的挑戰(zhàn)。既然概念化的模型都是以事件為中心的,那么代碼也需要以事件為中心,以便它能夠表述概念化模型。這就是領(lǐng)域事件設(shè)計(jì)模式所帶來(lái)的價(jià)值。”

所以在大多數(shù)時(shí)候您將感受到項(xiàng)目逐漸具有 EDA(事件驅(qū)動(dòng)架構(gòu))的風(fēng)格。而此時(shí),您可能會(huì)聯(lián)想到DDD中的另外一種模式:事件溯源(EventSource),認(rèn)為自己必須要采用事件溯源來(lái)建立您的ddd項(xiàng)目。其實(shí)這并不是一定的,采用領(lǐng)域事件和使用事件溯源是沒(méi)有直接關(guān)系的,雖然領(lǐng)域事件會(huì)幫助事件溯源完成的更好。

捕獲領(lǐng)域事件

結(jié)合上面的介紹,您可能已經(jīng)對(duì)發(fā)現(xiàn)領(lǐng)域事件有一點(diǎn)感覺(jué)了。當(dāng)聚合與聚合之間具有交互關(guān)系時(shí),我們往往會(huì)發(fā)現(xiàn)他們之間會(huì)存在某個(gè)領(lǐng)域事件來(lái)引發(fā)這系列行為。

如果與領(lǐng)域?qū)<医徽剷r(shí),發(fā)現(xiàn)了這樣的關(guān)鍵詞匯:“當(dāng)………………”、“如果A完成之后,那么…………”,“發(fā)生…………的時(shí)候”。這些詞匯可能在隱式的告訴您,該處也許存在著“領(lǐng)域事件”對(duì)象。

內(nèi)部事件 and 外部事件

在使用領(lǐng)域事件之前,我們必須要知道事件其實(shí)被劃分成了:“內(nèi)部”和“外部”。就正如它的描述一樣,內(nèi)部的領(lǐng)域事件發(fā)生在邊界之內(nèi),而外部的事件發(fā)生在邊界之外(比如微服務(wù)A產(chǎn)生了一個(gè)事件,而微服務(wù)B會(huì)受到該事件的影響)。

在Microsoft關(guān)于ESHOP案例的指導(dǎo)書(shū)籍《.NET 微服務(wù) - 體系結(jié)構(gòu)》?中,將其命名為“領(lǐng)域事件和集成事件”:

該圖也形象的說(shuō)明了基于一個(gè)邊界內(nèi)的內(nèi)部事件是如何交互的:

外部的事件往往需要一些基礎(chǔ)結(jié)構(gòu)來(lái)實(shí)現(xiàn)遠(yuǎn)程服務(wù)之間的進(jìn)程間和分布式通信,比如rabbitMQ,kafka等。本篇文章重點(diǎn)講解內(nèi)容為內(nèi)部的領(lǐng)域事件,關(guān)于外部的事件將會(huì)在后期《分布式中的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》系列中為大家介紹。

可選 Or 必須

那么是否我的DDD項(xiàng)目就必須使用“領(lǐng)域事件”呢?也許您在網(wǎng)上從來(lái)沒(méi)有見(jiàn)到過(guò)這樣的問(wèn)題,因此也沒(méi)有該問(wèn)題的確切性答案。關(guān)于該問(wèn)題,我個(gè)人覺(jué)得答案是“不一定”。

就像上文說(shuō)的一樣,如果您開(kāi)始使用領(lǐng)域事件,那么就證明您的項(xiàng)目和思維將轉(zhuǎn)換為“以事件作為中心”。領(lǐng)域中大部分的交互都將以事件的方式來(lái)呈現(xiàn)。所以與其考慮“我的DDD項(xiàng)目就必須使用“領(lǐng)域事件””這個(gè)問(wèn)題,還不如轉(zhuǎn)換為:“我是否需要用事件作為中心來(lái)考慮問(wèn)題?”。

所以,該問(wèn)題的答案就取決于您自己了。這也是為什么您會(huì)在某些DDD框架或者DDD項(xiàng)目中沒(méi)有發(fā)現(xiàn)“領(lǐng)域事件”的原因之一。

那么,如果不使用事件來(lái)建模,聚合與聚合之間是如何進(jìn)行交互的呢?請(qǐng)看下文↓。

領(lǐng)域事件 VS 領(lǐng)域服務(wù)

我利用搜索引擎進(jìn)行了大量的查找,沒(méi)有發(fā)現(xiàn)任何關(guān)于“領(lǐng)域事件” 和 “領(lǐng)域服務(wù)”之間的對(duì)比內(nèi)容。但是我認(rèn)為這兩者卻有著很多相似的地方。當(dāng)Evans在初次提出領(lǐng)域驅(qū)動(dòng)的概念時(shí),是沒(méi)有考慮領(lǐng)域事件的,那么也就意味著我們能夠通過(guò)原有的領(lǐng)域?qū)ο笸瓿深I(lǐng)域建模和業(yè)務(wù)流程。

回到剛才那個(gè)問(wèn)題,聚合與聚合之間只能通過(guò)事件完成操作嗎?不一定。“領(lǐng)域服務(wù)”也承擔(dān)著領(lǐng)域?qū)ο笈c領(lǐng)域?qū)ο筠D(zhuǎn)換的功能。

先回顧一下咱們?cè)陬I(lǐng)域服務(wù)章節(jié)了解到的部分內(nèi)容:

當(dāng)我們發(fā)現(xiàn)一個(gè)操作無(wú)法賦予一個(gè)實(shí)體或者值對(duì)象,且該操作又對(duì)業(yè)務(wù)流程很重要時(shí),我們往往需要使用領(lǐng)域服務(wù)
通過(guò)A和B,得到一個(gè)C。
A需要一個(gè)繁瑣的內(nèi)部策略才能得到一個(gè)結(jié)果B。(ps: A,B,C指的是領(lǐng)域?qū)ο笾械闹祵?duì)象或者實(shí)體)

所以這也意味著,領(lǐng)域服務(wù)內(nèi)部可以對(duì)多個(gè)領(lǐng)域?qū)ο?#xff08;比如聚合根)進(jìn)行操作。所以某些DDD框架將領(lǐng)域服務(wù)作為完成流程操作的主要工具,允許使用者在領(lǐng)域服務(wù)中注入多個(gè)倉(cāng)儲(chǔ),從而對(duì)多個(gè)聚合根進(jìn)行操作。

而“領(lǐng)域事件”呢,它通過(guò)發(fā)布領(lǐng)域事件來(lái)達(dá)到不同領(lǐng)域?qū)ο蟮慕换ァ?/p>

那么到底應(yīng)該使用“領(lǐng)域服務(wù)”還是“領(lǐng)域事件”呢?先回答自己是否需要引入事件模型。如果“是”,那么請(qǐng)優(yōu)先考慮使用領(lǐng)域事件。

這是很容易讓人頭暈的兩個(gè)對(duì)象,下面我將用兩句話讓您感受他們的使用場(chǎng)景:

A:快遞在入庫(kù)時(shí)需要進(jìn)行規(guī)格檢查,比如是否超重等
該場(chǎng)景,我們除了引入“快遞”這一聚合根之外,沒(méi)有引入其他領(lǐng)域?qū)ο蟆D敲创颂幍摹皺z查”操作,該行為應(yīng)該交給誰(shuí)呢?給“快遞”?快遞自己檢查自己?顯然不對(duì),所以當(dāng)某行為不屬于一個(gè)實(shí)體或者值對(duì)象時(shí),我們就需要引入一個(gè)領(lǐng)域服務(wù)了。

B:當(dāng)快遞被投遞到營(yíng)業(yè)點(diǎn)時(shí),證明快遞已經(jīng)到達(dá),配送員將打電話給用戶進(jìn)行派送。
該場(chǎng)景中,我們已經(jīng)發(fā)現(xiàn)了有“快遞”、“營(yíng)業(yè)點(diǎn)”、“快遞員”等領(lǐng)域?qū)ο?#xff0c;如果要完成一個(gè)“快遞到達(dá)”的用例,我們會(huì)如何操作呢?調(diào)用"營(yíng)業(yè)點(diǎn)"的“收納進(jìn)快遞”,并且接下來(lái)是調(diào)用“快遞員”的“配送快遞”。此處涉及到多個(gè)聚合根之間的交互,那么是選用領(lǐng)域服務(wù)還是領(lǐng)域事件呢?如果您基于事件建模,可以采用領(lǐng)域事件,反之,您可以使用領(lǐng)域服務(wù)。

如果您開(kāi)始嘗試DDD項(xiàng)目,我建議您優(yōu)先采用事件建模的方式。也就是說(shuō),考慮采用領(lǐng)域事件。將聚合根與聚合根之間的交互動(dòng)作通過(guò)領(lǐng)域事件來(lái)傳達(dá),而將領(lǐng)域?qū)ο蟮牟呗赃\(yùn)算交由領(lǐng)域服務(wù)完成。更清晰的劃分它倆之間的職責(zé)。

實(shí)踐方案

實(shí)踐方案主要采用了Jimmy Bogard所提出的領(lǐng)域事件實(shí)現(xiàn)方案。聚合根中保持領(lǐng)域事件的集合,通過(guò)事件分配器將事件分配給對(duì)應(yīng)的處理事件。

因此我們可以先建立幾個(gè)接口:IDomainEvent(表明該類為領(lǐng)域事件)、IDomainEventHandler(用于攔截處理領(lǐng)域事件)、IEventDispatcher(事件分配器,將領(lǐng)域事件分發(fā)給處理程序)。

復(fù)制代碼

public interface IDomainEvent { }public interface IDomainEventHandler<in TDomainEvent>where TDomainEvent : IDomainEvent {Task HandleAysnc(TDomainEvent domainEvent, CancellationToken cancellationToken = default); }public interface IEventDispatcher {Task DispatchAsync<TDomainEvent>(TDomainEvent domainEvent,CancellationToken cancellationToken = default) where TDomainEvent :IDomainEvent; }

然后還需要給聚合根添加上一些方法,便于它能夠保留領(lǐng)域事件在實(shí)例中:

復(fù)制代碼

public abstract class AggregateRoot<TKey> {public virtual TKey Id { get; set; }protected List<IDomainEvent> _domainEvents = new List<IDomainEvent>();public virtual void AddDomainEvent(IDomainEvent domainEvent)=> _domainEvents.Add(domainEvent);public virtual void RemoveDomainEvent(IDomainEvent domainEvent)=> _domainEvents.Remove(domainEvent);public List<IDomainEvent> GetDomainEvents()=> _domainEvents; }

最后,在倉(cāng)儲(chǔ)進(jìn)行持久化之前,通過(guò)事件分發(fā)器將保持在聚合根實(shí)例上的領(lǐng)域事件分發(fā)給對(duì)應(yīng)的事件處理程序:

復(fù)制代碼

// EF Core DbContext public class OrderingContext : DbContext {public async Task<bool> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)){//Get aggregateRootvar aggregateRoots = dbContext.ChangeTracker.Entries().ToList();// Dispatch Domain Events collection.await _eventDispatcher.DispatchAsync(aggregateRoots,cancellationToken);// After this line runs, all the changes (from the Command Handler and Domain// event handlers) performed through the DbContext will be committedvar result = await base.SaveChangesAsync();} }

由于篇幅有限,上面的實(shí)現(xiàn)方案只是給了大家一個(gè)思路,所以缺少了一些實(shí)現(xiàn),如果您有需要可以聯(lián)系我,我提取一個(gè)小Demo上傳至Github。

關(guān)于另外的實(shí)現(xiàn)方案,您可以查看微軟Eshop教程。

為什么選取領(lǐng)域事件

為什么我會(huì)建議您優(yōu)先考慮使用領(lǐng)域事件呢?為了后期能夠更容易的拆解項(xiàng)目為微服務(wù)。假如咱們都是將聚合根之間的交互通過(guò)領(lǐng)域服務(wù)來(lái)完成,比如現(xiàn)在有一個(gè)領(lǐng)域服務(wù)A,它需要幫助聚合根A和聚合根B完成操作:

復(fù)制代碼

public class DomainServiceA {DomainServiceA(IRepositoryA repositoryA,IRepositoryB repositoryB); }

在該領(lǐng)域服務(wù)中,以來(lái)了聚合根A、B的存儲(chǔ)庫(kù)。現(xiàn)在A和B位于同一個(gè)服務(wù)中,這可以很好的運(yùn)行。但是如果有一天,B需要被獨(dú)立出去,單獨(dú)成為一個(gè)服務(wù)怎么辦呢?該領(lǐng)域服務(wù)不得不進(jìn)行更改。

而加入我們通過(guò)領(lǐng)域事件來(lái)進(jìn)行流轉(zhuǎn),當(dāng)聚合B被拆分出去之后,假如B需要A發(fā)布的某個(gè)事件,那么B只需要在自己的項(xiàng)目中添加一個(gè)該事件的類型就可以了,而不需要修改其他邏輯。(也許需要將內(nèi)部事件轉(zhuǎn)換為外部事件,但是核心業(yè)務(wù)代碼是不會(huì)更改的)。

所以構(gòu)建項(xiàng)目初期,我們?cè)谶x型時(shí)要進(jìn)行長(zhǎng)遠(yuǎn)的考慮。

總結(jié)

本次我們介紹了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中的領(lǐng)域事件。“如果捕獲領(lǐng)域事件?”,“DDD是否一定需要領(lǐng)域事件?”相信這些問(wèn)題,看到這里您心里已經(jīng)有了自己的答案。

領(lǐng)域事件能夠幫助我們更好的描述領(lǐng)域中各個(gè)對(duì)象之間的狀態(tài),就如同本文剛開(kāi)始所提及到的觀點(diǎn):“如果發(fā)現(xiàn)有一項(xiàng)動(dòng)作發(fā)生了之后,與之關(guān)聯(lián)的其他領(lǐng)域?qū)ο髮?huì)受到波及。” 將這些提取建模為領(lǐng)域事件,將對(duì)您的項(xiàng)目帶來(lái)很好的收益。

總結(jié)

以上是生活随笔為你收集整理的如何运用领域驱动设计 - 领域事件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。