烽火
哈嘍大家好,老張又見(jiàn)面了桩撮,這兩天被各個(gè)平臺(tái)的“雞湯貼”差點(diǎn)亂了心神敦第,博客園如此,簡(jiǎn)書(shū)亦如此距境,還好群里小伙伴及時(shí)提醒申尼,路還很長(zhǎng)垮卓,這些小事兒就隨風(fēng)而去吧垫桂,這周本不打算更了,但是被群里小伙伴“催稿”了粟按,至少也是對(duì)我的一個(gè)肯定吧诬滩,又開(kāi)始熬夜中,請(qǐng)@初久小伙伴留言灭将,我不知道你的地址疼鸟,就不放鏈接了。
收住庙曙,言歸正傳空镜,上次咱們說(shuō)到了領(lǐng)域命令驗(yàn)證《九 ║從軍事故事中,明白領(lǐng)域命令驗(yàn)證(上)》捌朴,也介紹了其中的兩個(gè)角色——領(lǐng)域命令模型和命令驗(yàn)證吴攒,這些都是屬于領(lǐng)域?qū)拥母拍睿?dāng)然這里的內(nèi)容是 命令 砂蔽,查詢就當(dāng)然不需要這個(gè)了洼怔,查詢的話,直接從倉(cāng)儲(chǔ)中獲取值就行了左驾,很簡(jiǎn)單镣隶。也沒(méi)人問(wèn)我問(wèn)題极谊,那我就權(quán)當(dāng)大家已經(jīng)對(duì)上篇都看懂了,這里就不再贅述安岂。不知道大家是否還記得上篇文章末尾轻猖,提到的幾個(gè)問(wèn)題,我這里再提一下嗜闻,就是今天的提綱了蜕依,如果你今天看完本篇,這幾個(gè)問(wèn)題能回答上來(lái)琉雳,那恭喜样眠,你就明白了今天所講的問(wèn)題:
1、命令模型RegisterStudentCommand 放到 Controller 中真的好么翠肘?//我們平時(shí)都是這么做的
2檐束、如果不放到Controller里調(diào)用,我們?nèi)绻{(diào)用束倍?在 Service里么被丧?//也是一個(gè)辦法,至少Controller干凈了绪妹,但是 Service 就重了
3甥桂、驗(yàn)證的結(jié)果又如何獲取并在前臺(tái)展示呢?//本文會(huì)先用一個(gè)錯(cuò)誤的方法來(lái)說(shuō)明問(wèn)題邮旷,下篇會(huì)用正確的
4黄选、如何把領(lǐng)域模型 Student 從應(yīng)用層 StudentAppService 解耦出去( Register()方法中 )。//本文重點(diǎn)婶肩,中介者模式
好啦办陷,簡(jiǎn)單先寫(xiě)這四個(gè)問(wèn)題吧,這個(gè)時(shí)候你可以先不要從 Github 上拉取代碼律歼,先看著目前手中的代碼民镜,然后思考這四個(gè)問(wèn)題,如果要是自己险毁,或者咱們以前是怎么做的制圈,如果你看過(guò)以后會(huì)有一些新的認(rèn)識(shí)和領(lǐng)悟,請(qǐng)幫忙評(píng)論一下畔况,捧個(gè)人場(chǎng)嘛鲸鹦,是吧??。好啦问窃,今天的東西可能有點(diǎn)兒多亥鬓,請(qǐng)做好大概半個(gè)小時(shí)的準(zhǔn)備,當(dāng)然這半個(gè)小時(shí)你需要思考域庇,要是走馬觀花嵌戈,肯定是收獲沒(méi)有那么多的覆积,代碼已經(jīng)更新了,記得看完的時(shí)候 pull 一下代碼熟呛。
讀前必讀
1宽档、本文中可能會(huì)涉及比較多的依賴注入,請(qǐng)一定要看清楚庵朝,因?yàn)檫@是第二個(gè)系列了吗冤,有時(shí)候小細(xì)節(jié)就不點(diǎn)明了,需要大家有一定的基礎(chǔ)九府,可以看我第一個(gè)系列椎瘟。
2、這三篇核心內(nèi)容侄旬,都是重點(diǎn)在領(lǐng)域?qū)臃挝担?qǐng)一定要多思考。
3儡羔、文章不僅有代碼宣羊,更多的是理解,比如用聯(lián)合國(guó)的栗子來(lái)說(shuō)明中介者模式汰蜘,請(qǐng)務(wù)必要多思考仇冯。
零、今天實(shí)現(xiàn)左下角淺紫色的部分
一族操、什么是中介者模式苛坚?
1、中介模式的概念
這個(gè)其實(shí)很好理解坪创,單單從名字上大家也都能理解它是一個(gè)什么模式炕婶,因?yàn)楸疚牡闹攸c(diǎn)不是一個(gè)講解什么是23種設(shè)計(jì)模式的姐赡,大家有興趣的可以好好的買本書(shū)莱预,或者找找資料,好好项滑,主要是思想依沮,不需要自己寫(xiě)一個(gè)項(xiàng)目,如果大家有需要枪狂,可以留言危喉,我以后單寫(xiě)一篇文章,介紹中介者模式州疾。
這里就摘抄一段定義吧:
中介者模式是一個(gè)行為設(shè)計(jì)模式辜限,它允許我們公開(kāi)一個(gè)統(tǒng)一的接口,系統(tǒng)的 **不同部分 **可以通過(guò)該接口進(jìn)行 通信严蓖,而 **不需要 **顯示的相互作用薄嫡;
適用場(chǎng)景:如果一個(gè)系統(tǒng)的各個(gè)組件之間看起來(lái)有太多的直接關(guān)系(就比如我們系統(tǒng)中那么多模型對(duì)象氧急,下邊會(huì)解釋),這個(gè)時(shí)候則需要一個(gè)中心控制點(diǎn)毫深,以便各個(gè)組件可以通過(guò)這個(gè)中心控制點(diǎn)進(jìn)行通信吩坝;
該模式促進(jìn)松散耦合的方式是:確保組件的交互是通過(guò)這個(gè)中心點(diǎn)來(lái)進(jìn)行處理的,而不是通過(guò)顯示的引用彼此哑蔫;
比如系統(tǒng)和各個(gè)硬件钉寝,系統(tǒng)作為中介者,各個(gè)硬件作為同事者闸迷,當(dāng)一個(gè)同事的狀態(tài)發(fā)生改變的時(shí)候嵌纲,不需要告訴其他每個(gè)硬件自己發(fā)生了變化,只需要告訴中介者系統(tǒng)腥沽,系統(tǒng)會(huì)通知每個(gè)硬件某個(gè)硬件發(fā)生了改變疹瘦,其他的硬件會(huì)做出相應(yīng)的變化;
這樣巡球,之前是網(wǎng)狀結(jié)構(gòu)言沐,現(xiàn)在變成了以中介者為中心的星星結(jié)構(gòu):
是不是挺像一個(gè)容器的,他自己把控著整個(gè)流程酣栈,和每一個(gè)對(duì)象都有或多或少险胰,或近或遠(yuǎn)的聯(lián)系,多個(gè)對(duì)象之間不用理睬其他對(duì)象發(fā)生了什么矿筝,只是負(fù)責(zé)自己的模塊就好起便,然后把消息發(fā)給中介者,讓中介者再分發(fā)給其他的具體對(duì)象窖维,從而實(shí)現(xiàn)通訊 —— 這個(gè)思想就是中介者的核心思想榆综,而且也是DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的核心思想之一( 還有一個(gè)核心思想是領(lǐng)域設(shè)計(jì)的思想 ),這里你可能還是不那么直觀铸史,我剛剛花了一個(gè)小時(shí)鼻疮,對(duì)咱們的DDD框架中的中介者模式畫(huà)了一個(gè)圖,相信會(huì)有一些新的認(rèn)識(shí)琳轿,在下邊第 3 點(diǎn)會(huì)看到判沟,請(qǐng)耐心往下看。
2崭篡、中介模式的原理
這里有一個(gè)聯(lián)合國(guó)的栗子挪哄,也是常用來(lái)介紹和解釋中介者模式的栗子:
抽象中介者(AbstractMediator):定義中介者和各個(gè)同事者之間的通信的接口;//比如下文提到的 抽象聯(lián)合國(guó)機(jī)構(gòu)
抽象同事者(AbstractColleague):定義同事者和中介者通信的接口琉闪,實(shí)現(xiàn)同事的公共功能迹炼;//比如下文中的 抽象國(guó)家
中介者(ConcreteMediator):需要了解并且維護(hù)每個(gè)同事對(duì)象,實(shí)現(xiàn)抽象方法颠毙,負(fù)責(zé)協(xié)調(diào)和各個(gè)具體的同事的交互關(guān)系斯入;//比如下文中的 聯(lián)合國(guó)安理會(huì)
同事者(ConcreteColleague):實(shí)現(xiàn)自己的業(yè)務(wù)拿霉,并且實(shí)現(xiàn)抽象方法,和中介者進(jìn)行通信咱扣;//比如下文的 美國(guó)绽淘、英國(guó)、伊拉克等國(guó)家
注意:其中同事者是多個(gè)同事相互影響的才能叫做同事者闹伪;
還是希望大家能好好看看沪铭,好好想想,如果你還沒(méi)有接觸過(guò)這個(gè)中介者模式偏瓤,如果了解并使用過(guò)杀怠,就簡(jiǎn)單看一看,要是你能把這個(gè)小栗子看懂了厅克,那下邊的內(nèi)容赔退,就很容易了,甚至是以后的內(nèi)容就如魚(yú)得水了证舟,畢竟DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)兩個(gè)核心就是:CQRS讀寫(xiě)分離 + 中介者模式 硕旗。
這個(gè)下邊是一個(gè)簡(jiǎn)單的Demo,可以簡(jiǎn)單的看一看:
namespace 中介者模式
{
class Program
{
static void Main(string[] args)
{
//實(shí)例化 具體中介者 聯(lián)合國(guó)安理會(huì)
UnitedNationsSecurityCouncil UNSC = new UnitedNationsSecurityCouncil();
//實(shí)例化一個(gè)美國(guó)
USA c1 = new USA(UNSC);
//實(shí)例化一個(gè)里拉開(kāi)
Iraq c2 = new Iraq(UNSC);
//將兩個(gè)對(duì)象賦值給安理會(huì)
//具體的中介者必須知道全部的對(duì)象
UNSC.Colleague1 = c1;
UNSC.Colleague2 = c2;
//美國(guó)發(fā)表聲明女责,伊拉克接收到
c1.Declare("不準(zhǔn)研制核武器漆枚,否則要發(fā)動(dòng)戰(zhàn)爭(zhēng)!");
//伊拉克發(fā)表聲明抵知,美國(guó)收到信息
c2.Declare("我們沒(méi)有核武器墙基,也不怕侵略。");
Console.Read();
}
}
/// <summary>
/// 聯(lián)合國(guó)機(jī)構(gòu)抽象類
/// 抽象中介者
/// </summary>
abstract class UnitedNations
{
/// <summary>
/// 聲明
/// </summary>
/// <param name="message">聲明信息</param>
/// <param name="colleague">聲明國(guó)家</param>
public abstract void Declare(string message, Country colleague);
}
/// <summary>
/// 聯(lián)合國(guó)安全理事會(huì),它繼承 聯(lián)合國(guó)機(jī)構(gòu)抽象類
/// 具體中介者
/// </summary>
class UnitedNationsSecurityCouncil : UnitedNations
{
//美國(guó) 具體國(guó)家類1
private USA colleague1;
//伊拉克 具體國(guó)家類2
private Iraq colleague2;
public USA Colleague1
{
set { colleague1 = value; }
}
public Iraq Colleague2
{
set { colleague2 = value; }
}
//重寫(xiě)聲明函數(shù)
public override void Declare(string message, Country colleague)
{
//如果美國(guó)發(fā)布的聲明刷喜,則伊拉克獲取消息
if (colleague == colleague1)
{
colleague2.GetMessage(message);
}
else//反之亦然
{
colleague1.GetMessage(message);
}
}
}
/// <summary>
/// 國(guó)家抽象類
/// </summary>
abstract class Country
{
//聯(lián)合國(guó)機(jī)構(gòu)抽象類
protected UnitedNations mediator;
public Country(UnitedNations mediator)
{
this.mediator = mediator;
}
}
/// <summary>
/// 美國(guó) 具體國(guó)家類
/// </summary>
class USA : Country
{
public USA(UnitedNations mediator)
: base(mediator)
{
}
//聲明方法残制,將聲明內(nèi)容較給抽象中介者 聯(lián)合國(guó)
public void Declare(string message)
{
//通過(guò)抽象中介者發(fā)表聲明
//參數(shù):信息+類
mediator.Declare(message, this);
}
//獲得消息
public void GetMessage(string message)
{
Console.WriteLine("美國(guó)獲得對(duì)方信息:" + message);
}
}
/// <summary>
/// 伊拉克 具體國(guó)家類
/// </summary>
class Iraq : Country
{
public Iraq(UnitedNations mediator)
: base(mediator)
{
}
//聲明方法,將聲明內(nèi)容較給抽象中介者 聯(lián)合國(guó)
public void Declare(string message)
{
//通過(guò)抽象中介者發(fā)表聲明
//參數(shù):信息+類
mediator.Declare(message, this);
}
//獲得消息
public void GetMessage(string message)
{
Console.WriteLine("伊拉克獲得對(duì)方信息:" + message);
}
}
}
最終的結(jié)果是:
從這個(gè)小栗子中掖疮,也許你能看出來(lái)初茶,美國(guó)和伊拉克之間,對(duì)象之間并沒(méi)有任何的交集和聯(lián)系氮墨,但是他們之間卻發(fā)生了通訊纺蛆,各自獨(dú)立吐葵,但是又相互通訊规揪,這個(gè)不就是很好的實(shí)現(xiàn)了解耦的作用么!一切都是通過(guò)中介者來(lái)控制温峭,當(dāng)然這只是一個(gè)小栗子猛铅,咱們推而廣之:
命令模式、消息通知模型凤藏、領(lǐng)域模型等奸忽,內(nèi)部運(yùn)行完成后堕伪,將產(chǎn)生的信息拋向給中介者,然后中介者再根據(jù)情況分發(fā)給各個(gè)成員(如果又需要的)栗菜,這樣就實(shí)現(xiàn)多個(gè)對(duì)象的解耦欠雌,而且也達(dá)到同步的作用,當(dāng)然還有一些輔助知識(shí):異步疙筹、注入富俄、事件等,咱們慢慢學(xué)習(xí)而咆,至少現(xiàn)在中介者模式的思想和原理你應(yīng)該都懂了霍比。
3、本項(xiàng)目是如何使用中介者模式的
相信如果你是從我的第一篇文章看下去的暴备,一定會(huì)以下幾個(gè)模型很熟悉:視圖模型悠瞬、領(lǐng)域模型、命令模型涯捻、驗(yàn)證(上次說(shuō)的)浅妆、還有沒(méi)有說(shuō)到的通知模型,如果你對(duì)這幾個(gè)名稱還很朦朧障癌,請(qǐng)現(xiàn)在先在腦子里仔細(xì)想一想狂打,不然下邊的可能會(huì)亂,如果你一看到名字就能理解都是干什么的混弥,都是什么作用趴乡,那好,請(qǐng)看下邊的關(guān)系圖蝗拿。
首先咱們看看晾捏,如果不適用中介者模式,會(huì)是什么狀態(tài):
這個(gè)時(shí)候你會(huì)說(shuō)哀托,不惦辛!我不信會(huì)這么復(fù)雜!是真的么仓手?我們的視圖模型肯定和命令模型有交互吧胖齐,命令模型和領(lǐng)域模型肯定也有吧,那命令中有錯(cuò)誤信息吧嗽冒,肯定要交給通知模型的呀伙,說(shuō)到這里,你應(yīng)該會(huì)感覺(jué)可能真的有一些復(fù)雜的交互添坊,當(dāng)然剿另!也可能沒(méi)有那么復(fù)雜,我們平時(shí)就是一個(gè)實(shí)體 model 走天下的,錯(cuò)誤信息隨便返回給字符串呀雨女,等等諸如此類谚攒。
如果你承認(rèn)了這個(gè)結(jié)構(gòu)很復(fù)雜,那好氛堕!咱們看看中介者模式會(huì)是什么樣子的馏臭,可能你看著會(huì)更復(fù)雜,但是會(huì)很清晰:
(這可是老張花了一個(gè)小時(shí)畫(huà)的讼稚,兄弟給個(gè)贊??吧)
不知道你看到這里會(huì)不會(huì)腦子一嗡位喂,沒(méi)關(guān)系,等這個(gè)系列說(shuō)完了乱灵,你就會(huì)明白了塑崖,今天咱們就主要說(shuō)的是其中一個(gè)部分,**命令總線 Command Bus痛倚、命令處理程序规婆、工作單元的提交 **這三塊:
從上邊的大圖中,我們看到蝉稳,本來(lái)交織在一起的多個(gè)模型抒蚜,本一條虛擬的流程串了起來(lái),這里邊就包括CQRS讀寫(xiě)分離思想 和 中介者模型耘戚,當(dāng)然還有人說(shuō)是發(fā)布-訂閱模型嗡髓,這個(gè)我還在醞釀,以后的文章會(huì)說(shuō)到收津。雖然對(duì)象還是那么多饿这,但是清晰了起來(lái),多個(gè)對(duì)象之間也沒(méi)有存在一個(gè)很深的聯(lián)系撞秋,讓業(yè)務(wù)之間更加專注自身業(yè)務(wù)长捧。
如果你現(xiàn)在對(duì)中介者模式已經(jīng)有了一定的意識(shí),也知道了它的作用和意思吻贿,那它到底是如何操作的呢串结,請(qǐng)耐心往外看,重點(diǎn)來(lái)了舅列。
二肌割、創(chuàng)建命令總線 Command Bus
1、創(chuàng)建一個(gè)中介處理程序接口
在我們的核心領(lǐng)域?qū)?Christ3D.Domain.Core 中帐要,新建 Bus 文件夾把敞,然后創(chuàng)建中介處理程序接口 IMediatorHandler.cs
namespace Christ3D.Domain.Core.Bus
{
/// <summary>
/// 中介處理程序接口
/// 可以定義多個(gè)處理程序
/// 是異步的
/// </summary>
public interface IMediatorHandler
{
/// <summary>
/// 發(fā)布命令,將我們的命令模型發(fā)布到中介者模塊
/// </summary>
/// <typeparam name="T"> 泛型 </typeparam>
/// <param name="command"> 命令模型宠叼,比如RegisterStudentCommand </param>
/// <returns></returns>
Task SendCommand<T>(T command) where T : Command;
}
}
發(fā)布命令:就好像我們調(diào)用某招聘平臺(tái)先巴,發(fā)布了一個(gè)招聘命令其爵。
2冒冬、一個(gè)低調(diào)的中介者工具 —— MediatR
微軟官方eshopOnContainer開(kāi)源項(xiàng)目中使用到了該工具伸蚯,
mediatR 是一種中介工具,解耦了消息處理器和消息之間耦合的類庫(kù)简烤,支持跨平臺(tái) .net Standard和.net framework
https://github.com/jbogard/MediatR/wiki 這里是原文地址剂邮。其作者也是Automapper的作者。
功能要是簡(jiǎn)述的話就倆方面:
request/response 請(qǐng)求響應(yīng) //咱們就采用這個(gè)方式
pub/sub 發(fā)布訂閱
使用方法:通過(guò) .NET CORE 自帶的 IoC 注入
引用 MediatR nuget:install-package MediatR
引用IOC擴(kuò)展 nuget:installpackage MediatR.Extensions.Microsoft.DependencyInjection //擴(kuò)展包
使用方式:
services.AddMediatR(typeof(MyxxxHandler));//單單注入某一個(gè)處理程序
或
services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);//目的是為了掃描Handler的實(shí)現(xiàn)對(duì)象并添加到IOC的容器中
//參考示例
//請(qǐng)求響應(yīng)方式(request/response)横侦,三步走:
//步驟一:創(chuàng)建一個(gè)消息對(duì)象挥萌,需要實(shí)現(xiàn)IRequest,或IRequest<> 接口,表明該對(duì)象是處理器的一個(gè)對(duì)象
public class Ping : IRequest<string>
{
}
//步驟二:創(chuàng)建一個(gè)處理器對(duì)象
public class PingHandler : IRequestHandler<Ping, string>
{
public Task<string> Handle(Ping request, CancellationToken cancellationToken)
{
return Task.FromResult("老張的哲學(xué)");
}
}
//步驟三:最后枉侧,通過(guò)mediator發(fā)送一個(gè)消息
var response = await mediator.Send(new Ping());
Debug.WriteLine(response); // "老張的哲學(xué)"
3引瀑、項(xiàng)目中實(shí)現(xiàn)中介處理程序接口
這里就不講解為什么要使用 MediatR 來(lái)實(shí)現(xiàn)我們的中介者模式了,因?yàn)槲覜](méi)有找到其他的??榨馁,具體的使用方法很簡(jiǎn)單憨栽,就和我們的緩存 IMemoryCache 一樣,通過(guò)注入翼虫,調(diào)用該接口即可屑柔,如果你還是不清楚的話,先往下看吧珍剑,應(yīng)該也能看懂掸宛。
添加 nuget 包:MediatR
注意:我這里把包安裝到了Christ3D.Domain.Core 核心領(lǐng)域?qū)恿耍驗(yàn)檫€記得上邊的那個(gè)大圖么招拙,我說(shuō)到的唧瘾,一條貫穿項(xiàng)目的線,所以這個(gè)中介處理程序接口在其他地方也用的到(比如領(lǐng)域?qū)樱┍鸱铮晕以诤诵念I(lǐng)域?qū)优蓿惭b了這個(gè)nuget包。注意安裝包后闻妓,需要編譯下當(dāng)前項(xiàng)目菌羽。
新建一個(gè)類庫(kù) Christ3D.Infra.Bus
當(dāng)然你也可以把它和接口 IMediatorHandler 放在一起,不過(guò)我個(gè)人感覺(jué)不是很舒服由缆,因?yàn)檫@個(gè)具體的實(shí)現(xiàn)過(guò)程注祖,不是我們領(lǐng)域設(shè)計(jì)需要知道的,就好像我們的 EFCore 倉(cāng)儲(chǔ)均唉,我們就是在領(lǐng)域?qū)邮浅浚⒘藗}(cāng)儲(chǔ)接口,然后再在基礎(chǔ)設(shè)施數(shù)據(jù)層 Christ3D.Infrastruct.Data 中實(shí)現(xiàn)的舔箭,所以為了保持一致性罩缴,我就新建了這個(gè)類庫(kù)項(xiàng)目蚊逢,用來(lái)實(shí)現(xiàn)我們的中介處理程序接口。
注意下箫章,Bus總線類庫(kù)是需要引用 Domain.Core 核心領(lǐng)域?qū)拥睦雍桑晕覀円院笤?Domain領(lǐng)域?qū)樱苯右?Bus總線層即可檬寂。
實(shí)現(xiàn)我們的中介處理程序接口
namespace Christ3D.Infra.Bus
{ /// <summary>
/// 一個(gè)密封類终抽,實(shí)現(xiàn)我們的中介記憶總線 /// </summary>
public sealed class InMemoryBus : IMediatorHandler
{ //構(gòu)造函數(shù)注入
private readonly IMediator _mediator; public InMemoryBus(IMediator mediator)
{
_mediator = mediator;
} /// <summary>
/// 實(shí)現(xiàn)我們?cè)贗MediatorHandler中定義的接口 /// 沒(méi)有返回值 /// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="command"></param>
/// <returns></returns>
public Task SendCommand<T>(T command) where T : Command
{ return _mediator.Send(command);//這里要注意下 command 對(duì)象
}
}
}
這個(gè)send方法,就是我們的中介者來(lái)替代對(duì)象桶至,進(jìn)行命令的分發(fā)昼伴,這個(gè)時(shí)候你可以會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了,我們F12看看這個(gè)方法:
可以看到 send 方法的入?yún)⒘鸵伲仨毷荕ediarR指定的 IRequest 對(duì)象圃郊,所以,我們需要給我們的 Command命令基類女蜈,再繼承一個(gè)抽象類:
這個(gè)時(shí)候持舆,我們的中介總線就搞定了。
4鞭光、刪除命令模型在Controller中的使用
1吏廉、把領(lǐng)域命令模型 從 controller 中去掉
只需要一個(gè)service調(diào)用即可
這個(gè)時(shí)候我們文字開(kāi)頭的第一個(gè)問(wèn)題就出現(xiàn)了,我們先把 Controller 中的命令模型驗(yàn)證去掉惰许,然后在我們的應(yīng)用層 Service 中調(diào)用席覆,這里先看看文章開(kāi)頭的第二個(gè)問(wèn)題方法(當(dāng)然是不對(duì)的方法):
public void Register(StudentViewModel StudentViewModel)
{
RegisterStudentCommand registerStudentCommand = new RegisterStudentCommand(studentViewMod.........ewModel.Phone); //如果命令無(wú)效,證明有錯(cuò)誤
if (!registerStudentCommand.IsValid())
{
List<string> errorInfo = new List<string>(); //獲取到錯(cuò)誤汹买,請(qǐng)思考這個(gè)Result從哪里來(lái)的 //..... //對(duì)錯(cuò)誤進(jìn)行記錄佩伤,還需要拋給前臺(tái)
ViewBag.ErrorData = errorInfo;
}
_StudentRepository.Add(_mapper.Map<Student>(StudentViewModel));
_StudentRepository.SaveChanges();
}
且不說(shuō)這里邊語(yǔ)法各種有問(wèn)題(比如不能用 ViewBag ,當(dāng)然你可能會(huì)說(shuō)用緩存)晦毙,單單從整體設(shè)計(jì)上就很不舒服生巡,這樣僅僅是從api接口層,挪到了應(yīng)用服務(wù)層见妒,這一塊明明是業(yè)務(wù)邏輯孤荣,業(yè)務(wù)邏輯就是領(lǐng)域問(wèn)題,應(yīng)該放到領(lǐng)域?qū)印?/p>
而且還有文章說(shuō)到的第四個(gè)問(wèn)題须揣,這里也沒(méi)有解決盐股,就是這里依然有領(lǐng)域模型 Student ,沒(méi)有實(shí)現(xiàn)命令模型耻卡、領(lǐng)域模型等的交互通訊疯汁。
說(shuō)到這里,你可能腦子里有了一個(gè)大膽的想法卵酪,還記得上邊說(shuō)的中介者模式么幌蚊,就是很好的實(shí)現(xiàn)了多個(gè)對(duì)象之間的通訊谤碳,還不破壞各自的內(nèi)部邏輯,使他們只關(guān)心自己的業(yè)務(wù)邏輯溢豆,那具體如果使用呢蜒简,請(qǐng)往下看。
5沫换、在 StudentAppService 服務(wù)中臭蚁,調(diào)用中介處理接口
通過(guò)構(gòu)造函數(shù)注入我們的中介處理接口最铁,這個(gè)大家應(yīng)該都會(huì)了吧
//注意這里是要IoC依賴注入的讯赏,還沒(méi)有實(shí)現(xiàn)
private readonly IStudentRepository _StudentRepository; //用來(lái)進(jìn)行DTO
private readonly IMapper _mapper; //中介者 總線
private readonly IMediatorHandler Bus; public StudentAppService(
IStudentRepository StudentRepository,
IMediatorHandler bus,
IMapper mapper
)
{
_StudentRepository = StudentRepository;
_mapper = mapper;
Bus = bus;
}
然后修改服務(wù)方法
public void Register(StudentViewModel StudentViewModel)
{ //這里引入領(lǐng)域設(shè)計(jì)中的寫(xiě)命令 還沒(méi)有實(shí)現(xiàn) //請(qǐng)注意這里如果是平時(shí)的寫(xiě)法,必須要引入Student領(lǐng)域模型冷尉,會(huì)造成污染 //_StudentRepository.Add(_mapper.Map<Student>(StudentViewModel)); //_StudentRepository.SaveChanges();
var registerCommand = _mapper.Map<RegisterStudentCommand>(StudentViewModel);
Bus.SendCommand(registerCommand);
}
最后記得要對(duì)服務(wù)進(jìn)行注入漱挎,這里有兩個(gè)點(diǎn)
1、ConfigureServices 中添加 MediatR 服務(wù)
// Adding MediatR for Domain Events // 領(lǐng)域命令雀哨、領(lǐng)域事件等注入 // 引用包 MediatR.Extensions.Microsoft.DependencyInjection
services.AddMediatR(typeof(Startup));
2磕谅、在我們的 Christ3D.Infra.IoC 項(xiàng)目中,注入我們的中介總線接口
services.AddScoped<IMediatorHandler, InMemoryBus>();
老張說(shuō):這里的注入雾棺,就是指膊夹,每當(dāng)我們?cè)L問(wèn) IMediatorHandler 處理程序的時(shí)候,就是實(shí)例化 InmemoryBus 對(duì)象捌浩。
到了這里放刨,我們才完成了第一步,命令總線的定義尸饺,也就是中介處理接口的定義與使用进统,那具體是如何進(jìn)行分發(fā)的呢,我們又是如何進(jìn)行數(shù)據(jù)持久化浪听,保存數(shù)據(jù)的呢螟碎?請(qǐng)往下看,我們先說(shuō)下工作單元迹栓。
三掉分、工作單元模式 UnitOfWork
博主按:這是一個(gè)很豐富的內(nèi)容,今天就不詳細(xì)說(shuō)明了克伊,留一個(gè)坑酥郭,為以后23種設(shè)計(jì)模式的時(shí)候,再詳細(xì)說(shuō)明答毫!
1褥民、為什么要定義工作單元
首先了解工作單元(Unit of Work)的意圖:維護(hù)受業(yè)務(wù)影響的對(duì)象列表,并且協(xié)調(diào)變化的寫(xiě)入和解決并發(fā)問(wèn)題洗搂。
可以用工作單元來(lái)實(shí)現(xiàn)事務(wù)消返,工作單元就是記錄對(duì)象數(shù)據(jù)變化的對(duì)象载弄。只要開(kāi)始做一些可能對(duì)所要記錄的對(duì)象的數(shù)據(jù)有影響的操作,就會(huì)創(chuàng)建一個(gè)工作單元去記錄這些變化撵颊,所以宇攻,每當(dāng)創(chuàng)建、修改倡勇、或刪除一個(gè)對(duì)象的時(shí)候逞刷,就會(huì)通知工作單元。
2妻熊、如何定義UnitOfWork
1夸浅、在Christ3D.Domain 領(lǐng)域?qū)拥慕涌谖募AInterfaces種,新建工作單元接口 IUnitOfWork.cs
namespace Christ3D.Domain.Interfaces
{ /// <summary>
/// 工作單元接口 /// </summary>
public interface IUnitOfWork : IDisposable
{ //是否提交成功
bool Commit();
}
}
2扔役、在基礎(chǔ)設(shè)施層帆喇,實(shí)現(xiàn)工作單元接口
namespace Christ3D.Infra.Data.UoW
{ /// <summary>
/// 工作單元類 /// </summary>
public class UnitOfWork : IUnitOfWork
{ //數(shù)據(jù)庫(kù)上下文
private readonly StudyContext _context; //構(gòu)造函數(shù)注入
public UnitOfWork(StudyContext context)
{
_context = context;
} //上下文提交
public bool Commit()
{ return _context.SaveChanges() > 0;
} //手動(dòng)回收
public void Dispose()
{
_context.Dispose();
}
}
}
3、記得在IoC層依賴注入
services.AddScoped<IUnitOfWork, UnitOfWork>();
四亿胸、命令處理程序 CommandHandlers
因?yàn)槠ㄌL(zhǎng)了有些暈)和時(shí)間的問(wèn)題坯钦,今天就暫時(shí)先說(shuō)到這里,代碼我已經(jīng)寫(xiě)好了侈玄,并且提交到了Github婉刀,大家如果想看的可以先pull下來(lái),至于為什么這么用以及它的意義序仙,咱們下篇文章再詳細(xì)說(shuō)突颊。其實(shí)整體流程和原理,我在上邊也說(shuō)的很詳細(xì)了诱桂,如果你能根據(jù)聯(lián)合國(guó)的栗子看懂這個(gè)(注意要結(jié)合與依賴注入來(lái)理解)洋丐,那你就是完完全全的理解了,如果下邊的代碼還不是很清楚挥等,沒(méi)關(guān)系奖地,周末大家先看看嘁酿,下周我詳細(xì)給大家講解下。
我這里先給大家列舉下三步走,為下次做準(zhǔn)備:
1济瓢、添加一個(gè)命令處理程序基類 CommandHandler.cs
2涡贱、通過(guò)緩存Memory來(lái)記錄通知信息(錯(cuò)誤方法)
3践付、定義學(xué)生命令處理程序 StudentCommandHandler.cs
五全封、息鼓
今天真沒(méi)想到會(huì)寫(xiě)這么多,看來(lái)還是夜里安靜的時(shí)候更容易寫(xiě)東西榄檬,思路清晰卜范,沒(méi)辦法,我只能把本文拆成兩個(gè)文章了鹿榜。這篇文章我是來(lái)來(lái)回回的刪了寫(xiě)海雪,寫(xiě)了刪锦爵,一個(gè)下午+一個(gè)晚上,大概6個(gè)小時(shí)奥裸,真是很累心的一個(gè)過(guò)程险掀,不過(guò)想想,哪怕有一個(gè)小伙伴能通過(guò)文字學(xué)到東西湾宙,也是極好極開(kāi)心的樟氢,好啦,老張要睡覺(jué)了侠鳄,至于文章的病句埠啃,截圖等,明天再調(diào)整吧畦攘。加油霸妹!