Skip to content

Commit

Permalink
timediatr initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
astorDev committed Nov 11, 2024
1 parent 0a473b9 commit ff87645
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 0 deletions.
21 changes: 21 additions & 0 deletions Backi.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{7F2FF8
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Mediatr", "mediator\dotnet\lib\Backi.Mediatr.csproj", "{1AD779EA-2592-48D0-95BB-6E68FDB5276F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Timediatr.Tests", "mediator\dotnet\Backi.Timediatr.Tests\Backi.Timediatr.Tests.csproj", "{C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Mediatr.Tests", "mediator\dotnet\Backi.Mediatr.Tests\Backi.Mediatr.Tests.csproj", "{2243861D-99F1-427E-80A6-DDED312872D7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Timediatr", "mediator\dotnet\Backi.Timediatr\Backi.Timediatr.csproj", "{28FE8A5E-60D5-43A9-8550-AF7A077B1B35}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -46,6 +52,18 @@ Global
{1AD779EA-2592-48D0-95BB-6E68FDB5276F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AD779EA-2592-48D0-95BB-6E68FDB5276F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AD779EA-2592-48D0-95BB-6E68FDB5276F}.Release|Any CPU.Build.0 = Release|Any CPU
{C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Release|Any CPU.Build.0 = Release|Any CPU
{2243861D-99F1-427E-80A6-DDED312872D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2243861D-99F1-427E-80A6-DDED312872D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2243861D-99F1-427E-80A6-DDED312872D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2243861D-99F1-427E-80A6-DDED312872D7}.Release|Any CPU.Build.0 = Release|Any CPU
{28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{ADCEE902-52AF-4E52-9CEA-77450049AA7C} = {BBB56B6A-667F-43C0-8AFE-79530301D381}
Expand All @@ -55,5 +73,8 @@ Global
{F9FA4AC6-6E34-4D32-8C9D-573B2321BDE7} = {ADCEE902-52AF-4E52-9CEA-77450049AA7C}
{7F2FF828-62E7-497D-8D5C-F0F4A1540AD1} = {27BF9028-A9AC-4B9E-9A63-5260B70734D0}
{1AD779EA-2592-48D0-95BB-6E68FDB5276F} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
{C47EDCC3-FF07-4B1E-BDC4-06544C6264F5} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
{2243861D-99F1-427E-80A6-DDED312872D7} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
{28FE8A5E-60D5-43A9-8550-AF7A077B1B35} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
EndGlobalSection
EndGlobal
3 changes: 3 additions & 0 deletions Backi.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=backi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=timediatr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
7 changes: 7 additions & 0 deletions Backi.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=0c682e2c_002D1af8_002D41e4_002D9940_002Da3dcb1d4afe0/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="CreateHandlerEachTime" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;MSTest::2243861D-99F1-427E-80A6-DDED312872D7::net8.0::Backi.Tests.SendMediatrRequestShould.CreateHandlerEachTime&lt;/TestId&gt;
&lt;TestId&gt;MSTest::C47EDCC3-FF07-4B1E-BDC4-06544C6264F5::net8.0::Backi.Tests.TimediatrShould.SendCommandOneFewTimes&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
25 changes: 25 additions & 0 deletions mediator/dotnet/Backi.Mediatr.Tests/Backi.Mediatr.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4"/>
<PackageReference Include="MSTest.TestFramework" Version="3.0.4"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\lib\Backi.Mediatr.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions mediator/dotnet/Backi.Mediatr.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Microsoft.VisualStudio.TestTools.UnitTesting;
59 changes: 59 additions & 0 deletions mediator/dotnet/Backi.Mediatr.Tests/Infrastructure.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Backi.Tests;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMediatrTestingInfrastructure(this IServiceCollection services)
{
services.AddSingleton<CounterCollection>();
services.AddMediatR(m => m.RegisterServicesFromAssembly(typeof(CounterCollection).Assembly));
services.AddLogging(l =>
{
l.AddSimpleConsole(c => c.SingleLine = true);
l.SetMinimumLevel(LogLevel.Debug);
});

return services;
}
}

public class CounterCollection
{
public readonly Dictionary<string, int> items = new ();

public void Increment(string key)
{
if (!items.TryAdd(key, 1))
{
items[key] += 1;
}
}

public int Get(string key) => items.GetValueOrDefault(key);
}

public class CommandOne : IRequest
{
public class Handler : IRequestHandler<CommandOne>
{
readonly CounterCollection counters;
public const string ConstructorCounterKey = "CommandA.Handler.Constructor";
public const string HandleCounterKey = "CommandA.Handler.Handle";

public Handler(CounterCollection counters)
{
this.counters = counters;

counters.Increment(ConstructorCounterKey);
}

public Task Handle(CommandOne request, CancellationToken cancellationToken)
{
counters.Increment(HandleCounterKey);
return Task.CompletedTask;
}
}
}
32 changes: 32 additions & 0 deletions mediator/dotnet/Backi.Mediatr.Tests/SendMediatrRequestShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Backi.Tests;

[TestClass]
public class SendMediatrRequestShould
{
[TestMethod]
public async Task CreateHandlerEachTime()
{
var services = new ServiceCollection();

services.AddMediatrTestingInfrastructure();

var provider = services.BuildServiceProvider();

var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
var logger = provider.GetRequiredService<ILogger<SendMediatrRequestShould>>();

var commandInstance = new CommandOne();

await scopeFactory.SendMediatorRequest(commandInstance, logger);
await scopeFactory.SendMediatorRequest(commandInstance, logger);

var counters = provider.GetRequiredService<CounterCollection>();

counters.Get(CommandOne.Handler.ConstructorCounterKey).Should().Be(2);
counters.Get(CommandOne.Handler.HandleCounterKey).Should().Be(2);
}
}
26 changes: 26 additions & 0 deletions mediator/dotnet/Backi.Timediatr.Tests/Backi.Timediatr.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4"/>
<PackageReference Include="MSTest.TestFramework" Version="3.0.4"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Backi.Mediatr.Tests\Backi.Mediatr.Tests.csproj" />
<ProjectReference Include="..\Backi.Timediatr\Backi.Timediatr.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions mediator/dotnet/Backi.Timediatr.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Microsoft.VisualStudio.TestTools.UnitTesting;
34 changes: 34 additions & 0 deletions mediator/dotnet/Backi.Timediatr.Tests/TimediatrShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Backi.Tests;

[TestClass]
public class TimediatrShould
{
[TestMethod]
public async Task SendCommandOneFewTimes()
{
var services = new ServiceCollection();

services.AddMediatrTestingInfrastructure();
services.AddTimediatr(timediatr => timediatr.Configure(o =>
{
o.Schedule.Add(new CommandOne(), TimeSpan.FromSeconds(3));
}));

var provider = services.BuildServiceProvider();

var backgroundService = (TimediatrBackgroundService)provider.GetRequiredService<IHostedService>();

await backgroundService.StartAsync(CancellationToken.None);
await Task.Delay(TimeSpan.FromSeconds(10));
await backgroundService.StopAsync(CancellationToken.None);

var counter = provider.GetRequiredService<CounterCollection>();

counter.Get(CommandOne.Handler.HandleCounterKey).Should().Be(4);
counter.Get(CommandOne.Handler.ConstructorCounterKey).Should().Be(4);
}
}
18 changes: 18 additions & 0 deletions mediator/dotnet/Backi.Timediatr/Backi.Timediatr.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Backi.Timers" Version="2024.110.129.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\lib\Backi.Mediatr.csproj" />
</ItemGroup>

</Project>
65 changes: 65 additions & 0 deletions mediator/dotnet/Backi.Timediatr/Timediatr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Backi.Timers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Backi;

public class TimediatrBackgroundService(
IOptions<TimediatrConfiguration> options,
IServiceScopeFactory serviceScopeFactory,
ILogger<TimediatrBackgroundService> logger) : IHostedService
{
readonly List<SafeTimer> timers = [];

public Task StartAsync(CancellationToken cancellationToken)
{
var schedule = options.Value.Schedule;

foreach (var timer in schedule)
{
logger.LogDebug("Setting {requestType} to run with interval {interval}", timer.Key.GetType(), timer.Value);

timers.Add(SafeTimer.RunNowAndPeriodically(
timer.Value,
() => serviceScopeFactory.SendMediatorRequest(timer.Key, logger, cancellationToken),
ex => logger.LogError(ex, "Error while processing {requestType}", timer.Key.GetType())
));

logger.LogInformation("Set {requestType} to run with interval {interval}", timer.Key.GetType(), timer.Value);
}

return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogDebug("Stopping all timers");

foreach (var timer in timers)
{
timer.Stop();
}

logger.LogInformation("Stopped all timers");
return Task.CompletedTask;
}
}

public class TimediatrConfiguration
{
public Dictionary<object, TimeSpan> Schedule { get; set; } = new();
}

public static class TimediatrServiceCollectionExtensions
{
public static IServiceCollection AddTimediatr(this IServiceCollection services, Action<OptionsBuilder<TimediatrConfiguration>> configuration)
{
services.AddHostedService<TimediatrBackgroundService>();
var optionsBuilder = services.AddOptions<TimediatrConfiguration>();
configuration(optionsBuilder);

return services;
}
}
9 changes: 9 additions & 0 deletions mediator/dotnet/lib/ServiceScopeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@ public static async Task SendMediatorRequest<TRequest>(this IServiceScopeFactory
await mediator.Send(request, cancellationToken ?? CancellationToken.None);
logger?.LogInformation("{requestType} processed by mediator", typeof(TRequest));
}

public static async Task SendMediatorRequest(this IServiceScopeFactory serviceScopeFactory, object request, ILogger? logger = null, CancellationToken? cancellationToken = null)
{
logger?.LogDebug("Sending {requestType} to mediator in a new scope", request.GetType());
using var scope = serviceScopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Send(request, cancellationToken ?? CancellationToken.None);
logger?.LogInformation("{requestType} processed by mediator", request.GetType());
}
}

0 comments on commit ff87645

Please sign in to comment.