使用 CQRS 在中介管道中处理 errors/exceptions?
Handling errors/exceptions in a mediator pipeline using CQRS?
我正在尝试关注 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。我不太明白如何连接所有这些,所以这是我的第一次尝试。仅供参考 - 我将 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>();
}
这是显示 HttpGet 的当前 Web api 2 控制器。
[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。但是,当验证失败时(传递的 id 为 0),如何抛出异常或停止管道的执行?我如何 return .WithMessage?
我也为此苦苦挣扎。似乎有 two/three 个选项:
使用预处理器...
1) 您可以将错误加载到请求中,并让主处理程序在处理 command/query
之前检查错误
或
2) 让预处理器抛出异常。似乎围绕这种做法存在一些分歧。一方面,这感觉就像管理带有异常的控制流,但 "pro" 阵营认为客户端应该负责发送一个有效的命令。 IE。在让用户单击 "Create Account" 之前,它可以发送 ajax 查询以确认用户名可用。在这种情况下,违反此规则的异常将是由于竞争条件。
将验证处理程序直接放入管道中。
我相信这更符合@jbogard 的想法。
我目前没有使用它,但我已经勾勒出它可能的样子——可能有更好的例子,当然你想要如何定义和处理事情可能会有所不同。它的要点是,作为管道的一部分,验证运行器可以 return 到调用者,而无需调用主处理程序。
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
希望这对您有所帮助....
我正在尝试关注 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。我不太明白如何连接所有这些,所以这是我的第一次尝试。仅供参考 - 我将 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>();
}
这是显示 HttpGet 的当前 Web api 2 控制器。
[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。但是,当验证失败时(传递的 id 为 0),如何抛出异常或停止管道的执行?我如何 return .WithMessage?
我也为此苦苦挣扎。似乎有 two/three 个选项:
使用预处理器...
1) 您可以将错误加载到请求中,并让主处理程序在处理 command/query
之前检查错误或
2) 让预处理器抛出异常。似乎围绕这种做法存在一些分歧。一方面,这感觉就像管理带有异常的控制流,但 "pro" 阵营认为客户端应该负责发送一个有效的命令。 IE。在让用户单击 "Create Account" 之前,它可以发送 ajax 查询以确认用户名可用。在这种情况下,违反此规则的异常将是由于竞争条件。
将验证处理程序直接放入管道中。
我相信这更符合@jbogard 的想法。 我目前没有使用它,但我已经勾勒出它可能的样子——可能有更好的例子,当然你想要如何定义和处理事情可能会有所不同。它的要点是,作为管道的一部分,验证运行器可以 return 到调用者,而无需调用主处理程序。
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
希望这对您有所帮助....