從壹開始微服務 [ DDD ] 之八 ║剪不斷理還亂的 值物件和Dto
正文
緣起
哈嘍大家週四好,時間是過的真快,這幾天一直忙著在公司的專案,然後帶帶新人,眼看這周要過去了,還是要抽出時間學習學習,這些天看到群裡的小夥伴也都在忙著新學習,還是很開心的,至少當時的初衷已經達到了,一起學習一起進步嘛,哪怕是對現在或者是對以後的工作有一丟丟的幫助,也是不枉此時的努力,哈哈夜裡寫文章總是容易多想,好啦,廢話不多說,上次我們們說到了《》,今天本來應該接著寫 領域命令 了,在設計的領域命令的時候,發現了值物件的存在,對 領域模型 和 檢視模型 有著剪不斷理還亂的困擾,所以我就暫時單寫一篇了,既是對上一篇的補充,又是對領域命令的鋪墊,好啦,馬上開始今天的說明吧~~
還是老規矩,每篇文章先給大家一個小問題,先思考下,然後有助於理解本文:
問題:我們在領域模型 Student 中,有一個戶籍的值物件(為啥叫戶籍,下邊會說到),然後我們也有一個學生的檢視模型 StudentViewModel ,那麼問題來了,我們在 StudentViewModel 中,如何去定義這個戶籍的檢視模型呢,然後又是如何傳給領域模型 Student 呢?
1、不寫這戶籍一塊,直接在業務邏輯裡,手動賦值給 Student 領域模型
public class StudentViewModel { [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 等等其他,只是學生的個人資訊,不涉及戶籍地址 }
2、和領域模型一樣,也寫一個物件,甚至直接就用領域模型中的 Address 值物件
public class StudentViewModel { [Key] public Guid Id { get; set; } [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 等等其他的資訊 //這個就是在領域模型Student中使用的,戶籍值物件 public Address Address { get; set; } }
3、把 Address 屬性拆開,一個一個的放在檢視模型 StudentViewModel 中
public class StudentViewModel { [Key] public Guid Id { get; set; } [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 等等其他學生資訊,比如手機號,郵箱等 /// <summary> /// 城市 /// </summary> public string City { get; set; } /// <summary> /// 區縣 /// </summary> public string County { get; set; } /// <summary> /// 街道 /// </summary> public string Street { get; set; } }
或許你還有其他啥辦法,要是有感覺更好的,或者更正確的,千萬要評論留言喲,只不過這三種辦法是我親身實驗的,這裡大家先思考一下,希望看完本文你會有一些自己的想法。
零、今天實現藍色的部分
一、建立 Student 的新增模組
話說上次我們們是把領域模型(包括實體和值物件)透過EFCore儲存到了資料庫,然後也查詢出來了相應的學習資訊,(這裡注意下,學習的戶籍資訊還沒有取出來),這裡說一下為什麼是戶籍地址資訊,
上篇文章中,有小夥伴還是對這個不是很理解,一直想著要一定和資料庫對應上,比如說,為啥叫地址,那如果學生有多個地址咋辦;再比如,這樣修改學生資訊,值物件就會發生變化呀,這樣就不能滿足值物件不可變的特性;等等諸如此類的疑問,這裡說一下:
1、值物件其實就是一個值,它和Name、Phone、Email等等一模一樣,只不過它是一個物件,複雜了一些,有了自己的內部結構,所以說,值物件是沒有狀態的,沒有唯一標識(多個學生叫張三 == 兩個學生一個地址),是內部不可變性,就比如我們修改一個學校省份,需要將整個值物件都修改,這和修改Name是一樣的。
2、值物件是一個領域中孕育出來的概念,千萬不要事事都要和資料庫,資料模型,扯上關係,如果想要一個會員多個地址,那這個時候地址就是一個實體,甚至是一個聚合了,比如物流地址,這也就是我為什麼要把這個Address稱之為 戶籍 的原因了,從領域出發,而不要再和資料模型資料庫表相提並論了。
那我們們就先新增學生的 Create 模組
1、在 StudentController 中新增 Create Action
// GET: Student/Create // 頁面 public ActionResult Create() { return View(); } // POST: Student/Create // 方法 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(StudentViewModel studentViewModel) { try { // 檢視模型驗證 if (!ModelState.IsValid) return View(studentViewModel); // 執行新增方法 _studentAppService.Register(studentViewModel); ViewBag.Sucesso = "Student Registered!"; return View(studentViewModel); } catch(Exception e) { return View(e.Message); } }
這個時候大家肯定都已經很熟悉了,而且 Service 層注入什麼的,相信大家已經得心應手了,這裡都不細說了。
2、建立 Create View頁面
@model Christ3D.Application.ViewModels.StudentViewModel @{ ViewData["Title"] = "Register new Student"; }<h2>@ViewData["Title"]</h2> <form asp-action="Create"> <div class="form-horizontal"> <hr /> @* Replacing classic Validation Summary to Custom ViewComponent as TagHelper *@ <vc:summary /> <div class="form-group"> <label asp-for="Name" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Name" class="form-control" /> <span asp-validation-for="Name" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Email" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Phone" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Phone" class="form-control" /> <span asp-validation-for="Phone" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="BirthDate" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="BirthDate" class="form-control" /> <span asp-validation-for="BirthDate" class="text-danger"></span> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-success" /> <a asp-action="Index" class="btn btn-info">Back to List</a> </div> </div> </div> </form>
這些都是 AspNetCore.Mvc.ViewFeature 的模型命令還有驗證等,相比以前的模型,已經有很大的改善了,這個可以自己試試,很簡單,直接往下走,重頭戲來了。
這個時候,如果我們新增資訊儲存的話,一定會發現一個問題,就是戶籍資訊到底如何傳入呢,上邊說的三種辦法到底該選擇哪一種呢,下邊我們們一一來實驗下。
二、如何把值物件新增到檢視模型
這個時候肯定會有小夥伴說,為什麼一定要把值物件放到檢視模型中,就比如文章的第一個方法,我就不放進去,我從頁面內獲取到Country、Province、City等等後,然後再傳到領域模型不就行了,真的麼?
1、手動賦值的方法
假設我們已經從前臺頁面內獲取到了戶籍資訊,然後我們就會這麼做
public ActionResult Create(StudentViewModel studentViewModel,string country,string provice,string city,string street) { // 檢視模型驗證 if (!ModelState.IsValid) return View(studentViewModel); //這個時候還需要對戶籍資訊進行驗證判斷 //比如字串不能數字,字元啥的 // 執行新增方法,把戶籍資訊傳遞過去 _studentAppService.Register(studentViewModel,country, provice, city, street); ViewBag.Sucesso = "Student Registered!"; return View(studentViewModel); }
Stop!相信我,你肯定不會這麼做的,當然,偶爾偶爾我們會這麼接受一個引數,也偶會會這麼寫,可是這麼寫肯定是不行的,且不說不是DDD領域驅動設計思想,就連OOP思想也沒有發揮起來,所以方法一直接pass。
這個時候我們開始思考,至少需要把戶籍資訊放到檢視模型 StudentViewMode 中吧,嗯看著文章開頭的第二個方法就特別好!物件是吧,這個可是真是的OOP思想,全部用物件接收引數,然後把資料傳如到倉儲的Add()方法中,這樣就直接儲存了嘛,多好呀!想想的心動,那就開始吧,一個小坑正在慢慢變大。
2、用物件的方法將值物件新增到檢視模型中
聽著很拗口,說白了,就是文章開頭的第二種方法,領域模型和檢視模型,共用一個 值物件。然後我們修改下 view 頁面,用來傳遞引數。
<div class="form-group"> <label asp-for="BirthDate" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="BirthDate" class="form-control" /> <span asp-validation-for="BirthDate" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Address.County" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Address.County" class="form-control" /> <span asp-validation-for="Address.County" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Address.Province" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Address.Province" class="form-control" /> <span asp-validation-for="Address.Province" class="text-danger"></span> </div> </div> <div class="form-group"> <label asp-for="Address.City" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Address.City" class="form-control" /> <span asp-validation-for="Address.City" class="text-danger"></span> </div> </div>
這個時候,我們一定很歡喜,然後點選提交,發現,無論怎麼提交都不會在
public ActionResult Create(StudentViewModel studentViewModel)
中獲取到我們需要的戶籍資訊,天哪!這是啥情況,當然是獲取不到的,因為 Address 是一個值物件,具有不可變性,它的 set 都是私有的,不能被賦值,不信請看
這個時候怎麼辦,聰明的你肯定能想到一個方法,既然值物件不行,它內部不可變,不能賦值,那我就自己在檢視模型中,再寫一個 AddressViweModel 不就行啦,然後可以進行set操作,想到這裡還是很激動,趕緊試試,這就看看能不能獲取到值。
很不錯,已經把內容獲取到了,然後透過檢視物件傳到Add() 方法,很成功的達到了目的。
看來這個方法也是可以的,只不過有一個小問題就是,這裡需要多了一個類來實現,如果我不想用類接受,而且是直接用屬性呢?那就是第三種辦法了,請繼續往下看。
3、用屬性欄位來講戶籍資訊放到檢視模型中
就是文章開頭的第三種辦法,這樣的:
public class StudentViewModel { [Required(ErrorMessage = "The Name is Required")] [MinLength(2)] [MaxLength(100)] [DisplayName("Name")] public string Name { get; set; } //... 其他 /// <summary> /// 省份 /// </summary> [Required(ErrorMessage = "The Province is Required")] [DisplayName("Province")] public string Province { get; set; } /// <summary> /// 城市 /// </summary> public string City { get; set; } /// <summary> /// 區縣 /// </summary> public string County { get; set; } /// <summary> /// 街道 /// </summary> public string Street { get; set; } }
然後再修改下頁面裡的呼叫情況,直接用呼叫屬性
<div class="form-group"> <label asp-for="Province" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Province" class="form-control" /> <span asp-validation-for="Province" class="text-danger"></span> </div> </div>
這個時候,我們滿懷開心的執行專案的時候,發現,index頁面的戶籍資訊沒有了,也就是說 Student -> StudentViewModel 的時候,透過 Automapper 沒有成功。
然後我們提交的時候,發現後端雖然能接受到資料,
可是在轉換到 Student 的時候失敗了:
這裡顯示的是,我們無法對其進行轉換,因為在檢視模型中,沒有匹配到 Student 的 Address 值物件資訊,不要慌,下邊我們會說這個問題。
三、Automapper實現複雜物件的轉換
為了解決上一個問題,我研究了下 Automapper 官網,發現,這種複雜複製,需要進行手動配置,其實也是很簡單,只需要建立匹配屬性即可
注意,在第二種方法中是不需要配置的,因為第二種方法,兩個模型結構幾乎一模一樣,這第三種方法,結構已經變了,一個是物件,一個僅僅是一個屬性值。
1、複雜領域模型轉換到檢視模型
/// <summary> /// 配置建構函式,用來建立關係對映 /// </summary> public DomainToViewModelMappingProfile() { CreateMap<Student, StudentViewModel>() .ForMember(d => d.County, o => o.MapFrom(s => s.Address.County)) .ForMember(d => d.Province, o => o.MapFrom(s => s.Address.Province)) .ForMember(d => d.City, o => o.MapFrom(s => s.Address.City)) .ForMember(d => d.Street, o => o.MapFrom(s => s.Address.Street)) ; }
這個時候,我們看Index頁面,戶籍資訊也出來了
2、檢視模型轉換到複雜領域模型
public ViewModelToDomainMappingProfile() { //手動進行配置 CreateMap<StudentViewModel, Student>() .ForPath(d => d.Address.Province, o => o.MapFrom(s => s.Province)) .ForPath(d => d.Address.City, o => o.MapFrom(s => s.City)) .ForPath(d => d.Address.County, o => o.MapFrom(s => s.County)) .ForPath(d => d.Address.Street, o => o.MapFrom(s => s.Street)) ; }
這裡將 Student 中的戶籍資訊,一一匹配到檢視模型中的屬性。
然後我們測試資料,不僅僅可以把資料獲取到,還可以成功的轉換過去:
最後首頁檢視驗證資訊,以及新增上了,完成。
四、結語
今天呢,是補充了上一把的坑,一共提供了三個辦法,當然其實第一種也不算是方法,主要是後兩者,不知道大家是否能看的懂,然後更傾向於哪一種:
2、不用配置 Automapper 對映資訊,只需要新建一個一樣的戶籍值物件的檢視模型 —— 戶籍檢視模型即可,因為結構相同,所以不需要手動配置對映,就能達到目的。
3、只需要一個檢視模型即可控制,在某些情況下,我們不方便使用巢狀的複雜檢視模型,只需要配置下對映檔案即可達到目的。
今天,也為下一篇做準備,怎麼說呢,大家發現,現在我們能正確的新增進去了,但是如果我們要進行驗證該怎麼辦?比如說,我們要判斷學校不能小於14歲,手機號格式,郵箱格式等等,
當然,你可以說,我會用前端js校驗,也可以後端獲取到,if 判斷,都是可以的,
不過我個人感覺,後端校驗還是很需要的,我採用 FluentValidation 進行後端校驗,並且融入到 領域命令 中,那如何實現呢,下次再見咯~~~
五、GitHub & Gitee
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1727/viewspace-2817394/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 從壹開始微服務 [ DDD ] 之二 ║ DDD入門 & 專案結構粗搭建微服務
- 微服務之間如何共享DTO?微服務
- Java值物件或DTO克隆工具Java物件
- 從零開始,使用Dapr簡化微服務微服務
- DDD之1微服務設計為什麼選擇DDD微服務
- TypeScript如何實現DDD的值物件?TypeScript物件
- silky微服務快速開始微服務
- 微服務SpringCloud之熔斷器微服務SpringGCCloud
- 不要從微服務開始!單體應用是你的朋友 - arnoldgalovics微服務
- DDD實體值物件的equals和hashcode方法實現 - wimdeblauwe物件
- 微前端:DDD和微服務對客戶端開發的好處 - thenewstack前端微服務客戶端
- 微服務SpringCloud之熔斷監控Hystrix Dashboard和Turbine微服務SpringGCCloud
- DDD值物件:被遺忘的價值 – SoftwareMill Tech Blog物件REM
- 微服務架構 - 正確的開始微服務架構
- 微服務熔斷限流Hystrix之Dashboard微服務
- 還不會K8S嗎?先從kubeadm開始吧K8S
- 終端斷開,任務不斷
- 從零開始學習C++之if判斷語句C++
- 實現微服務預熱呼叫之後再開始服務(上)微服務
- 實現微服務預熱呼叫之後再開始服務(下)微服務
- 從 Spring Cloud 開始談一談微服務架構的實踐之路SpringCloud微服務架構
- 從0開始學微服務-胡忠想-極客時間微服務
- 類似DDD的值物件的Java中新的值型別ValueType -jaxenter物件Java型別
- .NET Core 微服務之Polly熔斷策略微服務
- 從零開始學Python:第八課-函式和模組Python函式
- hyperf從零開始構建微服務(二)——構建服務消費者微服務
- hyperf從零開始構建微服務(一)——構建服務提供者微服務
- 從 1.5 開始搭建一個微服務框架——日誌追蹤 traceId微服務框架
- 可落地的DDD(4)-如何利用DDD進行微服務的劃分(2)微服務
- 從零開始學Python(八):Python多執行緒和佇列Python執行緒佇列
- 微服務元件之限流器與熔斷器微服務元件
- DDD與團隊拓撲以及微服務之間的關係圖 - aleixmorgadas微服務
- java從頭開始--物件導向1Java物件
- DDD中實體與值物件是幹什麼的物件
- 好文分享 努力從何時開始都不晚 跟自己比 不斷進步
- 教你玩轉微服務--基於DDD的微服務架構落地實踐之路微服務架構
- 一起玩轉微服務(11)——一切從簡單開始微服務
- DDD的實體、值物件、聚合根的基類和介面:設計與實現物件