Skip to content

Commit bd4fd4b

Browse files
authored
Merge pull request #20 from GarageGroup/feature/improve-console
Use HandlerConsoleRunner
2 parents e900f99 + 9ecaf58 commit bd4fd4b

5 files changed

Lines changed: 196 additions & 110 deletions

File tree

Lines changed: 22 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,45 @@
11
using System;
22
using System.Diagnostics.CodeAnalysis;
3-
using System.Threading;
4-
using System.Threading.Tasks;
53
using Microsoft.Extensions.Configuration;
64
using Microsoft.Extensions.DependencyInjection;
7-
using Microsoft.Extensions.Logging;
85
using PrimeFuncPack;
96

107
namespace GarageGroup.Infra;
118

129
public static class ConsoleDependencyExtensions
1310
{
14-
public static Task<Unit> RunConsoleAsync<THandler, TIn>(
15-
this Dependency<THandler> dependency,
16-
Action<ILoggingBuilder>? configureLogger = null,
17-
Action<IServiceCollection>? configureServices = null,
18-
[AllowNull] string inputSection = null,
19-
[AllowNull] string[] args = null)
20-
where THandler : IHandler<TIn, Unit>
11+
public static HandlerConsoleRunner UseConsoleRunner<THandler>(
12+
this Dependency<IHandler<Unit, Unit>> dependency, [AllowNull] string[] args = null)
2113
{
2214
ArgumentNullException.ThrowIfNull(dependency);
23-
ArgumentNullException.ThrowIfNull(configureServices);
24-
25-
return dependency.InnerRunConsoleAsync<THandler, TIn>(configureLogger, configureServices, inputSection, args);
15+
return new(dependency.Resolve, args);
2616
}
2717

28-
public static Task<Unit> RunConsoleAsync<TIn>(
29-
this Dependency<IHandler<TIn, Unit>> dependency,
30-
Action<ILoggingBuilder>? configureLogger = null,
31-
Action<IServiceCollection>? configureServices = null,
32-
[AllowNull] string inputSection = null,
33-
[AllowNull] string[] args = null)
18+
public static HandlerConsoleRunner UseConsoleRunner<THandler>(
19+
this Dependency<THandler> dependency, [AllowNull] string[] args = null)
20+
where THandler : IHandler<Unit, Unit>
3421
{
3522
ArgumentNullException.ThrowIfNull(dependency);
36-
return dependency.InnerRunConsoleAsync<IHandler<TIn, Unit>, TIn>(configureLogger, configureServices, inputSection, args);
37-
}
38-
39-
private static async Task<Unit> InnerRunConsoleAsync<THandler, TIn>(
40-
this Dependency<THandler> dependency,
41-
Action<ILoggingBuilder>? configureLogger,
42-
Action<IServiceCollection>? configureServices,
43-
[AllowNull] string inputSection,
44-
[AllowNull] string[] args)
45-
where THandler : IHandler<TIn, Unit>
46-
{
47-
var configuration = BuildConfiguration(args ?? []);
48-
49-
using var serviceProvider = configuration.CreateServiceProvider(configureLogger, configureServices);
50-
using var cancellationTokenSource = configuration.GetCancellationTokenSource();
51-
52-
var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("HandlerConsoleRunner");
53-
54-
var input = configuration.ReadInput<TIn>(inputSection ?? string.Empty);
55-
var result = await dependency.Resolve(serviceProvider).InnerInvokeAsync(input, cancellationTokenSource.Token);
56-
57-
return result.Fold(Unit.From, logger.LogFailure);
58-
}
59-
60-
private static async Task<Result<Unit, Failure<HandlerFailureCode>>> InnerInvokeAsync<THandler, TIn>(
61-
this THandler handler, TIn? input, CancellationToken cancellationToken)
62-
where THandler : IHandler<TIn, Unit>
63-
{
64-
try
65-
{
66-
return await handler.HandleAsync(input, cancellationToken);
67-
}
68-
finally
69-
{
70-
if (handler is IDisposable disposable)
71-
{
72-
disposable.Dispose();
73-
}
74-
}
75-
}
23+
return new(InnerResolve, args);
7624

77-
private static TIn? ReadInput<TIn>(this IConfiguration configuration, string sectionName)
78-
{
79-
if (typeof(TIn) == typeof(Unit))
80-
{
81-
return default;
82-
}
83-
84-
return configuration.GetRequiredSection(sectionName).Get<TIn>();
85-
}
86-
87-
private static Unit LogFailure(this ILogger logger, Failure<HandlerFailureCode> failure)
88-
{
89-
if (failure.FailureCode is not HandlerFailureCode.Persistent)
90-
{
91-
throw new InvalidOperationException($"An unexpected error has occured: {failure.FailureMessage}", failure.SourceException);
92-
}
93-
94-
logger.LogError(failure.SourceException, "An unexpected failure has occured: {failureMessage}", failure.FailureMessage);
95-
return default;
25+
IHandler<Unit, Unit> InnerResolve(IServiceProvider serviceProvider)
26+
=>
27+
dependency.Resolve(serviceProvider);
9628
}
9729

98-
private static CancellationTokenSource GetCancellationTokenSource(this IConfiguration configuration)
99-
{
100-
var timeout = configuration.GetValue<TimeSpan?>("MaxTimeout");
101-
return timeout is null ? new() : new(timeout.Value);
102-
}
103-
104-
private static ServiceProvider CreateServiceProvider(
105-
this IConfiguration configuration, Action<ILoggingBuilder>? configureLogger, Action<IServiceCollection>? configureServices)
30+
public static HandlerConsoleRunner UseConsoleRunner<TIn>(
31+
this Dependency<IHandler<TIn, Unit>> dependency,
32+
string inputSection,
33+
[AllowNull] string[] args = null)
10634
{
107-
var services = new ServiceCollection()
108-
.AddLogging(InnerConfigureLogger)
109-
.AddSingleton(configuration)
110-
.AddSocketsHttpHandlerProviderAsSingleton()
111-
.AddTokenCredentialStandardAsSingleton();
112-
113-
configureServices?.Invoke(services);
114-
115-
return services.BuildServiceProvider();
35+
ArgumentNullException.ThrowIfNull(dependency);
36+
return new(InnerResolve, args);
11637

117-
void InnerConfigureLogger(ILoggingBuilder builder)
118-
{
119-
builder = builder.AddConsole();
120-
configureLogger?.Invoke(builder);
121-
}
38+
IHandler<Unit, Unit> InnerResolve(IServiceProvider serviceProvider)
39+
=>
40+
new AdapterHandler<TIn>(
41+
innerHandler: dependency.Resolve(serviceProvider),
42+
configuration: serviceProvider.GetRequiredService<IConfiguration>(),
43+
sectionName: inputSection);
12244
}
123-
124-
private static IConfiguration BuildConfiguration(string[] args)
125-
=>
126-
new ConfigurationBuilder()
127-
.AddJsonFile("appsettings.json", true, true)
128-
.AddEnvironmentVariables()
129-
.AddCommandLine(args)
130-
.Build();
13145
}

