DDD:關於模型的合法性,Entity.IsValid()合理嗎?


背景

見過很多框架(包括我自己的)都會在實體的定義中包含一個IsValid()方法,用來判斷實體的合法性,是否應該這樣設計呢?本文就這個問題介紹一點想法,希望大家多批評。

實體能否處於“非法”狀態?

實體是否應該包含IsValid()方法的深層次問題是:“實體能否處於非法狀態?”。我們來定義一些術語,接下來我就引用這些術語:

  • A模式:實體允許處於非法狀態,但是實體要包含一個IsValid()方法進行校驗。
  • B模式:實體不允許處於非法狀態,業務邏輯必須保證這一點。

關於A模式我不想多說了,A模式本身沒有問題的,今天重點說說如何實現B模式。

如何實現B模式?

最好的說明就是寫一個例子,下面是我們例子的需求:

  • xxx屬性不能為空。
  • xxx屬性必須唯一。

這個例子非常簡單,也具有代表性,可以進一步抽象為:

  • xxx屬性不能為空,聚合自身的驗證。
  • xxx屬性必須唯一,跨聚合驗證。

讓我們一個一個來。

xxx屬性不能為空,聚合自身的驗證。

聚合本身應該負責自己狀態的完整性,反射可能會繞過這些驗證,使用類似AutoMapper的工具需要注意(我已經處理了)。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 using Happy.Domain;
 8 using Happy.Domain.Tree;
 9 using Happy.Example.Events.TestGrids;
10 
11 using Happy.Infrastructure;
12 
13 namespace Happy.Example.Domain.TestGrids
14 {
15     public partial class TestGrid : AggregateRoot<Guid>
16     {
17         public System.Int64? BigIntField { get; set; }
18         public System.Boolean? BitField { get; set; }
19         public System.DateTime? DateField { get; set; }
20         public System.DateTime? DateTimeField { get; set; }
21         public System.Decimal? DecimalField { get; set; }
22         public System.Double? FloatField { get; set; }
23         public System.Int32? IntField { get; set; }
24         public System.Decimal? MoneyField { get; set; }
25         public System.Decimal? NumericField { get; set; }
26         public System.String NVarcharField { get; private set; }
27         public System.Single? RealField { get; set; }
28         public System.TimeSpan? TimeField { get; set; }
29         public System.Byte[] TimestampField { get; set; }
30 
31         public void SetNVarcharField(string value)
32         {
33             value.MustNotNullAndNotWhiteSpace(value);
34 
35             this.NVarcharField = value;
36         }
37 
38         internal void PublishCreatedEvent()
39         {
40             this.PublishEvent(new TestGridCreatedEvent());
41         }
42     }
43 }

xxx屬性必須唯一,跨聚合驗證。

倉儲負責判斷唯一性,應用服務負責驗證,注意:是先驗證,然后修改的實體。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 using Happy.Command;
 8 using Happy.Application;
 9 using Happy.Example.Domain.TestGrids;
10 using Happy.Example.Commands.TestGrids;
11 
12 namespace Happy.Example.Application.TestGrids
13 {
14     public class TestGridCommandHandler : ApplicationService,
15         ICommandHandler<CreateTestGridComamnd>,
16         ICommandHandler<UpdateTestGridComamnd>,
17         ICommandHandler<DeleteTestGridComamnd>
18     {
19         public void Handle(CreateTestGridComamnd command)
20         {
21             var testGridService = this.Service<TestGridService>();
22 
23             testGridService.CheckNVarcharFieldUnique(command.NVarcharField);
24             var testGrid = command.CreateTestGrid();
25 
26             testGridService.Create(testGrid);
27             command.Result = testGrid.Id;
28         }
29 
30         public void Handle(UpdateTestGridComamnd command)
31         {
32             var testGridService = this.Service<TestGridService>();
33 
34             var testGrid = testGridService.LoadAndDetach(command.Id);
35             if (testGrid.NVarcharField != command.NVarcharField)
36             {
37                 testGridService.CheckNVarcharFieldUnique(command.NVarcharField);
38             }
39             command.UpdateTestGrid(testGrid);
40 
41             testGridService.Update(testGrid);
42         }
43 
44         public void Handle(DeleteTestGridComamnd command)
45         {
46             this.Service<TestGridService>().Delete(command.Id);
47         }
48     }
49 }
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 using AutoMapper;
 8 
 9 using Happy.Infrastructure;
10 using Happy.Infrastructure.AutoMapper;
11 using Happy.Command;
12 using Happy.Example.Domain.TestGrids;
13 
14 namespace Happy.Example.Commands.TestGrids
15 {
16     public class UpdateTestGridComamnd : ICommand, IHasIdProperty<Guid>
17     {
18         public Guid Id { get; set; }
19         public System.Int64? BigIntField { get; set; }
20         public System.Boolean? BitField { get; set; }
21         public System.DateTime? DateField { get; set; }
22         public System.DateTime? DateTimeField { get; set; }
23         public System.Decimal? DecimalField { get; set; }
24         public System.Double? FloatField { get; set; }
25         public System.Int32? IntField { get; set; }
26         public System.Decimal? MoneyField { get; set; }
27         public System.Decimal? NumericField { get; set; }
28         public System.String NVarcharField { get; set; }
29         public System.Single? RealField { get; set; }
30         public System.TimeSpan? TimeField { get; set; }
31         public System.Byte[] TimestampField { get; set; }
32         public byte[] OptimisticKey { get; set; }
33 
34         internal void UpdateTestGrid(TestGrid testGrid)
35         {
36             Mapper.Map(this, testGrid);
37             testGrid.SetNVarcharField(this.NVarcharField);
38         }
39 
40         static UpdateTestGridComamnd()
41         {
42             var map = Mapper.CreateMap<UpdateTestGridComamnd, TestGrid>();
43             map.ForMember(x => x.Id, m => m.Ignore());
44             map.IgnoreNotPublicSetter();
45         }
46     }
47 }
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 using Happy.Example.Domain.TestGrids;
 8 
 9 namespace Happy.Example.Application.TestGrids
10 {
11     public partial class TestGridService
12     {
13         protected override void AfterCreate(TestGrid aggregate)
14         {
15             base.AfterCreate(aggregate);
16 
17             aggregate.PublishCreatedEvent();
18         }
19 
20         internal void CheckNVarcharFieldUnique(string value)
21         {
22             if (!this.Repository.IsNVarcharFieldExist(value))
23             {
24                 throw new InvalidOperationException("NVarcharField必須唯一");
25             }
26         }
27     }
28 }

備注

一些好的資源:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM