烽煙
哈嘍大家周二好呀,咱們又見面了,上周末掐指一算,距離 圣誕節(jié) 只有 5 周的時(shí)間了(如果你還不知道為啥我要提圣誕節(jié)這個(gè)時(shí)間點(diǎn),可以看看我的第二系列開篇《之一 ║ D3模式設(shè)計(jì)初探 與 我的計(jì)劃書》),然后我簡單的思考了下這個(gè)DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)還剩下的知識點(diǎn),現(xiàn)在已經(jīng)進(jìn)入了第二部分,就是領(lǐng)域命令和領(lǐng)域驅(qū)動(dòng)這一塊,第三部分包括Identity驗(yàn)證和.net core api等設(shè)計(jì)點(diǎn),大概就是剩了這么多,預(yù)計(jì)應(yīng)該能在圣誕節(jié)前完成。還有一個(gè)就是,之前的八篇文章,已經(jīng)比較完整的實(shí)現(xiàn)了普通框架的整體搭建,我也單獨(dú)的新建了一個(gè) Git分支—— Framework8 ,如果你不想用領(lǐng)域命令、領(lǐng)域事件、事件回溯這些東西,僅僅就想要一個(gè)空的框架,一個(gè)包括 EFCore+Dtos+Automapper+IoC+Repository 的空框架(就比如我的第一個(gè)系列,就是一個(gè)普通的框架,請不要再說是這是一個(gè)普通三層了,拜托??),你就可以直接用這個(gè)Framework8 ****分支即可。
言歸正傳,上次咱們說到了創(chuàng)建新student的時(shí)候,提出來一個(gè)問題,不知道大家是否還記得,這里再給大家說明一下,還是每篇一問,希望能好好思考下,或者是看看自己是如何設(shè)計(jì)的:
問題1:平時(shí)是如何進(jìn)行表單驗(yàn)證的(包括:判空、字符類型有效、業(yè)務(wù)驗(yàn)證:成人不能小于18歲、金額不能小于0等)?
問題2:如果后來驗(yàn)證變化了改怎么辦?(比如:手機(jī)號要支持全球,或者座機(jī);亦或者退休年齡從60歲變成65歲;)
1、JavaScript前端驗(yàn)證即可,后端從來不進(jìn)行驗(yàn)證?(問題2:修改js)
2、后端驗(yàn)證:直接在Controller中,通過寫很多判斷邏輯,比如 If Else等,而且CURD還需要寫很多重復(fù)的判斷方法?(問題2:每一個(gè)地方都需要仔細(xì)修改,額。)
3、后端驗(yàn)證:寫一個(gè)統(tǒng)一的驗(yàn)證類,或者驗(yàn)證機(jī)制,比如一個(gè)公共類?甚至更高級的AOP切面驗(yàn)證?(問題2:好像還是無法滿足每個(gè)領(lǐng)域特例)
4、后端驗(yàn)證:在DTO基礎(chǔ)上,基于領(lǐng)域命令,通過中介者Bus分發(fā)?(當(dāng)然這個(gè)就是以后我要寫的)
其實(shí)說實(shí)話,前三種我都用過,甚至現(xiàn)在偶爾也還是會用,畢竟很平常的用法,但是現(xiàn)在我感覺第四種真的很整潔,真正的把整體項(xiàng)目放到了領(lǐng)域中,一切以領(lǐng)域?yàn)楹诵牧?。這里我先把第四種的應(yīng)用層 Service 方法簡單寫下,你就知道多么簡潔了,具體的會在下面兩篇文章中說到:
/// <summary>
/// StudentAppService 添加新 Student /// </summary>
public void Register(StudentViewModel studentViewModel)
{ //講視圖模型,轉(zhuǎn)換成命名模型
var registerCommand = _mapper.Map<RegisterStudentCommand>(studentViewModel); //通過Mediator處理程序分發(fā)命令 //執(zhí)行順序:驗(yàn)證 -> 通知 -> 注冊
Bus.SendCommand(registerCommand);
}
老張說:這兩天我在研究,啃書的時(shí)候,發(fā)現(xiàn)了這個(gè)DDD領(lǐng)域驅(qū)動(dòng)的整體流程,從前臺數(shù)據(jù)傳遞視圖模型 ,到Dto的命令模型,然后對其校驗(yàn)的命令驗(yàn)證模式,最后還有總線分發(fā),然后就是異常通知等等,就像是一場軍事戰(zhàn)斗中的過程:
這里說的命令是動(dòng)作的意思,是用戶發(fā)出的一個(gè)請求(從前臺向后端),當(dāng)然你也可以理解是改領(lǐng)域模型下的命令動(dòng)作(從內(nèi)到外),還記得我們說到的讀寫分離CQRS么,就是Command。
每一個(gè)個(gè)小的戰(zhàn)役(領(lǐng)域模型),都會有自己戰(zhàn)場的一些信息和動(dòng)作數(shù)據(jù)(視圖模型),當(dāng)然這里有正常的消息,也有惡性攻擊或者不當(dāng)?shù)牟僮鳎恳粋€(gè)動(dòng)作執(zhí)行都是一個(gè)前鋒部隊(duì)(領(lǐng)域命令模型),先鋒部隊(duì)把這些數(shù)據(jù)打包,加上時(shí)間戳等標(biāo)識,生成命令標(biāo)簽,這個(gè)時(shí)候通過總線指揮官(中介者),交給參謀來處理數(shù)據(jù)命令(領(lǐng)域驗(yàn)證),進(jìn)行安全甄別,將正常的、正確的往下傳遞,傳給司令部(持久化),如果是惡性的錯(cuò)誤信息,則通過通訊兵打包給前線(通知),每次前線執(zhí)行操作,只需要看看是否有通訊兵是否有錯(cuò)誤異常提醒,如果沒有則證明執(zhí)行成功。
當(dāng)然還有事件回溯和事件源,我會在以后文章說明,不知道這個(gè)栗子是否合理,如果大家看不懂也沒關(guān)系,或者請下邊留言,我們一起討論討論。
更新
有的小伙伴,可能看本文或者其他的概念的時(shí)候,比較懵懂,我這里根據(jù)自己的理解,簡單畫了個(gè)草圖,當(dāng)然等系列結(jié)束的時(shí)候,還是有完整的,這里先來一個(gè)簡單的:
零、今天實(shí)現(xiàn)棕色的部分
一、領(lǐng)域命令Commands —— 領(lǐng)域模型的先鋒官
說到這個(gè)領(lǐng)域命令,大家肯定不會陌生,或者說應(yīng)該是在哪里見過,沒錯(cuò)!就是我們在上上一篇《七 ║項(xiàng)目第一次實(shí)現(xiàn) & CQRS初探》中,說到的讀寫分離 CQRS 中的C —— Commend命令,這里我簡單說下,為什么叫先鋒官,我們把整個(gè)項(xiàng)目比作一個(gè)戰(zhàn)場的化,前端一直和后端進(jìn)行交互 —— 表單提交,這個(gè)時(shí)候,肯定就離不開查詢和命令,查詢這里暫時(shí)先不說,就說一下這個(gè)命令,前端的任何一個(gè)動(dòng)作其實(shí)都是一個(gè)事件。
大家肯定知道從前臺DTO拿到的實(shí)體模型數(shù)據(jù),肯定不能直接操作領(lǐng)域模型(當(dāng)然現(xiàn)在我們是直接這么操作的,直接用的是視圖模型和領(lǐng)域模型進(jìn)行交互操作,這個(gè)時(shí)候領(lǐng)域模型就起到了一個(gè)沖鋒陷陣的作用了,其實(shí)這種設(shè)計(jì)不符合DDD領(lǐng)域設(shè)計(jì)的思想,因?yàn)轭I(lǐng)域模型是一切的核心,它應(yīng)該是一個(gè)個(gè)司令部,不能參與到前線,他會下發(fā)出一個(gè)個(gè)的命令模型去執(zhí)行),這個(gè)時(shí)候我們的命令模型就出現(xiàn)了,他充當(dāng)著從前臺到后臺的先鋒官的作用,執(zhí)行一個(gè)個(gè)的命令指令,完成從視圖模型到領(lǐng)域模型的操作和數(shù)據(jù)的過度作用。
然后再通過中介者模式,通過事件總線,通過領(lǐng)域命令一一分發(fā)出去,然后通過驗(yàn)證,最后是實(shí)現(xiàn)(比如持久化等),然后將中間產(chǎn)生的錯(cuò)誤信息,或者通知信息,再扔給了前臺,所以說,領(lǐng)域命令就是一個(gè)先鋒官,這里你也看到了,他是一個(gè)個(gè)先鋒官,他的作用是起到引導(dǎo)的作用,是下達(dá)命令的作用,他是不負(fù)責(zé)具體的邏輯實(shí)現(xiàn)的,具體是為什么呢,先按下不表。咱們先看看如何定義一個(gè)領(lǐng)域命令。
希望上邊的三段話大家可以幫忙想一想,如果想通了,但是和我寫的不一樣,請一定要留言!
1、創(chuàng)建命令抽象基類
在 Christ3D.Domain.Core 領(lǐng)域核心層中,新建Commands文件夾,并該文件夾下創(chuàng)建抽象命令基類 Command,這里可能有小伙伴會問,這個(gè)層的作用,我就簡單再說下,這個(gè)層的作用是為了定義核心的領(lǐng)域知識的,說人話就是很多基類,比如 Entity 是領(lǐng)域模型的基類,ValueObject 是值對象的基類,這里的Command 是領(lǐng)域命令的基類,當(dāng)然,你也可以把他放到領(lǐng)域?qū)又校靡粋€(gè) Base 文件夾來表示,這小問題就不要爭議了。
namespace Christ3D.Domain.Core.Commands
{ /// <summary>
/// 抽象命令基類 /// </summary>
public abstract class Command
{ //時(shí)間戳
public DateTime Timestamp { get; private set; } //驗(yàn)證結(jié)果,需要引用FluentValidation
public ValidationResult ValidationResult { get; set; } protected Command()
{
Timestamp = DateTime.Now;
} //定義抽象方法,是否有效
public abstract bool IsValid();
}
}
思考:為什么要單單頂一個(gè)抽象方法 IsValid();
2、定義 StudentCommand ,領(lǐng)域命令模型
上邊的領(lǐng)域基類建好以后,我們就需要給每一個(gè)領(lǐng)域模型,建立領(lǐng)域命令了,這里有一個(gè)小小的繞,你這個(gè)時(shí)候需要靜一靜,想一想,
1、為什么每一個(gè)領(lǐng)域模型都需要一個(gè)命令模型。
2、為什么是一個(gè)抽象類。
namespace Christ3D.Domain.Commands
{
/// <summary>
/// 定義一個(gè)抽象的 Student 命令模型 /// 繼承 Command /// 這個(gè)模型主要作用就是用來創(chuàng)建命令動(dòng)作的,不是用來實(shí)例化存數(shù)據(jù)的,所以是一個(gè)抽象類 /// </summary>
public abstract class StudentCommand : Command
{
public Guid Id { get; protected set; }//注意:set 都是 protected 的
public string Name { get; protected set; }
public string Email { get; protected set; }
public DateTime BirthDate { get; protected set; }
public string Phone { get; protected set; }
}
}
希望這個(gè)時(shí)候你已經(jīng)明白了上邊的兩個(gè)問題了,如果不是很明白,請?jiān)俸煤盟伎枷拢绻呀?jīng)明白了,請繼續(xù)往下走。
3、基于命令模型,創(chuàng)建各種動(dòng)作指令
上邊的模型創(chuàng)造出來了,咱們需要用它來實(shí)現(xiàn)各種動(dòng)作命令了,比如 URD 操作,肯定是沒有 C 查詢的。這里就重點(diǎn)說一下創(chuàng)建吧,剩下兩個(gè)都一樣。
namespace Christ3D.Domain.Commands
{ /// <summary>
/// 注冊一個(gè)添加 Student 命令 /// 基礎(chǔ)抽象學(xué)生命令模型 /// </summary>
public class RegisterStudentCommand : StudentCommand
{ // set 受保護(hù),只能通過構(gòu)造函數(shù)方法賦值
public RegisterStudentCommand(string name, string email, DateTime birthDate, string phone)
{
Name = name;
Email = email;
BirthDate = birthDate;
Phone = phone;
} // 重寫基類中的 是否有效 方法 // 主要是為了引入命令驗(yàn)證 RegisterStudentCommandValidation。
public override bool IsValid()
{
ValidationResult = new RegisterStudentCommandValidation().Validate(this);//注意:這個(gè)就是命令驗(yàn)證,我們會在下邊實(shí)現(xiàn)它 return ValidationResult.IsValid;
}
}
}
這里你應(yīng)該就能明白第一步的那個(gè)問題了吧:為什么要單單頂一個(gè)抽象方法 IsValid();
不僅僅是驗(yàn)證當(dāng)前命令模型是否有效(無效是指:數(shù)據(jù)有錯(cuò)誤、驗(yàn)證失敗等等),只有有效了才可以往下繼續(xù)走(比如持久化等 ),還要獲取驗(yàn)證失敗的情況下,收錄哪些錯(cuò)誤信息,并返回到前臺,這個(gè)就是
new RegisterStudentCommandValidation()
的作用。注意這里還沒有實(shí)現(xiàn),我們接下來就會實(shí)現(xiàn)它。
添加學(xué)生命令寫完了,然后就是更新 UpdateStudentCommand 和 刪除 RemoveStudentCommand 了,這里就不多說了。
二、領(lǐng)域驗(yàn)證Validations —— 領(lǐng)域模型的安保官
這里為啥要說是安保官(就是起的名字,要是不貼切可以留言)呢,因?yàn)檫@是從前臺 視圖模型 到 領(lǐng)域模型 的一個(gè)屏障,這個(gè)就不用解釋了,因?yàn)樗褪且粋€(gè)驗(yàn)證的作用,當(dāng)一個(gè)個(gè)命令執(zhí)行的時(shí)候,需要對數(shù)據(jù)進(jìn)行處理,就好像前線先鋒部隊(duì)執(zhí)行一個(gè)個(gè)命令的時(shí)候,需要對一個(gè)個(gè)事件或者數(shù)據(jù)進(jìn)行判斷,有些錯(cuò)誤的,假的數(shù)據(jù)是不能傳達(dá)到領(lǐng)域模型中的,而我們的先鋒官是不會處理這些的,他們只負(fù)責(zé)一個(gè)個(gè)命令的執(zhí)行,驗(yàn)證工作就交給了Validations,而且是每一條命令都需要進(jìn)行驗(yàn)證,這是肯定的。那如何創(chuàng)建基于命令的驗(yàn)證Validations呢,請往下看。
1、基于StudentCommand 創(chuàng)建抽象驗(yàn)證基類
在上邊的領(lǐng)域命令中,我們定義一個(gè)公共的抽象命令基類,在驗(yàn)證中,F(xiàn)luentValidation已經(jīng)為我們定義好了一個(gè)抽象基類 AbstractValidator,所以我們只需要繼承它就行。
namespace Christ3D.Domain.Validations
{ /// <summary>
/// 定義基于 StudentCommand 的抽象基類 StudentValidation /// 繼承 抽象類 AbstractValidator /// 注意需要引用 FluentValidation /// 注意這里的 T 是命令模型 /// </summary>
/// <typeparam name="T">泛型類</typeparam>
public abstract class StudentValidation<T> : AbstractValidator<T> where T : StudentCommand
{ //受保護(hù)方法,驗(yàn)證Name
protected void ValidateName()
{
//定義規(guī)則,c 就是當(dāng)前 StudentCommand 類
RuleFor(c => c.Name)
.NotEmpty().WithMessage("姓名不能為空")//判斷不能為空,如果為空則顯示Message
.Length(2, 10).WithMessage("姓名在2~10個(gè)字符之間");//定義 Name 的長度
} //驗(yàn)證年齡
protected void ValidateBirthDate()
{
RuleFor(c => c.BirthDate)
.NotEmpty()
.Must(HaveMinimumAge)//Must 表示必須滿足某一個(gè)條件,參數(shù)是一個(gè)bool類型的方法,更像是一個(gè)委托事件
.WithMessage("學(xué)生應(yīng)該14歲以上!");
} //驗(yàn)證郵箱
protected void ValidateEmail()
{
RuleFor(c => c.Email)
.NotEmpty()
.EmailAddress();
} //驗(yàn)證手機(jī)號
protected void ValidatePhone()
{
RuleFor(c => c.Phone)
.NotEmpty()
.Must(HavePhone)
.WithMessage("手機(jī)號應(yīng)該為11位!");
} //驗(yàn)證Guid
protected void ValidateId()
{
RuleFor(c => c.Id)
.NotEqual(Guid.Empty);
} // 表達(dá)式
protected static bool HaveMinimumAge(DateTime birthDate)
{
return birthDate <= DateTime.Now.AddYears(-14);
} // 表達(dá)式
protected static bool HavePhone(string phone)
{
return phone.Length == 11;
}
}
}
關(guān)于 FluentValidation 的使用,這里就不多說了,大家可以自己使用,基本的也就是這么多了,當(dāng)然大家也可以自己寫一些復(fù)雜的運(yùn)算,這里要說的重點(diǎn)是,大家應(yīng)該也已經(jīng)發(fā)現(xiàn)了,每一個(gè)驗(yàn)證方法都是獨(dú)立的,互不影響,就算是有一個(gè)出現(xiàn)錯(cuò)誤(當(dāng)然不是編譯錯(cuò)誤),也不會影響當(dāng)前整個(gè)領(lǐng)域命令,也就等同于不影響當(dāng)前事件操作,是不是和以前相比,不僅方便而且安全性更高了。
這個(gè)時(shí)候我們定義了這個(gè)抽象的學(xué)生驗(yàn)證基類,剩下的就是需要針對不同的,每一個(gè)領(lǐng)域命令,設(shè)計(jì)領(lǐng)域驗(yàn)證了。
2、實(shí)現(xiàn)各個(gè)領(lǐng)域命令模型的驗(yàn)證操作
這里就簡單說一個(gè)添加學(xué)生的命令驗(yàn)證,我們實(shí)現(xiàn) StudentValidation<RegisterStudentCommand> ,并初始化相應(yīng)的命令,這里可以看到,我們可以很自由針對某一個(gè)命令,隨心隨意的設(shè)計(jì)不同的驗(yàn)證,而且很好的進(jìn)行管控,比如以后我們不要對名字控制了,我們只需要去掉這個(gè)方法。亦或者我們以后不僅支持手機(jī)號,還支持座機(jī),這里就可以簡單的增加一個(gè)即可。
namespace Christ3D.Domain.Validations
{ /// <summary>
/// 添加學(xué)生命令模型驗(yàn)證 /// 繼承 StudentValidation 基類 /// </summary>
public class RegisterStudentCommandValidation : StudentValidation<RegisterStudentCommand> { public RegisterStudentCommandValidation()
{
ValidateName();//驗(yàn)證姓名
ValidateBirthDate();//驗(yàn)證年齡
ValidateEmail();//驗(yàn)證郵箱
ValidatePhone();//驗(yàn)證手機(jī)號 //可以自定義增加新的驗(yàn)證
}
}
}
說到了這里,相信你應(yīng)該也命令了領(lǐng)域驅(qū)動(dòng)的第一個(gè)小部分了,就是我們的每一個(gè)操作是如何生成命令并進(jìn)行驗(yàn)證的,那聰明的你一定會問了,我們?nèi)绾尾僮鬟@些領(lǐng)域命令呢,總得有一個(gè)驅(qū)動(dòng)程序吧,它們自己肯定是不會運(yùn)行的,不錯(cuò)!請繼續(xù)往下看。
三、運(yùn)籌命令模型 —— 誰會是指揮官?
上邊也說到了視圖模型轉(zhuǎn)成命令模型,然后在命令模型中進(jìn)行驗(yàn)證,現(xiàn)在問題來了,到底是誰在運(yùn)籌著這些命令,說人話就是,是誰在調(diào)用著這些命令,如果你能看懂我說到,那就恭喜你,如果不是很懂,也沒關(guān)系,今天咱們先不說這個(gè)指揮官,今天先說說,我們平時(shí)是怎么玩兒的。
1、在 Action 中調(diào)用我們的領(lǐng)域命令
[HttpPost]
[ValidateAntiForgeryToken] public ActionResult Create(StudentViewModel studentViewModel)
{ try {
ViewBag.ErrorData = null; // 視圖模型驗(yàn)證
if (!ModelState.IsValid) return View(studentViewModel); //添加命令驗(yàn)證,采用構(gòu)造函數(shù)方法實(shí)例
RegisterStudentCommand registerStudentCommand = new RegisterStudentCommand(studentViewModel.Name, studentViewModel.Email, studentViewModel.BirthDate, studentViewModel.Phone); //如果命令無效,證明有錯(cuò)誤
if (!registerStudentCommand.IsValid())
{
List<string> errorInfo = new List<string>(); //獲取到錯(cuò)誤,請思考這個(gè)Result從哪里來的
foreach (var error in registerStudentCommand.ValidationResult.Errors)
{
errorInfo.Add(error.ErrorMessage);
} //對錯(cuò)誤進(jìn)行記錄,還需要拋給前臺
ViewBag.ErrorData = errorInfo; return View(studentViewModel);
} // 執(zhí)行添加方法
_studentAppService.Register(studentViewModel);
ViewBag.Sucesso = "Student Registered!"; return View(studentViewModel);
} catch (Exception e)
{ return View(e.Message);
}
}
這個(gè)很好理解,就是普通的調(diào)用,這里有兩個(gè)問題,可以有助于大家是否真正理解:
1、new RegisterStudentCommand() 為什么是構(gòu)造函數(shù)實(shí)例?
2、ValidationResult.Errors 錯(cuò)誤信息是從哪里得到的?
如果這兩個(gè)看懂了,給自己一個(gè)攢??吧。這個(gè)時(shí)候,我們就需要把信息拋給前臺了,怎么進(jìn)行展示呢,這里我用的是自定義視圖組件,如果你會可以快速看一遍,如果沒有用過,請仔細(xì)看看。
2、自定義局部視圖頁面
添加一個(gè)視圖組件類
在 Web 根目錄下新建文件夾 ViewComponents,然后添加視圖組件類 AlertsViewComponent.cs
namespace Christ3D.UI.Web.ViewComponents
{ public class AlertsViewComponent : ViewComponent
{ /// <summary>
/// Alerts 視圖組件 /// 可以異步,也可以同步,注意方法名稱,同步的時(shí)候是Invoke /// 我寫異步是為了為以后做準(zhǔn)備 /// </summary>
/// <returns></returns>
public async Task<IViewComponentResult> InvokeAsync()
{ var notificacoes = await Task.Run(() => (List<string>)ViewBag.ErrorData); //遍歷錯(cuò)誤信息,賦值給 ViewData.ModelState
notificacoes?.ForEach(c => ViewData.ModelState.AddModelError(string.Empty, c)); return View();
}
}
}
每一個(gè)視圖組件一個(gè)類,固定寫法,這個(gè)其實(shí)就像mvc的controller。那我們還需要配置 view,如何配置呢,請往下看。
設(shè)計(jì)視圖頁面
這里我是手動(dòng)創(chuàng)建,不知道有沒有快捷鍵,有知道的請留言哈
在 Views -> Shared 文件夾下,新建 Components\alerts\Default.cshtml 文件
@if (ViewData.ModelState.ErrorCount > 0)
{ <div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert">×</button>
<h3 id="msgRetorno">Alert! Something went wrong:</h3>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</div> }
@if (!string.IsNullOrEmpty(ViewBag.Sucesso))
{ <div class="alert alert-success">
<button type="button" class="close" data-dismiss="alert">×</button>
<h3 id="msgRetorno">@ViewBag.Sucesso</h3>
</div> }
在主頁面內(nèi)調(diào)用
這里有兩種辦法:
@* 將經(jīng)典驗(yàn)證摘要替換為自定義視圖組件作為標(biāo)記助手 *@
@*方式一(可用,但不推薦) @await Component.InvokeAsync("Alerts")*@ <!-- 如果使用這個(gè)方法,請記得在_ViewImports.cshtml 中,導(dǎo)入@addTagHelper "*, Christ3D.UI.Web" -->
<vc:alerts />
我個(gè)人推薦使用第二種方法,注意 alerts,是我們的視圖名稱。
如果你想了解更多關(guān)于自定義視圖組件的知識,可以查看官網(wǎng)
1、https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/view-components?view=aspnetcore-2.1
3、瀏覽效果
這個(gè)時(shí)候,我們已經(jīng)把視圖模型,命令模型,命令驗(yàn)證等連接在一起,也實(shí)現(xiàn)了我們的目的,看似很正常,其實(shí)問題還有很多:
這個(gè)指揮官真的指揮的很好么?
為何contrller中還是會存在業(yè)務(wù)邏輯?
等等。。。
四、鳴金...
眼看時(shí)間已經(jīng)很晚,今天就暫時(shí)寫到這里了。
這個(gè)時(shí)候你一定會發(fā)現(xiàn),這種異常數(shù)據(jù)的寫法真的很不舒服,我們設(shè)計(jì)DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),目的就是為了要以領(lǐng)域?yàn)楹诵模褬I(yè)務(wù)邏輯分離出去,這個(gè)雖然用到了領(lǐng)域命令,和命令驗(yàn)證,咋看分離出去了,但是調(diào)用的時(shí)候,還是沒有把視圖模型和命令模型穿起來,而且細(xì)心的你應(yīng)該也發(fā)現(xiàn)了,我們的Service方法中,還是使用的領(lǐng)域模型,這個(gè)是不對的。那我們?nèi)绾尾拍馨岩晥D模型,領(lǐng)域模型,驗(yàn)證模型和命令模型穿起來呢,又是如何很好的把獲取錯(cuò)誤信息從controller撥離出來呢,請聽下回分解~