Search code examples
validationdomain-driven-designcqrs

where should put input validation in Domain Driven Design?


I was wondering where exactly we should put input validations(imagine an API call send input to apply free times of a user). Is it right to inject validation class in service Layer and call validate method inside service? or it's better to put it in the infrastructure layer or even in Domain model? I just wanted to see a sample code that's implement validation of input for an API in Domain-driven design approach? what if I use CQRS architecture?


Solution

  • I use in my DDD/CQRS project following approach, structure of a project is API layer, Domain layer, Data Access layer, all input data from UI or from User are validated before, command are created and dispatched, to update the state of Domain, and we validate input data two times one is on the UI, (Angular app), and second one in Web API layer, if the data are valid the CQRS command are created and dispatched after that you can have Business logic validation. For validation you can use FastValidator or FluentValidation

    UPDATE: Here is the simple example we have API for Create Batch Entity.

    [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);
    }
    

    As you can see as user input data will be BatchEditModel.

    so we have BatchEditModelValidator which contains input data validation:

    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);
        }
    }
    

    this Validator will be executed before BatchEditModel will be mapped to CreateBatchCommand

    and in CreateBatchCommandHandler we have Business logic validation 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;
    }