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

时间:2021-09-06 07:23:10

背景

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

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

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

  • A模式:实体允许处于非法状态,但是实体要包含一个IsValid()方法进行校验。
  • B模式:实体不允许处于非法状态,业务逻辑必须保证这一点。

关于A模式我不想多说了,A模式本身没有问题的,今天重点说说如何实现B模式。

如何实现B模式?

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

  • xxx属性不能为空。
  • xxx属性必须唯一。

这个例子非常简单,也具有代表性,可以进一步抽象为:

  • xxx属性不能为空,聚合自身的验证。
  • xxx属性必须唯一,跨聚合验证。

让我们一个一个来。

xxx属性不能为空,聚合自身的验证。

聚合本身应该负责自己状态的完整性,反射可能会绕过这些验证,使用类似AutoMapper的工具需要注意(我已经处理了)。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using Happy.Domain;
using Happy.Domain.Tree;
using Happy.Example.Events.TestGrids; using Happy.Infrastructure; namespace Happy.Example.Domain.TestGrids
{
public partial class TestGrid : AggregateRoot<Guid>
{
public System.Int64? BigIntField { get; set; }
public System.Boolean? BitField { get; set; }
public System.DateTime? DateField { get; set; }
public System.DateTime? DateTimeField { get; set; }
public System.Decimal? DecimalField { get; set; }
public System.Double? FloatField { get; set; }
public System.Int32? IntField { get; set; }
public System.Decimal? MoneyField { get; set; }
public System.Decimal? NumericField { get; set; }
public System.String NVarcharField { get; private set; }
public System.Single? RealField { get; set; }
public System.TimeSpan? TimeField { get; set; }
public System.Byte[] TimestampField { get; set; } public void SetNVarcharField(string value)
{
value.MustNotNullAndNotWhiteSpace(value); this.NVarcharField = value;
} internal void PublishCreatedEvent()
{
this.PublishEvent(new TestGridCreatedEvent());
}
}
}

xxx属性必须唯一,跨聚合验证。

仓储负责判断唯一性,应用服务负责验证,注意:是先验证,然后修改的实体。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using Happy.Command;
using Happy.Application;
using Happy.Example.Domain.TestGrids;
using Happy.Example.Commands.TestGrids; namespace Happy.Example.Application.TestGrids
{
public class TestGridCommandHandler : ApplicationService,
ICommandHandler<CreateTestGridComamnd>,
ICommandHandler<UpdateTestGridComamnd>,
ICommandHandler<DeleteTestGridComamnd>
{
public void Handle(CreateTestGridComamnd command)
{
var testGridService = this.Service<TestGridService>(); testGridService.CheckNVarcharFieldUnique(command.NVarcharField);
var testGrid = command.CreateTestGrid(); testGridService.Create(testGrid);
command.Result = testGrid.Id;
} public void Handle(UpdateTestGridComamnd command)
{
var testGridService = this.Service<TestGridService>(); var testGrid = testGridService.LoadAndDetach(command.Id);
if (testGrid.NVarcharField != command.NVarcharField)
{
testGridService.CheckNVarcharFieldUnique(command.NVarcharField);
}
command.UpdateTestGrid(testGrid); testGridService.Update(testGrid);
} public void Handle(DeleteTestGridComamnd command)
{
this.Service<TestGridService>().Delete(command.Id);
}
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using AutoMapper; using Happy.Infrastructure;
using Happy.Infrastructure.AutoMapper;
using Happy.Command;
using Happy.Example.Domain.TestGrids; namespace Happy.Example.Commands.TestGrids
{
public class UpdateTestGridComamnd : ICommand, IHasIdProperty<Guid>
{
public Guid Id { get; set; }
public System.Int64? BigIntField { get; set; }
public System.Boolean? BitField { get; set; }
public System.DateTime? DateField { get; set; }
public System.DateTime? DateTimeField { get; set; }
public System.Decimal? DecimalField { get; set; }
public System.Double? FloatField { get; set; }
public System.Int32? IntField { get; set; }
public System.Decimal? MoneyField { get; set; }
public System.Decimal? NumericField { get; set; }
public System.String NVarcharField { get; set; }
public System.Single? RealField { get; set; }
public System.TimeSpan? TimeField { get; set; }
public System.Byte[] TimestampField { get; set; }
public byte[] OptimisticKey { get; set; } internal void UpdateTestGrid(TestGrid testGrid)
{
Mapper.Map(this, testGrid);
testGrid.SetNVarcharField(this.NVarcharField);
} static UpdateTestGridComamnd()
{
var map = Mapper.CreateMap<UpdateTestGridComamnd, TestGrid>();
map.ForMember(x => x.Id, m => m.Ignore());
map.IgnoreNotPublicSetter();
}
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using Happy.Example.Domain.TestGrids; namespace Happy.Example.Application.TestGrids
{
public partial class TestGridService
{
protected override void AfterCreate(TestGrid aggregate)
{
base.AfterCreate(aggregate); aggregate.PublishCreatedEvent();
} internal void CheckNVarcharFieldUnique(string value)
{
if (!this.Repository.IsNVarcharFieldExist(value))
{
throw new InvalidOperationException("NVarcharField必须唯一");
}
}
}
}

备注

一些好的资源: