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

where should put input validation in Domain Driven Design?

我想知道我们应该将输入验证放在哪里(想象一下 API 调用发送输入来应用用户的空闲时间)。在service层注入validationclass,在service内部调用validate方法对吗?或者把它放在基础设施层甚至领域模型中更好?我只是想看一个示例代码,它在域驱动设计方法中为 API 实现输入验证?如果我使用 CQRS 架构怎么办?

我在我的 DDD/CQRS 项目中使用以下方法,项目结构是 API 层、域层、数据访问层,所有来自 UI 或用户的输入数据都是验证之前,命令被创建和调度,以更新域的状态,我们验证输入数据的时间第一个是在 UI,(Angular 应用程序),第二个是在 Web API 层,如果数据有效,则创建并调度 CQRS 命令,之后您可以进行业务逻辑验证。您可以使用 FastValidator or FluentValidation

进行验证

更新:这是我们为创建批处理实体提供的简单示例 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 中我们有业务逻辑验证 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;
}

what if I use CQRS architecture?

我不认为 CQRS 会改变很多东西。

通常,当您在域实体中调用方法时,您的输入应该已经从它们的域不可知形式转换为值对象。

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

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

我的方法是在域模型中进行验证,我验证聚合、实体、值对象等的功能

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

为什么在不同层重复验证?好吧,因为如果您只依赖 UI 或应用程序服务验证,那么如果它们出于某种原因不能正常工作并且您没有验证领域模型,那么您可能会在没有验证的情况下执行领域功能它。

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

最后,是否执行 CQRS 与您决定将验证放在哪里无关。只是如果你做 CQRS,那么应用层的验证就更容易做,因为你可以把它们放在包装命令和查询的装饰器中。

希望我的解释对您有所帮助。

您应该在尝试修改您的域之前在您的应用服务中进行验证。验证应该朝向您的应用程序的边缘(但不在 UI 中),因此无效或不完整的请求甚至不会进入您的域模型。

我认为它是两级验证,因为您将在尝试模型上的某些行为之前验证请求,然后模型应再次验证内部一致性,因为它永远不会保持在无效状态。

where should put input validation [in Domain Driven Design]?

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

您不会等到无效数据跨越 4 层才丢弃它。

Input 验证恰恰意味着你不需要任何其他东西(例如加载其他数据)来检查它,所以你最好尽快做。当然,需要注意的是,必须仔细检查任何可以规避的验证 - 例如客户端 javascript。