Handling errors/exceptions in a mediator pipeline using CQRS?

筅森魡賤 提交于 2019-12-05 06:17:46

I've semi-struggling with this, too. It seems there are two/three options:

Using a pre-handler...

1) you can either load errors into the request and have the main handler check for errors before it processes the command/query

OR

2) Have the pre-handler throw an exception. It seems there's a fair bit of disagreement around this practice. On one hand it feels like managing control-flow with exceptions, but the "pro" camp argues that the client should be responsible for sending a valid command to begin with. Ie. It can send ajax queries to confirm that a user name is available prior to letting the user click "Create Account". In this case an exception breaking this rule would be due to a race condition.

Put the validation handler directly into the pipeline.

I believe this is more along the lines of what @jbogard was thinking. I am not currently using this, but I've sketched up what this might look like -- there are probably better examples out there, and of course exactly how you want to define and handle things may vary. The gist of it is that with it being part of the pipeline, the validation-runner can return to the caller without the main handler ever being called.

public class AsyncValidationPipeline<TRequest, TResponse> : IAsyncRequestHandler<TRequest, TResponse>
    where TRequest : IAsyncRequest<TResponse>
{
    private IAsyncRequestHandler<TRequest, TResponse> _inner;
    private IValidator<TRequest>[] _validators;

    public AsyncValidationPipeline(IAsyncRequestHandler<TRequest, TResponse> inner,
        IValidator<TRequest>[] validators)
    {
        _inner = inner;
        _validators = validators;
    }
    public Task<TResponse> Handle(TRequest message)
    {
        List<string> errors = new List<string>();
        if (_validators != null && _validators.Any())
        {
            errors = _validators.Where(v => v.Fails(message))
                .Select(v => v.ErrorMessage);
        }

        if (errors.Any())
        {
            throw new ValidationException(errors);
        }
        return _inner.Handle(message);
    }
}

Here's the code for hooking that up with AutoFac:

            //register all pre handlers
            builder.RegisterAssemblyTypes(assembliesToScan)
                .AsClosedTypesOf(typeof(IAsyncPreRequestHandler<>));

            //register all post handlers
            builder.RegisterAssemblyTypes(assembliesToScan)
                .AsClosedTypesOf(typeof(IAsyncPostRequestHandler<,>));

            const string handlerKey = "async-service-handlers";
            const string pipelineKey = "async-service-pipelines";

            // Request/Response for Query

            builder.RegisterAssemblyTypes(assembliesToScan)
                .AsKeyedClosedTypesOf(typeof(IAsyncRequestHandler<,>), handlerKey)
                ;

            // Decorate All Services with our Pipeline
            //builder.RegisterGenericDecorator(typeof(MediatorPipeline<,>), typeof(IRequestHandler<,>), fromKey: "service-handlers", toKey: "pipeline-handlers");
           builder.RegisterGenericDecorator(typeof(AsyncMediatorPipeline<,>), typeof(IAsyncRequestHandler<,>), fromKey: handlerKey, toKey: pipelineKey);

            // Decorate All Pipelines with our Validator
           builder.RegisterGenericDecorator(typeof(AsyncValidationHandler<,>), typeof(IAsyncRequestHandler<,>), fromKey: pipelineKey);//, toKey: "async-validator-handlers");

           // Add as many pipelines as you want, but the to/from keys must be kept in order and unique

Hope this helps....

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!