Skip to content

Commit 41f0993

Browse files
committed
ResponsibilityChain-1 Have support for intercepting a handler out of the box. Favor convention over configuration
ResponsibilityChain-2 The framework's default interception strategy should be configurable ResponsibilityChain-3 Override InterceptedHandler.ToString method to have a better view into InterceptedHandler to facilitate debugging
1 parent d519d1c commit 41f0993

24 files changed

+498
-118
lines changed

LICENSE

+5-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
1-
MIT License
1+
The MIT License (MIT)
22

3-
Copyright (c) 2018 Son Nguyen
3+
Copyright © 2019 Son Nguyen
44

5-
Permission is hereby granted, free of charge, to any person obtaining a copy
6-
of this software and associated documentation files (the "Software"), to deal
7-
in the Software without restriction, including without limitation the rights
8-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9-
copies of the Software, and to permit persons to whom the Software is
10-
furnished to do so, subject to the following conditions:
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
116

12-
The above copyright notice and this permission notice shall be included in all
13-
copies or substantial portions of the Software.
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
148

15-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

src/ResponsibilityChain.Tests/HandlerTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ public void GivenUnitsInAscendingOrder_ThrowsException(string workLog)
7373

7474
private class WorkLogParser : Handler<string, int>, IWorkLogParser
7575
{
76-
public WorkLogParser(WorkLogValidator workLogValidator, IndividualUnitParser individualUnitParser)
76+
public WorkLogParser(WorkLogValidator validator, IndividualUnitParser parser)
7777
{
78-
AddHandler(workLogValidator);
79-
AddHandler(individualUnitParser);
78+
AddHandler(validator);
79+
AddHandler(parser);
8080
}
8181
}
8282

