在域驱动设计中应将输入验证放在何处?

时间:2018-10-11 13:18:39

标签: validation domain-driven-design cqrs

我想知道我们应该在什么地方进行输入验证(想象一个API调用发送输入以应用用户的空闲时间)。在服务层中注入验证类并在服务内部调用validate方法是否正确?还是最好将其放入基础结构层甚至是Domain模型中?我只想看一个示例代码,该代码在域驱动的设计方法中实现对API输入的验证?如果我使用CQRS架构怎么办?

5 个答案:

答案 0 :(得分:1)

我在DDD / CQRS项目中使用以下方法,一个项目的结构是API层,域层,数据访问层,所有来自UI或用户的输入数据都经过验证, 创建并分派命令以更新Domain的状态,并验证输入数据的时间,一次是在UI(Angular应用)上,第二次是在Web API层中(如果数据有效),则创建CQRS命令并分派之后,您可以进行业务逻辑验证。为了进行验证,您可以使用FastValidatorFluentValidation

更新:这是我们拥有用于创建批处理实体的API的简单示例。

[HttpPost]
[Route("create")]  
public IHttpActionResult Create([FromBody] BatchEditModel model)
{
    var createCommand = model.Map<BatchEditModel, CreateBatchCommand>();

    var result = (OperationResult<int>) _commandDispatcher.Dispatch(createCommand);

    return Result(result);
}

如您所见,用户输入数据将为BatchEditModel

因此我们有BatchEditModelValidator,其中包含输入数据验证:

public class BatchEditModelValidator : AbstractValidator<BatchEditModel>
{
    public BatchEditModelValidator()
    {
        RuleFor(x => x.Number).NotEmpty()
            .WithMessage(ValidatorMessages.MustBeSpecified);
        RuleFor(x => x.ClientId).GreaterThan(0)
            .WithMessage(ValidatorMessages.MustBeSpecified);
        RuleFor(x => x.EntryAssigneeId).GreaterThan(0)
            .WithMessage(ValidatorMessages.MustBeSpecified);
        RuleFor(x => x.ReviewAssigneeId).GreaterThan(0)
            .WithMessage(ValidatorMessages.MustBeSpecified);
        RuleFor(x => x.Description).NotEmpty()
            .WithMessage(ValidatorMessages.MustBeSpecified);
    }
}

此验证器将在BatchEditModel映射到CreateBatchCommand之前执行

,在CreateBatchCommandHandler中,我们进行了Bussines逻辑验证CheckUniqueNumber

public OperationResult Handle(CreateBatchCommand command)
{
    var result = new OperationResult<int>();
    if (CheckUniqueNumber(result, command.ClientId, command.Number))
    {
        if (result.IsValid)
        {
            var batch = _batchFactory.Create(command);
            _batchRepository.Add(batch);
            _batchRepository.Save();

            result.Value = batch.Id;
        }
    }
    return result;
}

答案 1 :(得分:1)

  

如果我使用CQRS架构该怎么办?

我不希望CQRS会有很大改变。

通常,当您在域实体中调用方法时,您的输入应该已经从其与域无关的形式转换为值对象。

值对象应该在有效状态下构造,并且通常会在产生约束的构造函数/工厂方法中包括对约束的检查。但是,在Java和类似语言中,通常会抛出构造函数的实现(因为构造函数没有其他报告问题的方式)。

通常,客户想要的是对输入数据所违反的所有约束的清楚了解,而不仅仅是第一个约束。因此,您可能需要将约束作为模型中的一等公民淘汰,作为可以检查的谓词。

答案 2 :(得分:1)

我的方法是将验证放在域模型中,我验证集合,实体,值对象等的功能。

然后,您也可以验证应用程序服务和用户界面。但是这些验证是一个加号,从用户的角度来看,验证增强,因为验证更快。

为什么在不同的层重复进行验证?好吧,因为如果您仅依赖UI或应用程序服务验证,则可能由于某种原因它们不能很好地工作,并且您没有验证域模型,那么您在执行域功能时将不对其进行验证。

此外,我要指出的是,并非所有验证都可以在UI或应用程序层进行,因为您可能必须访问域。

最后,是否进行CQRS取决于您决定放置验证的位置。只是,如果您执行CQRS,则可以在应用程序层进行验证,因为您可以将其放入包装命令和查询的装饰器中。

希望我的解释有所帮助。

答案 3 :(得分:1)

您应先在应用服务中进行验证,然后再尝试修改您的域。验证应该针对您应用的边缘(而不是用户界面),这样无效或不完整的请求甚至都不会进入您的域模型。

我认为它是两个验证级别,因为您将在对模型进行某些行为之前先验证请求,然后模型应再次验证内部一致性,因为它永远无法保持无效状态。

答案 4 :(得分:1)

  

应在[域驱动设计]中将输入验证放在哪里?

这在很大程度上与DDD无关,但是:与输入源最接近。

您不会等到无效数据越过4层将其丢弃。

输入验证恰好意味着您不需要其他任何内容(例如,加载其他数据)来进行检查,因此您最好尽快进行验证。当然,需要注意的是,就像任何可以规避的验证都必须仔细检查-例如客户端javascript。