BREAKING: Use Scoped services for the IWorkflow in DI.

This commit is contained in:
Shkar T. Noori 2024-08-14 12:56:39 +03:00
parent f6f93d4744
commit 6de3d47516
No known key found for this signature in database
GPG Key ID: C5E1A00F3BB78732
9 changed files with 71 additions and 36 deletions

View File

@ -17,7 +17,7 @@ public interface IWorkflow<TState, TCommand, TContext>
/// <typeparam name="TCommand">The type of the workflow command.</typeparam> /// <typeparam name="TCommand">The type of the workflow command.</typeparam>
/// <typeparam name="TContext">The type of the workflow context.</typeparam> /// <typeparam name="TContext">The type of the workflow context.</typeparam>
/// <returns>A list of transition definitions.</returns> /// <returns>A list of transition definitions.</returns>
public List<TransitionDefinition<TState, TCommand, TContext>> GetAllTransitionDefinitions(); public List<TransitionDefinition<TState, TCommand, ContextWrapper<TContext>>> GetAllTransitionDefinitions();
/// <summary> /// <summary>
/// Gets a list of allowed transitions without any condition checks. /// Gets a list of allowed transitions without any condition checks.

View File

@ -1,3 +1,3 @@
namespace DIT.Workflower.DependencyInjection; namespace DIT.Workflower.DependencyInjection;
public record DIContextWrapper<TContext>(TContext Context, IServiceProvider ServiceProvider); public record ContextWrapper<TContext>(TContext Context, IServiceProvider ServiceProvider);

View File

@ -16,14 +16,14 @@
<!-- Deterministic Builds --> <!-- Deterministic Builds -->
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup > <ItemGroup >
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0" Condition="'$(TargetFramework)' == 'net6.0'" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" Condition="'$(TargetFramework)' == 'net6.0'" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0" Condition="'$(TargetFramework)' == 'net7.0'" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" Condition="'$(TargetFramework)' == 'net7.0'" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0" Condition="'$(TargetFramework)' == 'net8.0'" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" Condition="'$(TargetFramework)' == 'net8.0'" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,10 +2,10 @@
namespace DIT.Workflower.DependencyInjection.Extensions; namespace DIT.Workflower.DependencyInjection.Extensions;
public static class IServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static ITransitionStart<TState, TCommand, ContextWrapper<TContext>> AddWorkflowDefinition<TState, TCommand, TContext>(
public static ITransitionStart<TState, TCommand, DIContextWrapper<TContext>> AddWorkflowDefinition<TState, TCommand, TContext>(this IServiceCollection services, in int version = 1) this IServiceCollection services, in int version = 1)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
@ -13,25 +13,27 @@ public static class IServiceCollectionExtensions
return AddWorkflowDefinition<TState, TCommand, TContext>(services, id, version); return AddWorkflowDefinition<TState, TCommand, TContext>(services, id, version);
} }
public static ITransitionStart<TState, TCommand, DIContextWrapper<TContext>> AddWorkflowDefinition<TState, TCommand, TContext>(this IServiceCollection services, in string id) public static ITransitionStart<TState, TCommand, ContextWrapper<TContext>> AddWorkflowDefinition<TState, TCommand, TContext>(
this IServiceCollection services, in string id)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
return AddWorkflowDefinition<TState, TCommand, TContext>(services, id, version: 1); return AddWorkflowDefinition<TState, TCommand, TContext>(services, id, version: 1);
} }
public static ITransitionStart<TState, TCommand, DIContextWrapper<TContext>> AddWorkflowDefinition<TState, TCommand, TContext>(this IServiceCollection services, string id, int version) public static ITransitionStart<TState, TCommand, ContextWrapper<TContext>> AddWorkflowDefinition<TState, TCommand, TContext>(
this IServiceCollection services, string id, int version)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
var builder = WorkflowDefinitionBuilder<TState, TCommand, DIContextWrapper<TContext>>.Create(); var builder = WorkflowDefinitionBuilder<TState, TCommand, ContextWrapper<TContext>>.Create();
var definition = (WorkflowDefinitionBuilder<TState, TCommand, ContextWrapper<TContext>>)builder;
services.TryAddSingleton<IWorkflowFactory<TState, TCommand, DIContextWrapper<TContext>>, DefaultWorkflowFactory<TState, TCommand, DIContextWrapper<TContext>>>(); services.TryAddScoped<IWorkflowFactory<TState, TCommand, TContext>, DefaultWorkflowFactory<TState, TCommand, TContext>>();
services.AddSingleton<IWorkflow<TState, TCommand, DIContextWrapper<TContext>>, WorkflowDefinitionWrapper<TState, TCommand, DIContextWrapper<TContext>>>(sp => services.AddScoped<IWorkflow<TState, TCommand, TContext>, WorkflowDefinitionWrapper<TState, TCommand, TContext>>(sp =>
{ {
var definition = (WorkflowDefinitionBuilder<TState, TCommand, DIContextWrapper<TContext>>)builder; var wrapper = new WorkflowDefinitionWrapper<TState, TCommand, TContext>(definition, sp, id, version);
var wrapper = new WorkflowDefinitionWrapper<TState, TCommand, DIContextWrapper<TContext>>(definition, id, version);
return wrapper; return wrapper;
}); });

