Обработка ошибок / исключений в конвейере посредника с помощью CQRS?

Я пытаюсь следовать этот пост Джимми Богард для реализации конвейера посредника, чтобы я мог использовать обработчики запросов pre/post для выполнения некоторой работы. Из комментариев к этой статье я прихожу к этому github gist. Я еще не совсем понимаю, как все это подключить, так что вот мой первый ход. FYI-я использую Autofac для DI и Web Api 2. После CQRS, вот запрос.

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:

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.

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

наконец, вот код разрешения зависимостей:

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), как мне создать исключение или остановить выполнение конвейера? Как мне вернуть .WithMessage?

1 ответов


Я тоже наполовину борюсь с этим. Кажется, есть два-три варианта:

используя заранее-обработчик...

1) Вы можете загрузить ошибки в запрос и проверить основной обработчик на наличие ошибок перед обработкой команды/запроса

или

2)У предварительного обработчика есть исключение. Похоже, что вокруг этой практики есть много разногласий. С одной стороны, это похоже на управление control-flow с исключения, но лагерь " pro " утверждает, что клиент должен нести ответственность за отправку действительной команды для начала. То есть. Он может отправлять запросы ajax, чтобы подтвердить, что имя пользователя доступно, прежде чем позволить пользователю нажать "Создать учетную запись". В этом случае исключение, нарушающее это правило, будет связано с состоянием гонки.

поместите обработчик проверки непосредственно в конвейер.

Я считаю, что это больше похоже на то, что думал @jbogard. Я в настоящее время я не использую это, но я набросал, как это может выглядеть-есть, вероятно, лучшие примеры, и, конечно, точно, как вы хотите определить и обрабатывать вещи, могут отличаться. Суть в том, что, будучи частью конвейера, бегун проверки может вернуться к вызывающему объекту без вызова основного обработчика.

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:

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

надеюсь, что это помогает....