Skip to content

Commit 0684d35

Browse files
Quentin SCHROTERclaude
andcommitted
Rewrite ChainOfResponsibility as composable Pipeline middleware
- Replace old ResponsibilityChain (7 files) with new ChainOfResponsibility - Two variants: <TRequest> (no result) and <TRequest, TResult> (with result) - Implements IMiddleware so it composes with Pipeline via .With(cor) / .Add(cor) - Also implements ICommandHandler for standalone usage - Simple list iteration instead of linked-list Link abstraction - Factory pattern with fluent .Add<T>() builder, DI-integrated - Add instance overloads .With(instance) and .Add(instance) on IPipelineFactory<T,R> - Make monitoring optional: TraceFactory tolerates missing IOptionsMonitor - 7 new tests covering standalone and pipeline-composed scenarios Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a896fa7 commit 0684d35

20 files changed

+531
-208
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Bones.Flow.Core
8+
{
9+
internal class ChainOfResponsibility<TRequest> : IChainOfResponsibility<TRequest>
10+
where TRequest : IRequest
11+
{
12+
private List<IChainOfResponsibilityHandler<TRequest>> _handlers;
13+
private ILogger<ChainOfResponsibility<TRequest>> _logger;
14+
15+
public ChainOfResponsibility(
16+
ILogger<ChainOfResponsibility<TRequest>> logger
17+
)
18+
{
19+
_logger = logger;
20+
}
21+
22+
public void Configure(List<IChainOfResponsibilityHandler<TRequest>> handlers)
23+
{
24+
_handlers = handlers ?? throw new ArgumentNullException(nameof(handlers));
25+
}
26+
27+
async Task ICommandHandler<TRequest>.HandleAsync(TRequest request, CancellationToken cancellationToken, bool commit)
28+
{
29+
foreach (var handler in _handlers)
30+
{
31+
cancellationToken.ThrowIfCancellationRequested();
32+
33+
try
34+
{
35+
if (await handler.HandleAsync(request, cancellationToken))
36+
return;
37+
}
38+
catch (Exception ex)
39+
{
40+
_logger.LogError(ex, "An error occurred in ChainOfResponsibility handler {handler}", handler.GetType().Name);
41+
throw;
42+
}
43+
}
44+
}
45+
46+
async Task IMiddleware<TRequest>.HandleAsync(TRequest request, Func<Task> next, CancellationToken cancellationToken)
47+
{
48+
foreach (var handler in _handlers)
49+
{
50+
cancellationToken.ThrowIfCancellationRequested();
51+
52+
try
53+
{
54+
if (await handler.HandleAsync(request, cancellationToken))
55+
break;
56+
}
57+
catch (Exception ex)
58+
{
59+
_logger.LogError(ex, "An error occurred in ChainOfResponsibility handler {handler}", handler.GetType().Name);
60+
throw;
61+
}
62+
}
63+
64+
await next();
65+
}
66+
}
67+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
namespace Bones.Flow.Core
6+
{
7+
internal class ChainOfResponsibilityFactory<TRequest> : IChainOfResponsibilityFactory<TRequest>
8+
where TRequest : IRequest
9+
{
10+
private IServiceProvider _provider;
11+
private List<IChainOfResponsibilityHandler<TRequest>> _handlers;
12+
13+
public ChainOfResponsibilityFactory(IServiceProvider provider)
14+
{
15+
_provider = provider;
16+
_handlers = new List<IChainOfResponsibilityHandler<TRequest>>();
17+
}
18+
19+
public IChainOfResponsibilityFactory<TRequest> Add<THandler>()
20+
where THandler : IChainOfResponsibilityHandler<TRequest>
21+
{
22+
var handler = _provider.GetRequiredService<THandler>();
23+
return Add(handler);
24+
}
25+
26+
public IChainOfResponsibilityFactory<TRequest> Add<THandler>(THandler handler)
27+
where THandler : IChainOfResponsibilityHandler<TRequest>
28+
{
29+
_handlers.Add(handler);
30+
return this;
31+
}
32+
33+
public IChainOfResponsibility<TRequest> Build()
34+
{
35+
var chain = _provider.GetRequiredService<IChainOfResponsibility<TRequest>>();
36+
chain.Configure(_handlers);
37+
return chain;
38+
}
39+
}
40+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Bones.Flow.Core
8+
{
9+
internal class ResultChainOfResponsibility<TRequest, TResult> : IChainOfResponsibility<TRequest, TResult>
10+
where TRequest : IRequest<TResult>
11+
{
12+
private List<IChainOfResponsibilityHandler<TRequest, TResult>> _handlers;
13+
private ILogger<ResultChainOfResponsibility<TRequest, TResult>> _logger;
14+
15+
public ResultChainOfResponsibility(
16+
ILogger<ResultChainOfResponsibility<TRequest, TResult>> logger
17+
)
18+
{
19+
_logger = logger;
20+
}
21+
22+
public void Configure(List<IChainOfResponsibilityHandler<TRequest, TResult>> handlers)
23+
{
24+
_handlers = handlers ?? throw new ArgumentNullException(nameof(handlers));
25+
}
26+
27+
async Task<TResult> ICommandHandler<TRequest, TResult>.HandleAsync(TRequest request, CancellationToken cancellationToken, bool commit)
28+
{
29+
foreach (var handler in _handlers)
30+
{
31+
cancellationToken.ThrowIfCancellationRequested();
32+
33+
try
34+
{
35+
var (handled, result) = await handler.HandleAsync(request, cancellationToken);
36+
if (handled)
37+
return result;
38+
}
39+
catch (Exception ex)
40+
{
41+
_logger.LogError(ex, "An error occurred in ChainOfResponsibility handler {handler}", handler.GetType().Name);
42+
throw;
43+
}
44+
}
45+
46+
return default;
47+
}
48+
49+
async Task<TResult> IMiddleware<TRequest, TResult>.HandleAsync(TRequest request, Func<Task<TResult>> next, CancellationToken cancellationToken)
50+
{
51+
foreach (var handler in _handlers)
52+
{
53+
cancellationToken.ThrowIfCancellationRequested();
54+
55+
try
56+
{
57+
var (handled, result) = await handler.HandleAsync(request, cancellationToken);
58+
if (handled)
59+
return result;
60+
}
61+
catch (Exception ex)
62+
{
63+
_logger.LogError(ex, "An error occurred in ChainOfResponsibility handler {handler}", handler.GetType().Name);
64+
throw;
65+
}
66+
}
67+
68+
return await next();
69+
}
70+
}
71+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
namespace Bones.Flow.Core
6+
{
7+
internal class ResultChainOfResponsibilityFactory<TRequest, TResult> : IChainOfResponsibilityFactory<TRequest, TResult>
8+
where TRequest : IRequest<TResult>
9+
{
10+
private IServiceProvider _provider;
11+
private List<IChainOfResponsibilityHandler<TRequest, TResult>> _handlers;
12+
13+
public ResultChainOfResponsibilityFactory(IServiceProvider provider)
14+
{
15+
_provider = provider;
16+
_handlers = new List<IChainOfResponsibilityHandler<TRequest, TResult>>();
17+
}
18+
19+
public IChainOfResponsibilityFactory<TRequest, TResult> Add<THandler>()
20+
where THandler : IChainOfResponsibilityHandler<TRequest, TResult>
21+
{
22+
var handler = _provider.GetRequiredService<THandler>();
23+
return Add(handler);
24+
}
25+
26+
public IChainOfResponsibilityFactory<TRequest, TResult> Add<THandler>(THandler handler)
27+
where THandler : IChainOfResponsibilityHandler<TRequest, TResult>
28+
{
29+
_handlers.Add(handler);
30+
return this;
31+
}
32+
33+
public IChainOfResponsibility<TRequest, TResult> Build()
34+
{
35+
var chain = _provider.GetRequiredService<IChainOfResponsibility<TRequest, TResult>>();
36+
chain.Configure(_handlers);
37+
return chain;
38+
}
39+
}
40+
}

src/Bones.Flow/Core/RequestResultPipeline/RequestResultPipelineFactory.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ public IPipeline<TRequest, TResult> Build()
5151
public IBuildablePipelineFactory<TRequest, TResult> Add<TMiddleware>() where TMiddleware : IMiddleware<TRequest, TResult>
5252
{
5353
var middleware = _provider.GetRequiredService<TMiddleware>();
54+
return Add(middleware);
55+
}
5456

57+
public IBuildablePipelineFactory<TRequest, TResult> Add<TMiddleware>(TMiddleware middleware) where TMiddleware : IMiddleware<TRequest, TResult>
58+
{
5559
_requestResultMiddlewares.Add(middleware);
5660
_middlewareTypes.Add(MiddlewareType.RequestResultMiddleware);
5761

@@ -61,7 +65,11 @@ public IBuildablePipelineFactory<TRequest, TResult> Add<TMiddleware>() where TMi
6165
public IPipelineFactory<TRequest, TResult> With<TMiddleware>() where TMiddleware : IMiddleware<TRequest>
6266
{
6367
var middleware = _provider.GetRequiredService<TMiddleware>();
68+
return With(middleware);
69+
}
6470

71+
public IPipelineFactory<TRequest, TResult> With<TMiddleware>(TMiddleware middleware) where TMiddleware : IMiddleware<TRequest>
72+
{
6573
_requestMiddlewares.Add(middleware);
6674
_middlewareTypes.Add(MiddlewareType.RequestMiddleware);
6775

src/Bones.Flow/Core/ResponsibilityChain/ResponsibilityChain.cs

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/Bones.Flow/Core/ResponsibilityChain/ResponsibilityChainFactory.cs

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)