Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ioc part 1 #24

Merged
merged 5 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# MicroWorkflow .net
# Micro Workflow .net
<!--start-->
[![Stats](https://img.shields.io/badge/Code_lines-1,7_K-ff69b4.svg)]()
[![Stats](https://img.shields.io/badge/Test_lines-1,2_K-69ffb4.svg)]()
Expand Down
6 changes: 6 additions & 0 deletions src/Demos/ConsoleDemo/MicroWorkflow.ConsoleDemo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Autofac" Version="8.0.0" />
</ItemGroup>

<ItemGroup>

<ProjectReference Include="..\..\Product\MicroWorkflow.Ioc.Autofac\MicroWorkflow.Ioc.Autofac.csproj" />

<ProjectReference Include="..\..\Product\MicroWorkflow\MicroWorkflow.csproj" />
</ItemGroup>
Expand Down
51 changes: 21 additions & 30 deletions src/Demos/ConsoleDemo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using MicroWorkflow;
using Autofac;
using MicroWorkflow;
using MicroWorkflow.DemoImplementation;
using System.Reflection;
using System.Text.Json;


Expand Down Expand Up @@ -43,21 +43,20 @@ public async Task<ExecutionResult> ExecuteAsync(Step step)
{
var content = JsonSerializer.Deserialize<string>(step.State!);
var topWords = content!
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Split(' ')
.Where(x => x.Length > 3)
.GroupBy(x => x)
.OrderByDescending(x => x.Count())
.Take(3)
.Select(x => x.Key);

return await Task.FromResult(
step.Done().With(new Step(SendEmail.Name, topWords)));
ExecutionResult done = step.Done().With(new Step(SendEmail.Name, topWords));
return await Task.FromResult(done);
}
}

[StepName(Name)]
[StepName("v2/alternative-name")] // step implementation may have multiple names
class SendEmail : IStepImplementation
class SendEmail(EmailSender sender) : IStepImplementation
{
public const string Name = "v1/demos/fetch-wordanalyzeemail/ship-results";

Expand All @@ -66,8 +65,7 @@ public async Task<ExecutionResult> ExecuteAsync(Step step)
var topWords = JsonSerializer.Deserialize<string[]>(step.State!);
var words = string.Join(", ", topWords!);

await new EmailSender()
.SendEmail(to: "[email protected]", from: "[email protected]", $"Top 3 words: {words}");
await sender.SendEmail(to: "[email protected]", from: "[email protected]", $"Top 3 words: {words}");

return step.Done();
}
Expand All @@ -77,33 +75,26 @@ class Program
{
public static void Main()
{
// register the steps by scanning the assembly
var iocContainer = new DemoIocContainer().RegisterNamedSteps(Assembly.GetExecutingAssembly());
// we persist in-memory
iocContainer.RegisterInstance(typeof(IStepPersister), new DemoInMemoryPersister());

// For the demo we tell the engine to stop when there is no immediate pending work, so the program terminates quickly. For production you want the engine to run forever
// The number of workers is dynamically adjusted during execution to fit the pending work.
// This ensures we do not constantly bombard the persistence storage with requests while at the same time quickly respond to new work
var cfg = new WorkflowConfiguration(
new WorkerConfig
{
StopWhenNoImmediateWork = true,
MinWorkerCount = 1,
MaxWorkerCount = 8,
});

// register the logger. Loglevels can change at run-time so you can turn on e.g. fine-grained logs for a limited time
var logger = new ConsoleStepLogger(cfg.LoggerConfiguration);

var engine = new WorkflowEngine(logger, iocContainer, new DotNetStepStateFormatterJson(logger));
var builder = new ContainerBuilder();
var cfg = new WorkflowConfiguration(new WorkerConfig { StopWhenNoImmediateWork = true });
builder.UseMicroWorkflow(cfg);
builder.RegisterType<EmailSender>().AsSelf();
builder.RegisterType<ConsoleStepLogger>().As<IWorkflowLogger>();

var container = builder.Build();

// Add a step to be executed - you can add new steps at any time during run-time
var engine = container.Resolve<WorkflowEngine>();
engine.Data.AddStep(new Step(FetchData.Name, 0));

// Start the engine and wait for it to terminate
engine.Start(cfg);
engine.Start();

PrintResult();
}

private static void PrintResult()
{
Console.WriteLine(PrintTable("Ready", DemoInMemoryPersister.ReadySteps));
Console.WriteLine(PrintTable("Failed", DemoInMemoryPersister.FailedSteps));
Console.WriteLine(PrintTable("Done", DemoInMemoryPersister.DoneSteps));
Expand Down
16 changes: 8 additions & 8 deletions src/Demos/MicroWorkflow.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ public class TestHelper
public NewtonsoftStateFormatterJson? Formatter;
public CancellationTokenSource cts = new();
public AutofacAdaptor? iocContainer;
public SqlServerPersister Persister => (SqlServerPersister)iocContainer!.GetInstance<IStepPersister>();
public SqlServerPersister Persister => (SqlServerPersister)iocContainer!.GetInstance<IWorkflowStepPersister>();
public readonly string CorrelationId = guid();
public readonly string FlowId = guid();
public IWorkflowLogger? Logger;
public WorkflowEngine? Engine;
public (string, IStepImplementation)[] StepHandlers { get; set; } = Array.Empty<(string, IStepImplementation)>();
public (string name, IStepImplementation implementation)[] StepHandlers { get; set; } = Array.Empty<(string, IStepImplementation)>();

readonly ContainerBuilder builder = new();
public string ConnectionString = "Server=localhost;Database=adotest;Integrated Security=True;TrustServerCertificate=True";
Expand Down Expand Up @@ -50,16 +50,16 @@ public WorkflowEngine Build()
((DiagnosticsStepLogger)Logger).AddNestedLogger(new ConsoleStepLogger(WorkflowConfiguration.LoggerConfiguration));
}

builder.RegisterInstances(Logger, StepHandlers);
builder.Register<IStepPersister>(c => new SqlServerPersister(ConnectionString, Logger)).InstancePerDependency();
builder.RegisterWorkflowSteps(StepHandlers.ToArray());
builder.Register<IWorkflowStepPersister>(c => new SqlServerPersister(ConnectionString, Logger)).InstancePerDependency();

// register all classes having a [step] attribute
builder.RegisterStepImplementations(Logger, typeof(TestHelper).Assembly);
builder.RegisterWorkflowSteps(typeof(TestHelper).Assembly);

Formatter ??= new NewtonsoftStateFormatterJson(Logger);

iocContainer = new AutofacAdaptor(builder.Build());
Engine = new WorkflowEngine(Logger, iocContainer, Formatter);
Engine = new WorkflowEngine(WorkflowConfiguration, Logger, iocContainer, Formatter);

Engine.Data.AddSteps(Steps);

Expand Down Expand Up @@ -92,14 +92,14 @@ public WorkflowEngine BuildAndStart()

public WorkflowEngine StartAsync()
{
Engine!.StartAsync(WorkflowConfiguration, stoppingToken: cts.Token);
Engine!.StartAsync(stoppingToken: cts.Token);
return Engine;
}

public WorkflowEngine Start()
{
if (Engine == null) throw new Exception("Remember to 'build' before 'start'");
Engine!.Start(WorkflowConfiguration, stoppingToken: cts.Token);
Engine!.Start(stoppingToken: cts.Token);
return Engine;
}

Expand Down
6 changes: 4 additions & 2 deletions src/Demos/WebApiDemo/RegisterGreenFeetWF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class RegisterWorkflow : Module
protected override void Load(ContainerBuilder builder)
{
// log to in-memory storage
builder.RegisterType<DemoInMemoryPersister>().As<IStepPersister>();
builder.RegisterType<DemoInMemoryPersister>().As<IWorkflowStepPersister>();

// use a simple logger
builder.RegisterType<DiagnosticsStepLogger>().As<IWorkflowLogger>();
Expand All @@ -23,6 +23,8 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType<AutofacAdaptor>().As<IWorkflowIocContainer>();

// find and register all step-implementations
builder.RegisterStepImplementations(null, GetType().Assembly);
builder.RegisterWorkflowSteps(GetType().Assembly);

builder.RegisterInstance(new WorkflowConfiguration(new WorkerConfig()));
}
}
2 changes: 1 addition & 1 deletion src/Demos/WebApiDemo/WorkflowStarter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
SearchModel searchModel = new(Name: step.Name, Singleton: step.Singleton);
engine.Data.AddStepIfNotExists(step, searchModel);

engine.StartAsync(new WorkflowConfiguration(new WorkerConfig()), stoppingToken: stoppingToken);
engine.StartAsync(stoppingToken: stoppingToken);

await Task.CompletedTask;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Product/MicroWorkflow.AdoPersistence/AdoDb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace MicroWorkflow;

public class SqlServerPersister : IStepPersister
public class SqlServerPersister : IWorkflowStepPersister
{
public string TableNameReady { get; set; } = "[dbo].[Steps_Ready]";
public string TableNameFail { get; set; } = "[dbo].[Steps_Fail]";
Expand Down
77 changes: 64 additions & 13 deletions src/Product/MicroWorkflow.Ioc.Autofac/AutofacAdaptor.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,80 @@
using Autofac;
using System.Reflection;
using Autofac;

namespace MicroWorkflow;

public class AutofacAdaptor : IWorkflowIocContainer
{
readonly IComponentContext container;
readonly IComponentContext? container;
readonly ContainerBuilder? builder;

public AutofacAdaptor(IComponentContext container)
public AutofacAdaptor(IComponentContext container) => this.container = container ?? throw new ArgumentNullException(nameof(container));

public AutofacAdaptor(ContainerBuilder builder) => this.builder = builder ?? throw new ArgumentNullException(nameof(builder));

public T GetInstance<T>() where T : notnull => container!.Resolve<T>() ?? throw new Exception($"Type {typeof(T)} is not registered");

public IStepImplementation? GetStep(string stepName)
{
this.container = container;
if (!container!.IsRegisteredWithName<IStepImplementation>(stepName))
return null;

return container!.ResolveNamed<IStepImplementation>(stepName);
}

public T GetInstance<T>() where T : notnull
public void RegisterWorkflowStep(string stepName, Type implementationType)
=> builder!.RegisterType(implementationType).Named<IStepImplementation>(stepName);

public void RegisterWorkflowStep(string stepName, IStepImplementation instance)
=> builder!.RegisterInstance(instance).Named<IStepImplementation>(stepName);
}

public static class AutofacExtensions
{
public static void UseMicroWorkflow(this ContainerBuilder builder, WorkflowConfiguration config, params Assembly?[] assembliesToSearch)
{
var v = container.Resolve<T>()
?? throw new Exception($"Type {typeof(T)} is not registered");
return v;
builder.RegisterInstance(config);
builder.RegisterInstance(config.LoggerConfiguration);
builder.RegisterType<AutofacAdaptor>().As<IWorkflowIocContainer>();
builder.RegisterType<WorkflowEngine>().AsSelf();

var assemblies = ReflectionHelper.FindRelevantAssemblies(assembliesToSearch ?? new Assembly[0]);

var registrar = new AutofacAdaptor(builder);
foreach (var (stepName, implementationType) in ReflectionHelper.FindStepsFromAttribute(assemblies!))
registrar.RegisterWorkflowStep(stepName, implementationType);

FindAndRegister<IWorkflowLogger>();
FindAndRegister<IWorkflowStepPersister>();
FindAndRegister<IWorkflowStepStateFormatter>();

void FindAndRegister<T>() where T : notnull
{
var x = GetTypesInherit<T>(assemblies!);
if (x != null)
builder.RegisterType(x).As<T>();
}
}

public IStepImplementation? GetNamedInstance(string statename)
static Type? GetTypesInherit<T>(Assembly[] assembly)
=> assembly.SelectMany(x => x.GetTypes())
.Where(x => x.IsClass && !x.IsAbstract && x.IsAssignableTo(typeof(T)))
.FirstOrDefault();

public static void RegisterWorkflowSteps(this ContainerBuilder builder, params Assembly[] assemblies)
{
if (!container.IsRegisteredWithName<IStepImplementation>(statename))
return null;
if (assemblies.Length == 0)
assemblies = AppDomain.CurrentDomain.GetAssemblies();

return container.ResolveNamed<IStepImplementation>(statename);
var registrar = new AutofacAdaptor(builder);
foreach (var (stepName, implementationType) in ReflectionHelper.FindStepsFromAttribute(assemblies!))
registrar.RegisterWorkflowStep(stepName, implementationType);
}
}

public static void RegisterWorkflowSteps(this ContainerBuilder builder, params (string stepName, IStepImplementation implementation)[] stepHandlers)
{
var registrar = new AutofacAdaptor(builder);
foreach(var stepHandler in stepHandlers)
registrar.RegisterWorkflowStep(stepHandler.stepName, stepHandler.implementation);
}
}
35 changes: 0 additions & 35 deletions src/Product/MicroWorkflow.Ioc.Autofac/AutofacHelper.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
/// Simple in-memory storage FOR DEMO PURPOSES ONLY.
/// The current transaction handling is incorrect!
/// </summary>
public class DemoInMemoryPersister : IStepPersister
public class DemoInMemoryPersister : IWorkflowStepPersister
{
readonly object GlobalLock = new();
int GlobalId = 1;
static readonly object GlobalLock = new();
static int GlobalId = 1;

public static readonly Dictionary<int, Step> ReadySteps = new();
public static readonly Dictionary<int, Step> DoneSteps = new();
Expand Down
Loading
Loading