src/ResponsibilityChain.Tests/Handler_AddHandlerTest.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace ResponsibilityChain.Tests
55
{
6+
// ReSharper disable once InconsistentNaming
67
public class Handler_AddHandlerTest
78
{
89
[Fact]
@@ -13,7 +14,7 @@ public void AddNullHandlerToTheChain_ThrowsException()
1314
// act
1415
Action action = () =>
1516
{
16-
var _ = new CompositeHandler(dummyHandler: null);
17+
var _ = new CompositeHandler(null);
1718
};
1819

1920
// assert

src/ResponsibilityChain.Tests/Handler_ToStringTest.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace ResponsibilityChain.Tests
55
{
6+
// ReSharper disable once InconsistentNaming
67
public class Handler_ToStringTest
78
{
89
private class CompositeWithNoNestedHandler : Handler<int, int>
@@ -71,7 +72,10 @@ public void GivenCompositeWithOneLevelNestedHandler_ReturnsCorrectHierarchy()
7172
{
7273
// arrange
7374
var handler = new CompositeWithOneLevelNestedHandler(
74-
new CompositeWithNoNestedHandler(new SimpleHandler1(), new SimpleHandler2()),
75+
new CompositeWithNoNestedHandler(
76+
new SimpleHandler1(),
77+
new SimpleHandler2()
78+
),
7579
new SimpleHandler1()
7680
);
7781

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Threading;
5+
using FluentAssertions;
6+
using Xunit;
7+
8+
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
9+
10+
namespace ResponsibilityChain.Tests
11+
{
12+
public class InterceptionTest
13+
{
14+
public class given_an_interception_strategy : InterceptionTest, IDisposable
15+
{
16+
public given_an_interception_strategy()
17+
{
18+
InterceptionStrategy.SetStrategy(new FakeInterceptionStrategy(new MockServiceProvider()));
19+
}
20+
21+
private class MockServiceProvider : IServiceProvider
22+
{
23+
public object GetService(Type serviceType)
24+
{
25+
if (typeof(IEnumerable<IInterceptor<CoreBusinessHandler, int, string>>).IsAssignableFrom(
26+
serviceType
27+
))
28+
{
29+
return new IInterceptor<CoreBusinessHandler, int, string>[]
30+
{
31+
new FeatureOptedOutInterceptor<CoreBusinessHandler, int, string>(),
32+
new StopwatchInterceptor(),
33+
new DebugInterceptor()
34+
};
35+
}
36+
37+
return ActivatorServiceProvider.Instance.GetService(serviceType);
38+
}
39+
}
40+
41+
[Fact]
42+
public void then_invokes_eligible_interceptors_before_invoking_the_original_handler()
43+
{
44+
// arrange
45+
StopwatchInterceptor.LogMessages?.Clear();
46+
DebugInterceptor.LogMessages?.Clear();
47+
var handler = new CompositeHandler(new CoreBusinessHandler(), new FallbackHandler());
48+
49+
// act
50+
var result = handler.Handle(111, null);
51+
52+
// assert
53+
StopwatchInterceptor.LogMessages.Should().NotBeNullOrEmpty();
54+
DebugInterceptor.LogMessages.Should().NotBeNullOrEmpty();
55+
result.Should().Be("unhandled");
56+
}
57+
58+
public void Dispose()
59+
{
60+
InterceptionStrategy.SetStrategy(new NoopInterceptionStrategy());
61+
}
62+
}
63+
64+
public class given_no_configuration_to_the_framework : InterceptionTest
65+
{
66+
[Fact]
67+
public void then_ignores_all_interceptors()
68+
{
69+
// arrange
70+
StopwatchInterceptor.LogMessages?.Clear();
71+
DebugInterceptor.LogMessages?.Clear();
72+
var handler = new CompositeHandler(new CoreBusinessHandler(), new FallbackHandler());
73+
74+
// act
75+
var result = handler.Handle(112, null);
76+
77+
// assert
78+
StopwatchInterceptor.LogMessages.Should().BeNullOrEmpty();
79+
DebugInterceptor.LogMessages.Should().BeNullOrEmpty();
80+
result.Should().Be("business handled");
81+
}
82+
}
83+
84+
private class FakeInterceptionStrategy : IInterceptionStrategy
85+
{
86+
private readonly IServiceProvider _serviceProvider;
87+
88+
public FakeInterceptionStrategy(IServiceProvider serviceProvider)
89+
{
90+
_serviceProvider = serviceProvider;
91+
}
92+
93+
public IHandler<TIn, TOut> Intercept<THandler, TIn, TOut>(THandler handler)
94+
where THandler : class, IHandler<TIn, TOut>
95+
{
96+
var interceptors =
97+
(IEnumerable<IInterceptor<THandler, TIn, TOut>>) _serviceProvider.GetService(
98+
typeof(IEnumerable<IInterceptor<THandler, TIn, TOut>>)
99+
);
100+
101+
return DefaultInterceptionStrategyHelper.Intercept(handler, interceptors);
102+
}
103+
}
104+
105+
private class StopwatchInterceptor : IInterceptor<CoreBusinessHandler, int, string>
106+
{
107+
static StopwatchInterceptor()
108+
{
109+
LogMessages = new List<string>();
110+
}
111+
112+
public static List<string> LogMessages { get; }
113+
114+
public string Intercept(IHandler<int, string> handler, int input, Func<int, string> next)
115+
{
116+
var stopwatch = Stopwatch.StartNew();
117+
118+
LogMessages.Add($"DEBUG {typeof(CoreBusinessHandler)} started at {DateTime.Now.ToLongTimeString()}");
119+
stopwatch.Start();
120+
121+
var output = handler.Handle(input, next);
122+
123+
stopwatch.Stop();
124+
LogMessages.Add($"DEBUG {typeof(CoreBusinessHandler)} completed at {DateTime.Now.ToLongTimeString()}");
125+
LogMessages.Add($"DEBUG {typeof(CoreBusinessHandler)} elapsed {stopwatch.ElapsedMilliseconds} ms");
126+
127+
return output;
128+
}
129+
}
130+
131+
private class DebugInterceptor : IInterceptor<CoreBusinessHandler, int, string>
132+
{
133+
static DebugInterceptor()
134+
{
135+
LogMessages = new List<string>();
136+
}
137+
138+
public static List<string> LogMessages { get; }
139+
140+
public string Intercept(IHandler<int, string> handler, int input, Func<int, string> next)
141+
{
142+
LogMessages.Add($"DEBUG {typeof(CoreBusinessHandler)} input = {input}");
143+
string output;
144+
145+
try
146+
{
147+
output = handler.Handle(input, next);
148+
}
149+
catch (Exception exception)
150+
{
151+
LogMessages.Add($"error: {exception.Message}");
152+
throw;
153+
}
154+
155+
LogMessages.Add($"DEBUG {typeof(CoreBusinessHandler)} > {output}");
156+
157+
return output;
158+
}
159+
}
160+
161+
private class FeatureOptedOutInterceptor<THandler, TIn, TOut> : IInterceptor<THandler, TIn, TOut>
162+
where THandler : IHandler<TIn, TOut>
163+
{
164+
public TOut Intercept(IHandler<TIn, TOut> handler, TIn input, Func<TIn, TOut> next)
165+
{
166+
// user chooses to ignore this feature
167+
return next(input);
168+
}
169+
}
170+
171+
private class CoreBusinessHandler : IHandler<int, string>
172+
{
173+
public string Handle(int input, Func<int, string> next)
174+
{
175+
Thread.Sleep(100);
176+
177+
return "business handled";
178+
}
179+
}
180+
181+
private class FallbackHandler : IHandler<int, string>
182+
{
183+
public string Handle(int input, Func<int, string> next)
184+
{
185+
return "unhandled";
186+
}
187+
}
188+
189+
private class CompositeHandler : Handler<int, string>
190+
{
191+
public CompositeHandler(CoreBusinessHandler coreBusinessHandler, FallbackHandler fallbackHandler)
192+
{
193+
AddHandler(coreBusinessHandler);
194+
AddHandler(fallbackHandler);
195+
}
196+
}
197+
}
198+
}

src/ResponsibilityChain.Tests/ResponsibilityChain.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
66
</PropertyGroup>
77
<ItemGroup>
8+
<PackageReference Include="FluentAssertions" Version="5.6.0" />
89
<PackageReference Include="xunit" Version="2.4.1" />
910
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
1011
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />

src/ResponsibilityChain.Tests/ReturnCompletedTaskFromDefaultValueHandlerTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ private class CompositeFooAsyncHandler : Handler<int, Task<string>>
1111
public CompositeFooAsyncHandler(BarHandler barHandler)
1212
{
1313
AddHandler(barHandler);
14-
AddHandler(ReturnCompletedTaskFromDefaultValueHandler<int, string>.Instance);
15-
AddHandler(ThrowNotSupportedHandler<int, Task<string>>.Instance);
14+
AddHandler(new ReturnCompletedTaskFromDefaultValueHandler<int, string>());
15+
AddHandler(new ThrowNotSupportedHandler<int, Task<string>>());
1616
}
1717
}
1818

src/ResponsibilityChain.Tests/ReturnCompletedTaskHandlerTest.cs

+15-15
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,11 @@ public class ReturnCompletedTaskHandlerTest
88
{
99
private class CompositeFooAsyncHandler : Handler<int, Task>
1010
{
11-
public CompositeFooAsyncHandler()
11+
public CompositeFooAsyncHandler(BarHandler barHandler)
1212
{
13-
AddHandler(new BarHandler());
14-
AddHandler(ReturnCompletedTaskHandler<int>.Instance);
15-
AddHandler(ThrowNotSupportedHandler<int, Task>.Instance);
16-
}
17-
18-
private class BarHandler : IHandler<int, Task>
19-
{
20-
public async Task Handle(int input, Func<int, Task> next)
21-
{
22-
await Task.Delay(100);
23-
24-
await next(input);
25-
}
13+
AddHandler(barHandler);
14+
AddHandler(new ReturnCompletedTaskHandler<int>());
15+
AddHandler(new ThrowNotSupportedHandler<int, Task>());
2616
}
2717
}
2818

@@ -31,12 +21,22 @@ public async Task
3121
GivenThisShortCircuitHandlerPlacedInTheMiddleOfTheChain_ReturnsCompletedTaskWithoutThrowingException()
3222
{
3323
// arrange
34-
var handler = new CompositeFooAsyncHandler();
24+
var handler = new CompositeFooAsyncHandler(new BarHandler());
3525

3626
// act
3727
await handler.Handle(111, null);
3828

3929
// assert
4030
}
31+
32+
private class BarHandler : IHandler<int, Task>
33+
{
34+
public async Task Handle(int input, Func<int, Task> next)
35+
{
36+
await Task.Delay(100);
37+
38+
await next(input);
39+
}
40+
}
4141
}
4242
}

src/ResponsibilityChain.Tests/ReturnDefaultValueHandlerTest.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class ReturnDefaultValueHandlerTest
1010
public void IntegerOutputExpected_ReturnsZero()
1111
{
1212
// arrange
13-
var handler = ReturnDefaultValueHandler<string, int>.Instance;
13+
var handler = new ReturnDefaultValueHandler<string, int>();
1414

1515
// act
1616
var output = handler.Handle("some input", next: null);
@@ -23,7 +23,7 @@ public void IntegerOutputExpected_ReturnsZero()
2323
public void StringOutputExpected_ReturnsNull()
2424
{
2525
// arrange
26-
var handler = ReturnDefaultValueHandler<string, string>.Instance;
26+
var handler = new ReturnDefaultValueHandler<string, string>();
2727

2828
// act
2929
var output = handler.Handle("some input", next: null);
@@ -36,7 +36,7 @@ public void StringOutputExpected_ReturnsNull()
3636
public void DateTimeOutputExpected_ReturnsDateTimeMin()
3737
{
3838
// arrange
39-
var handler = ReturnDefaultValueHandler<string, DateTime>.Instance;
39+
var handler = new ReturnDefaultValueHandler<string, DateTime>();
4040

4141
// act
4242
var output = handler.Handle("some input", next: null);
@@ -49,7 +49,7 @@ public void DateTimeOutputExpected_ReturnsDateTimeMin()
4949
public void ReferenceTypeObjectOutputExpected_ReturnsNull()
5050
{
5151
// arrange
52-
var handler = ReturnDefaultValueHandler<string, StringBuilder>.Instance;
52+
var handler = new ReturnDefaultValueHandler<string, StringBuilder>();
5353

5454
// act
5555
var output = handler.Handle("some input", next: null);

0 commit comments

Comments
 (0)