View File

@ -1,22 +1,22 @@
namespace DIT.Workflower.DependencyInjection.Extensions; namespace DIT.Workflower.DependencyInjection.Extensions;
public static class IServiceProviderExtensions public static class ServiceProviderExtensions
{ {
public static IWorkflowFactory<TState, TCommand, DIContextWrapper<TContext>> GetRequiredWorkflowFactory<TState, TCommand, TContext>(this IServiceProvider sp) public static IWorkflowFactory<TState, TCommand, TContext> GetRequiredWorkflowFactory<TState, TCommand, TContext>(this IServiceProvider sp)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
return sp.GetRequiredService<IWorkflowFactory<TState, TCommand, DIContextWrapper<TContext>>>(); return sp.GetRequiredService<IWorkflowFactory<TState, TCommand, TContext>>();
} }
public static IWorkflowFactory<TState, TCommand, DIContextWrapper<TContext>>? GetWorkflowFactory<TState, TCommand, TContext>(this IServiceProvider sp) public static IWorkflowFactory<TState, TCommand, TContext>? GetWorkflowFactory<TState, TCommand, TContext>(this IServiceProvider sp)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
return sp.GetService<IWorkflowFactory<TState, TCommand, DIContextWrapper<TContext>>>(); return sp.GetService<IWorkflowFactory<TState, TCommand, TContext>>();
} }
public static IWorkflow<TState, TCommand, DIContextWrapper<TContext>> CreateWorkflow<TState, TCommand, TContext>(this IServiceProvider sp, int version = 1) public static IWorkflow<TState, TCommand, TContext> CreateWorkflow<TState, TCommand, TContext>(this IServiceProvider sp, int version = 1)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
@ -24,7 +24,7 @@ public static class IServiceProviderExtensions
return factory.CreateWorkflow(version); return factory.CreateWorkflow(version);
} }
public static IWorkflow<TState, TCommand, DIContextWrapper<TContext>> CreateWorkflow<TState, TCommand, TContext>(this IServiceProvider sp, string id, int version = 1) public static IWorkflow<TState, TCommand, TContext> CreateWorkflow<TState, TCommand, TContext>(this IServiceProvider sp, string id, int version = 1)
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {

View File

@ -1,21 +1,56 @@
namespace DIT.Workflower.DependencyInjection; namespace DIT.Workflower.DependencyInjection;
public record WorkflowDefinitionWrapper<TState, TCommand, TContext> : WorkflowDefinition<TState, TCommand, TContext>, IWorkflow<TState, TCommand, TContext> public sealed class WorkflowDefinitionWrapper<TState, TCommand, TContext> : IWorkflow<TState, TCommand, TContext>
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
private readonly IServiceProvider _serviceProvider;
private readonly WorkflowDefinition<TState, TCommand, ContextWrapper<TContext>> _definition;
public string Id { get; } public string Id { get; }
public int Version { get; } public int Version { get; }
public List<TransitionDefinition<TState, TCommand, ContextWrapper<TContext>>> GetAllTransitionDefinitions()
public WorkflowDefinitionWrapper(WorkflowDefinitionBuilder<TState, TCommand, TContext> builder, string id, int version)
: base(builder.Transitions)
{ {
return _definition.GetAllTransitionDefinitions();
}
public List<Transition<TState, TCommand>> GetAllowedTransitions(TState from)
{
return _definition.GetAllowedTransitions(from);
}
public List<Transition<TState, TCommand>> GetAllowedTransitions(TContext context, TState from)
{
return _definition.GetAllowedTransitions(GetContextWrapper(context), from);
}
public IEnumerable<Transition<TState, TCommand>> GetAllowedTransitions(ListTransitionsRequest<TState, TCommand, TContext> request)
{
var wrappedRequest = new ListTransitionsRequest<TState, TCommand, ContextWrapper<TContext>>
{
From = request.From,
To = request.To,
Command = request.Command,
Context = request.Context is null ? null : GetContextWrapper(request.Context)
};
return _definition.GetAllowedTransitions(wrappedRequest);
}
public WorkflowDefinitionWrapper(WorkflowDefinitionBuilder<TState, TCommand, ContextWrapper<TContext>> builder, IServiceProvider serviceProvider, string id, int version)
{
_definition = builder.Build();
_serviceProvider = serviceProvider;
Id = id; Id = id;
Version = version; Version = version;
} }
private ContextWrapper<TContext> GetContextWrapper(in TContext context)
=> new(context, _serviceProvider);
public static string GetDefaultId() public static string GetDefaultId()
{ {
var ctxType = typeof(TContext); var ctxType = typeof(TContext);

View File

@ -17,7 +17,7 @@
<!-- Deterministic Builds --> <!-- Deterministic Builds -->
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup> </PropertyGroup>

View File

@ -1,6 +1,6 @@
namespace DIT.Workflower; namespace DIT.Workflower;
public record WorkflowDefinition<TState, TCommand, TContext> public class WorkflowDefinition<TState, TCommand, TContext>
where TState : struct where TState : struct
where TCommand : struct where TCommand : struct
{ {
@ -41,24 +41,24 @@ public record WorkflowDefinition<TState, TCommand, TContext>
{ {
var query = _transitions.AsQueryable(); var query = _transitions.AsQueryable();
if (request.From is TState from) if (request.From is { } from)
{ {
query = query.Where(doc => doc.From.Equals(from)); query = query.Where(doc => doc.From.Equals(from));
} }
if (request.To is TState to) if (request.To is { } to)
{ {
query = query.Where(doc => doc.To.Equals(to)); query = query.Where(doc => doc.To.Equals(to));
} }
if (request.Command is TCommand command) if (request.Command is { } command)
{ {
query = query.Where(doc => doc.Command.Equals(command)); query = query.Where(doc => doc.Command.Equals(command));
} }
if (request.Context is not null) if (request.Context is not null)
{ {
query = query.Where(doc => doc.Conditions == null || !doc.Conditions.Any(cond => !cond(request.Context))); query = query.Where(doc => doc.Conditions == null || doc.Conditions.All(cond => cond(request.Context)));
} }
return query.Select(x => x.ToTransition()); return query.Select(x => x.ToTransition());

View File

@ -1,5 +1,4 @@
using DIT.Workflower.DependencyInjection.Abstractions; using DIT.Workflower.DependencyInjection.Extensions;
using DIT.Workflower.DependencyInjection.Extensions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace DIT.Workflower.Tests.DependencyInjection; namespace DIT.Workflower.Tests.DependencyInjection;
@ -49,7 +48,6 @@ public class DependencyInjectionTests
Assert.NotNull(v1); Assert.NotNull(v1);
Assert.NotNull(v2); Assert.NotNull(v2);
Assert.Single(v1!.GetAllowedTransitions(PhoneState.Idle)); Assert.Single(v1!.GetAllowedTransitions(PhoneState.Idle));
Assert.Single(v2!.GetAllowedTransitions(PhoneState.Idle)); Assert.Single(v2!.GetAllowedTransitions(PhoneState.Idle));