使用CQRS处理介体管道中的错误/异常? [英] Handling errors/exceptions in a mediator pipeline using CQRS?

查看:126
本文介绍了使用CQRS处理介体管道中的错误/异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试关注 github要点.我还不太了解如何将所有这些信息联系起来,所以这是我的第一步.仅供参考-我正在将Autofac用于DI和Web Api2.在CQRS之后,这是一个查询.

I'm trying to follow this post by Jimmy Bogard to implement a mediator pipeline so I can use pre/post request handlers to do some work. From the comments on that article I come to this github gist. I don't quite understand how to hook all of this up yet, so here is my first go. FYI - I'm using Autofac for DI and Web Api 2. Following CQRS, here is a query.

public class GetAccountRequest : IAsyncRequest<GetAccountResponse>
{
    public int Id { get; set; }
}

//try using fluent validation
public class GetAccountRequestValidationHandler 
    : AbstractValidator<GetAccountRequest>, IAsyncPreRequestHandler<GetAccountRequest>
{
    public GetAccountRequestValidationHandler() {
        RuleFor(m => m.Id).GreaterThan(0).WithMessage("Please specify an id.");
    }

    public Task Handle(GetAccountRequest request) {
        Debug.WriteLine("GetAccountPreProcessor Handler");   
        return Task.FromResult(true);
    }
}

public class GetAccountResponse
{
    public int AccountId { get; set; }
    public string Name { get; set; }
    public string AccountNumber { get; set; }
    public string Nickname { get; set; }
    public string PhoneNumber { get; set; }
    public List<OrderAckNotification> OrderAckNotifications { get; set; }

    public class OrderAckNotification {
        public int Id { get; set; }
        public bool IsDefault { get; set; }
        public string Description { get; set; }
        public string Type { get; set; }
    }
}

GetAccountRequestHandler:

GetAccountRequestHandler:

public class GetAccountRequestHandler 
    : IAsyncRequestHandler<GetAccountRequest, GetAccountResponse>
{
    private readonly IRedStripeDbContext _dbContext;

    public GetAccountRequestHandler(IRedStripeDbContext redStripeDbContext)
    {
        _dbContext = redStripeDbContext;
    }

    public async Task<GetAccountResponse> Handle(GetAccountRequest message)
    {
        //some mapping code here.. omitted for brevity
        Mapper.AssertConfigurationIsValid();

        return await _dbContext.Accounts.Where(a => a.AccountId == message.Id)
            .ProjectToSingleOrDefaultAsync<GetAccountResponse>();
    }

这是当前的Web api 2控制器,显示HttpGet.

Here is the current web api 2 controller showing the HttpGet.

[RoutePrefix("api/Accounts")]
public class AccountsController : ApiController
{
    private readonly IMediator _mediator;

    public AccountsController(IMediator mediator)
    {
        _mediator = mediator;
    }

    // GET: api/Accounts/2
    [Route("{id:int}")]
    [HttpGet]
    public async Task<IHttpActionResult> GetById([FromUri] GetAccountRequest request)
    {
        var model = await _mediator.SendAsync<GetAccountResponse>(request);

        return Ok(model);
    }
}

最后是依赖性解析代码:

Finally here is the dependency resolution code:

public void Configuration(IAppBuilder app)
{
    var config = new HttpConfiguration();

    ConfigureDependencyInjection(app, config);

    WebApiConfig.Register(config);
    app.UseWebApi(config);
}

private static void ConfigureDependencyInjection(IAppBuilder app, 
    HttpConfiguration config)
{
    var builder = new ContainerBuilder();
    builder.RegisterSource(new ContravariantRegistrationSource());
    builder.RegisterAssemblyTypes(typeof(IMediator).Assembly).AsImplementedInterfaces();

    builder.Register<SingleInstanceFactory>(ctx =>
    {
        var c = ctx.Resolve<IComponentContext>();
        return t => c.Resolve(t);
    });

    builder.Register<MultiInstanceFactory>(ctx =>
    {
        var c = ctx.Resolve<IComponentContext>();
        return t => (IEnumerable<object>)c.Resolve(
            typeof(IEnumerable<>).MakeGenericType(t));
    });

    //register all pre handlers
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
        .As(type => type.GetInterfaces()
            .Where(t => t.IsClosedTypeOf(typeof(IAsyncPreRequestHandler<>))));

    //register all post handlers
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
        .As(type => type.GetInterfaces()
            .Where(t => t.IsClosedTypeOf(typeof(IAsyncPostRequestHandler<,>))));


    //register all handlers
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
        .As(type => type.GetInterfaces()
            .Where(t => t.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>)))
            .Select(t => new KeyedService("asyncRequestHandler", t)));

    //register pipeline decorator
    builder.RegisterGenericDecorator(typeof(AsyncMediatorPipeline<,>), 
        typeof(IAsyncRequestHandler<,>), "asyncRequestHandler");

    // Register Web API controller in executing assembly.
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();

    //register RedStripeDbContext
    builder.RegisterType<RedStripeDbContext>().As<IRedStripeDbContext>()
        .InstancePerRequest();

    builder.RegisterType<AutofacServiceLocator>().AsImplementedInterfaces();
    var container = builder.Build();

    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

    // This should be the first middleware added to the IAppBuilder.
    app.UseAutofacMiddleware(container);

    // Make sure the Autofac lifetime scope is passed to Web API.
    app.UseAutofacWebApi(config);
}

我正在进入GetAccountRequestValidationHandler.但是,当验证失败(传递了0的ID)时,如何引发异常或停止管道的执行?如何返回.WithMessage?

I am getting into the GetAccountRequestValidationHandler. However, when the validation fails (an id of 0 was passed), how do I throw an exception or stop the execution of the pipeline? How do I return the .WithMessage?

推荐答案

我也为此苦苦挣扎.似乎有两个/三个选项:

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

正在使用预处理程序...

1)您可以将错误加载到请求中,并让主处理程序在处理命令/查询之前检查错误

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)让预处理器引发异常.似乎在这种做法上有很多分歧.一方面,感觉就像管理带有异常的控制流,但是专业"阵营认为,客户端应该负责发送有效的命令. IE.它可以发送ajax查询以确认用户名可用,然后再让用户单击创建帐户".在这种情况下,违反此规则的异常可能是由于竞赛条件造成的.

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.

将验证处理程序直接放入管道中.

我相信这更符合@jbogard的想法. 我目前没有使用它,但是我已经草拟了它的外观-可能有更好的示例,当然,您定义和处理事物的方式可能会有所不同.其要点在于,由于它是管道的一部分,因此验证运行程序可以返回到调用方,而无需调用主处理程序.

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

以下是使用AutoFac进行连接的代码:

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....

这篇关于使用CQRS处理介体管道中的错误/异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