背景
數據模型、領域模型和視圖模型是“模型”的三種角色,一些架構用一種類型表示這三種角色,如:傳統三層架構。也有一些架構用兩種類型表示這三種角色,如:結合ORM的領域驅動架構。非常少見的場景是用三種類型表示這三種角色,我只在個別領域這么弄過,如:工作流引擎。
今天只說一個話題:是否有必要為視圖模型引入獨立的類型?還是用一種類型表達領域模型和視圖模型這兩種角色比較方便?引入一些詞匯:
- A方案:用一種類型表達領域模型和視圖模型這兩種角色,又叫公開領域模型到視圖(Open Domain To View)。
- B方案:為視圖模型引入獨立的類型,又叫使用數據傳輸對象(DTO)。
A方案
因為領域模型和視圖模型是一個類型,所以領域模型會從UI進行重建,因為領域模型會從UI進行重建,而UI層是不能相信的,所以必須對領域模型進行驗證(包含IsValid()方法),而且領域的很多方法都是修復領域模型的非法狀態,如:重新計算訂單總額、加密未加密的密碼屬性等等。
代碼示例
1 internal sealed class TestGridCommandHandler : ApplicationService, 2 ICommandHandler<CreateTestGrid>, 3 ICommandHandler<UpdateTestGrid>, 4 ICommandHandler<DeleteTestGrid> 5 { 6 public void Handle(CreateTestGrid command) 7 { 8 var testGridService = this.Service<TestGridService>(); 9 10 testGridService.Create(command.TestGridInfo); 11 command.Id = command.TestGridInfo.Id; 12 } 13 14 public void Handle(UpdateTestGrid command) 15 { 16 var testGridService = this.Service<TestGridService>(); 17 18 testGridService.Update(command.TestGridInfo); 19 } 20 21 public void Handle(DeleteTestGrid command) 22 { 23 this.Service<TestGridService>().Delete(command.Id); 24 } 25 }
注意看第二個方法,這里的command.TestGridInfo就是領域模型,從客戶端重建后直接調用ApplicationService進行update,update負責修復模型狀態、執行驗證和處理樂觀並發。
B方案
因為領域模型和視圖模型是一個不同的類型,所以領域模型不會從UI進行重建,因為UI進行重建的只是視圖模型, 所以要從數據庫加載一份領域模型,然后將視圖模型合並到領域模型中,這里的合並不是指用AutoMapper這樣的合並工具,而是一種合理的合並過程(不能用反射繞過領域模型封裝的邏輯),在這個合並過程,領域模型始終處於合法狀態(也可以不合法,很多人都這么弄,保留IsValid()方法即可)。
代碼示例
1 internal sealed class TestOrderCommandHandler : ApplicationService, 2 ICommandHandler<CreateTestOrder>, 3 ICommandHandler<UpdateTestOrder>, 4 ICommandHandler<DeleteTestOrder> 5 { 6 public void Handle(CreateTestOrder command) 7 { 8 var testOrderService = this.Service<TestOrderService>(); 9 10 var testOrder = command.CreateTestOrder(); 11 12 testOrderService.Create(testOrder); 13 command.Id = testOrder.Id; 14 } 15 16 public void Handle(UpdateTestOrder command) 17 { 18 var testOrderService = this.Service<TestOrderService>(); 19 20 var testOrder = testOrderService.Repository.Load(command.Id); 21 testOrder.CheckOptimisticKey(command.TestOrderInfo.OptimisticKey); 22 23 command.UpdateTestOrder(testOrder); 24 testOrderService.Update(testOrder); 25 } 26 27 public void Handle(DeleteTestOrder command) 28 { 29 this.Service<TestOrderService>().Delete(command.Id); 30 } 31 }
注意看第二個方法,這里先用Repository從數據庫返回一個領域模型,執行樂觀鎖檢查,用視圖模型修改領域模型(不是簡單的反射),然后調用ApplicationService進行Update。
備注
只看代碼大家可能覺得A方案比較簡單,而B方案視乎有點脫褲子放屁的感覺,我之前都是用的A方案,開發效率確實高,但是應對比較復雜的邏輯就非常不爽了,具體為啥不爽我還沒有想明白。
我現在非常有信心用好任何一個方案,因為一個高人告訴我:關注代碼細節勝於關注這些架構上的問題。
結合四色原型,我覺得可以這樣弄:PPT和DES用A方案,MI用B方案。