src/Handler.Console/Handler.Console.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
<NoWarn>$(NoWarn);IDE0130;CA1859</NoWarn>
1010
<RootNamespace>GarageGroup.Infra</RootNamespace>
1111
<AssemblyName>GarageGroup.Infra.Handler.Console</AssemblyName>
12-
<Version>0.12.0</Version>
12+
<Version>0.13.0</Version>
1313
</PropertyGroup>
1414

1515
<ItemGroup>
1616
<PackageReference Include="GarageGroup.Infra.Azure.TokenCredential" Version="0.3.0" />
1717
<PackageReference Include="GarageGroup.Infra.Handler.Core" Version="0.6.1" />
18-
<PackageReference Include="GarageGroup.Infra.Http.SocketsHandlerProvider" Version="3.0.0" />
18+
<PackageReference Include="GarageGroup.Infra.Http.SocketsHandlerProvider" Version="3.1.0" />
1919
</ItemGroup>
2020

2121
</Project>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Configuration;
6+
7+
namespace GarageGroup.Infra;
8+
9+
internal sealed class AdapterHandler<TIn> : IHandler<Unit, Unit>
10+
{
11+
private readonly IHandler<TIn, Unit> innerHandler;
12+
13+
private readonly IConfiguration configuration;
14+
15+
private readonly string sectionName;
16+
17+
internal AdapterHandler(IHandler<TIn, Unit> innerHandler, IConfiguration configuration, [AllowNull] string sectionName)
18+
{
19+
this.innerHandler = innerHandler;
20+
this.configuration = configuration;
21+
this.sectionName = sectionName ?? string.Empty;
22+
}
23+
24+
public ValueTask<Result<Unit, Failure<HandlerFailureCode>>> HandleAsync(
25+
Unit _, CancellationToken cancellationToken)
26+
{
27+
return ReadInput().ForwardValueAsync(InnerHandleAsync);
28+
29+
ValueTask<Result<Unit, Failure<HandlerFailureCode>>> InnerHandleAsync(TIn? @in)
30+
=>
31+
innerHandler.HandleAsync(@in, cancellationToken);
32+
}
33+
34+
private Result<TIn?, Failure<HandlerFailureCode>> ReadInput()
35+
{
36+
try
37+
{
38+
return configuration.GetRequiredSection(sectionName).Get<TIn>();
39+
}
40+
catch (Exception ex)
41+
{
42+
return ex.ToFailure(
43+
HandlerFailureCode.Persistent,
44+
$"Input of type '{typeof(TIn).FullName}' can't be readed from section '{sectionName}'");
45+
}
46+
}
47+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace GarageGroup.Infra;
10+
11+
public sealed class HandlerConsoleRunner : IHandlerConsoleRunner
12+
{
13+
private readonly Func<IServiceProvider, IHandler<Unit, Unit>> handlerResolver;
14+
15+
private readonly IConfiguration configuration;
16+
17+
private readonly Action<IServiceCollection>? configureServices;
18+
19+
private readonly Action<ILoggingBuilder>? configureLogger;
20+
21+
internal HandlerConsoleRunner(Func<IServiceProvider, IHandler<Unit, Unit>> handlerResolver, [AllowNull] string[] args)
22+
{
23+
this.handlerResolver = handlerResolver;
24+
configuration = BuildConfiguration(args ?? []);
25+
}
26+
27+
private HandlerConsoleRunner(
28+
Func<IServiceProvider, IHandler<Unit, Unit>> handlerResolver,
29+
IConfiguration configuration,
30+
Action<IServiceCollection>? configureServices,
31+
Action<ILoggingBuilder>? configureLogger)
32+
{
33+
this.handlerResolver = handlerResolver;
34+
this.configuration = configuration;
35+
this.configureServices = configureServices;
36+
this.configureLogger = configureLogger;
37+
}
38+
39+
public IHandlerConsoleRunner Configure(
40+
Action<IServiceCollection> configureServices,
41+
Action<ILoggingBuilder>? configureLogger = null)
42+
=>
43+
new HandlerConsoleRunner(handlerResolver, configuration, configureServices, configureLogger);
44+
45+
public async Task RunAsync()
46+
{
47+
using var serviceProvider = CreateServiceProvider();
48+
using var cancellationTokenSource = GetCancellationTokenSource();
49+
50+
var handler = handlerResolver.Invoke(serviceProvider);
51+
var result = await InnerInvokeAsync(handler, cancellationTokenSource.Token);
52+
53+
_ = result.Fold(Unit.From, InnerLogFailre);
54+
55+
Unit InnerLogFailre(Failure<HandlerFailureCode> failure)
56+
{
57+
if (failure.FailureCode is not HandlerFailureCode.Persistent)
58+
{
59+
throw new InvalidOperationException($"An unexpected persistent error occured: {failure.FailureMessage}", failure.SourceException);
60+
}
61+
62+
var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<HandlerConsoleRunner>();
63+
logger.LogError(failure.SourceException, "An unexpected transient failure occured: {failureMessage}", failure.FailureMessage);
64+
65+
return default;
66+
}
67+
}
68+
69+
private static async Task<Result<Unit, Failure<HandlerFailureCode>>> InnerInvokeAsync(
70+
IHandler<Unit, Unit> handler, CancellationToken cancellationToken)
71+
{
72+
try
73+
{
74+
return await handler.HandleAsync(default, cancellationToken);
75+
}
76+
finally
77+
{
78+
if (handler is IDisposable disposable)
79+
{
80+
disposable.Dispose();
81+
}
82+
}
83+
}
84+
85+
private ServiceProvider CreateServiceProvider()
86+
{
87+
var services = new ServiceCollection()
88+
.AddLogging(InnerConfigureLogger)
89+
.AddSingleton(configuration)
90+
.AddSocketsHttpHandlerProviderAsSingleton()
91+
.AddTokenCredentialStandardAsSingleton();
92+
93+
configureServices?.Invoke(services);
94+
95+
return services.BuildServiceProvider();
96+
97+
void InnerConfigureLogger(ILoggingBuilder builder)
98+
{
99+
builder = builder.AddConsole();
100+
configureLogger?.Invoke(builder);
101+
}
102+
}
103+
104+
private CancellationTokenSource GetCancellationTokenSource()
105+
{
106+
var timeout = configuration.GetValue<TimeSpan?>("MaxTimeout");
107+
return timeout is null ? new() : new(timeout.Value);
108+
}
109+
110+
private static IConfiguration BuildConfiguration(string[] args)
111+
=>
112+
new ConfigurationBuilder()
113+
.AddJsonFile("appsettings.json", true, true)
114+
.AddEnvironmentVariables()
115+
.AddCommandLine(args)
116+
.Build();
117+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Threading.Tasks;
2+
3+
namespace GarageGroup.Infra;
4+
5+
public interface IHandlerConsoleRunner
6+
{
7+
Task RunAsync();
8+
}

0 commit comments

Comments
 (0)