DDD:关于模型的合法性,Entity.IsValid()合理吗?

前端之家收集整理的这篇文章主要介绍了DDD:关于模型的合法性,Entity.IsValid()合理吗?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

背景

见过很多框架(包括我自己的)都会在实体的定义中包含一个IsValid()方法,用来判断实体的合法性,是否应该这样设计呢?本文就这个问题介绍一点想法,希望大家多批评。

实体能否处于“非法”状态?

实体是否应该包含IsValid()方法的深层次问题是:“实体能否处于非法状态?”。我们来定义一些术语,接下来我就引用这些术语:

@H_403_11@
  • A模式:实体允许处于非法状态,但是实体要包含一个IsValid()方法进行校验。
  • B模式:实体不允许处于非法状态,业务逻辑必须保证这一点。
  • 关于A模式我不想多说了,A模式本身没有问题的,今天重点说说如何实现B模式。

    如何实现B模式?

    最好的说明就是写一个例子,下面是我们例子的需求:

    @H_403_11@
  • xxx属性不能为空。
  • xxx属性必须唯一。
  • 这个例子非常简单,也具有代表性,可以进一步抽象为:

    @H_403_11@
  • 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 }

    备注

    一些好的资源:

    @H_403_11@
  • http://msdn.microsoft.com/en-us/library/ff664356(v=pandp.50).aspx
  • http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-ddd/
  • http://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/
  • http://abdullin.com/journal/2009/1/29/ddd-and-rule-driven-ui-validation-in-net.html
  • http://www.iteye.com/topic/413815
  • http://devlicio.us/blogs/billy_mccafferty/archive/2009/02/17/a-response-to-validation-in-a-ddd-world.aspx
  • 猜你在找的设计模式相关文章