Gestion des erreurs / exceptions dans un pipeline de médiateur à l’aide de CQRS?

Jimmy Bogard essaie de suivre l’oeuvre de Jimmy Bogard pour mettre en place un pipeline de médiateurs afin que je puisse utiliser des gestionnaires de demande avant / après pour effectuer certains travaux. Des commentaires sur cet article, je viens à cette idée de github . Je ne comprends pas très bien comment mettre tout cela en place, alors voici mon premier essai. FYI – J’utilise Autofac pour DI et Web Api 2. Après CQRS, voici une requête.

public class GetAccountRequest : IAsyncRequest { public int Id { get; set; } } //try using fluent validation public class GetAccountRequestValidationHandler : AbstractValidator, IAsyncPreRequestHandler { 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 ssortingng Name { get; set; } public ssortingng AccountNumber { get; set; } public ssortingng Nickname { get; set; } public ssortingng PhoneNumber { get; set; } public List OrderAckNotifications { get; set; } public class OrderAckNotification { public int Id { get; set; } public bool IsDefault { get; set; } public ssortingng Description { get; set; } public ssortingng Type { get; set; } } } 

GetAccountRequestHandler:

 public class GetAccountRequestHandler : IAsyncRequestHandler { private readonly IRedSsortingpeDbContext _dbContext; public GetAccountRequestHandler(IRedSsortingpeDbContext redSsortingpeDbContext) { _dbContext = redSsortingpeDbContext; } public async Task Handle(GetAccountRequest message) { //some mapping code here.. omitted for brevity Mapper.AssertConfigurationIsValid(); return await _dbContext.Accounts.Where(a => a.AccountId == message.Id) .ProjectToSingleOrDefaultAsync(); } 

Voici le contrôleur actuel de l’api Web 2 affichant 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 GetById([FromUri] GetAccountRequest request) { var model = await _mediator.SendAsync(request); return Ok(model); } } 

Enfin, voici le code de résolution de dépendance:

 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(ctx => { var c = ctx.Resolve(); return t => c.Resolve(t); }); builder.Register(ctx => { var c = ctx.Resolve(); return t => (IEnumerable)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 RedSsortingpeDbContext builder.RegisterType().As() .InstancePerRequest(); builder.RegisterType().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); } 

J’entre dans le GetAccountRequestValidationHandler. Cependant, lorsque la validation échoue (un identifiant de 0 a été passé), comment puis-je lever une exception ou arrêter l’exécution du pipeline? Comment renvoyer le .WithMessage?

J’ai à moitié du mal avec ça aussi. Il semble y avoir deux / trois options:

Utiliser un pré-handler …

1) vous pouvez charger des erreurs dans la demande et demander au gestionnaire principal de vérifier les erreurs avant de traiter la commande / requête

OU

2) Demandez au gestionnaire préalable de lancer une exception. Il semble y avoir pas mal de désaccord autour de cette pratique. D’un côté, on a l’impression de gérer des stream de contrôle avec des exceptions, mais le camp “pro” affirme que le client devrait être responsable de l’envoi d’une commande valide. C’est à dire. Il peut envoyer des requêtes ajax pour confirmer qu’un nom d’utilisateur est disponible avant de laisser l’utilisateur cliquer sur “Créer un compte”. Dans ce cas, une exception enfreignant cette règle serait due à une situation de concurrence critique.

Placez le gestionnaire de validation directement dans le pipeline.

Je crois que cela va plus dans le sens de ce que pensait @jbogard. Je ne l’utilise pas actuellement, mais j’ai esquissé ce à quoi cela pourrait ressembler – il existe probablement de meilleurs exemples, et bien sûr, la façon dont vous voulez définir et gérer les choses peut varier. L’essentiel est que, comme il fait partie du pipeline, le coureur de validation peut revenir à l’appelant sans que le gestionnaire principal ne soit appelé.

 public class AsyncValidationPipeline : IAsyncRequestHandler where TRequest : IAsyncRequest { private IAsyncRequestHandler _inner; private IValidator[] _validators; public AsyncValidationPipeline(IAsyncRequestHandler inner, IValidator[] validators) { _inner = inner; _validators = validators; } public Task Handle(TRequest message) { List errors = new List(); 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); } } 

Voici le code pour le connecter avec AutoFac:

  //register all pre handlers builder.RegisterAssemblyTypes(assembliesToScan) .AsClosedTypesOf(typeof(IAsyncPreRequestHandler<>)); //register all post handlers builder.RegisterAssemblyTypes(assembliesToScan) .AsClosedTypesOf(typeof(IAsyncPostRequestHandler<,>)); const ssortingng handlerKey = "async-service-handlers"; const ssortingng 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 

J’espère que cela t’aides….