Skip to content

Releases: danielgerlag/workflow-core

v3.11.0

10 Sep 16:40
3f12604
Compare
Choose a tag to compare

What's Changed

  • #1270 Fixed list items deserialization duplication by @michalkrzych in #1271
  • fix typo in PersistanceFactory by @Revazashvili in #1267
  • Update correct workflow name for Sample09s by @AngrySKL in #1254
  • Bump System.Data.SqlClient from 4.8.5 to 4.8.6 in /src/providers/WorkflowCore.QueueProviders.SqlServer by @dependabot in #1228
  • Bump MongoDB.Driver from 2.8.1 to 2.19.0 in /src/providers/WorkflowCore.Persistence.MongoDB by @dependabot in #1147
  • Added CosmosClientOptions to UseCosmosDbPersistence method and CosmosClientFactory constructor by @afroze9 in #1062
  • Fix typo on Redis providers readme by @tvdias in #1282
  • Feat update rabbitmq by @JoaquimInGit in #1286
  • Fix morenullrefs in workflow activity by @jakenuts in #1281
  • Oracle persistance provider by @cjundt in #1148
  • added a type resolver to be replaced by another implementation later. by @rapmue in #1166
  • Bump Npgsql from 5.0.14 to 5.0.18 in /src/providers/WorkflowCore.Persistence.PostgreSQL by @dependabot in #1288

New Contributors

Full Changelog: v3.10.0...v3.11.0

v3.10.0

11 Apr 00:09
Compare
Choose a tag to compare

What's Changed

  • Bump System.Linq.Dynamic.Core from 1.2.13 to 1.3.0 in /src/WorkflowCore.DSL by @dependabot in #1182
  • Bump MongoDB.Driver from 2.8.1 to 2.19.0 in /test/WorkflowCore.Tests.MongoDB by @dependabot in #1146
  • Fixed usage of null WorkflowStep.Name in WorkflowActivity diagnostic service. by @jakenuts in #1198
  • the names of the fields in the logs have been changed by @pashtetus1 in #1073
  • Upgrade to net8 and EF8 by @wallyrion in #1215

New Contributors

Full Changelog: 3.9.0...v3.10.0

v3.9.0

14 Jun 14:45
12be887
Compare
Choose a tag to compare

Workflow Core 3.9.0

Updated EF core to v7 - #1168

v3.8.3

10 May 14:36
b7de0a1
Compare
Choose a tag to compare

v3.8.2

06 Apr 14:35
e7219e5
Compare
Choose a tag to compare

Merged PRs

v3.8.1

06 Apr 13:52
6a623b0
Compare
Choose a tag to compare
Merge pull request #1108 from danielgerlag/dependabot/nuget/src/provi…

v3.6.0

31 Oct 20:13
b45525a
Compare
Choose a tag to compare

Workflow Core 3.6.0

Scheduled Commands

Introduces the ability to schedule delayed commands to process a workflow or event, by persisting them to storage.
This is the first step toward removing constant polling of the DB. It also filters out duplicate work items on the queue which is the current problem the greylist tries to solve.
Initial implementation is supported by MongoDb, SQL Server, PostgeSQL, MySQL and SQLite.
Additional support from the other persistence providers to follow.

v3.4.0

19 Apr 01:38
db42f91
Compare
Choose a tag to compare

Workflow Core 3.4.0

Execute Workflow Middleware

These middleware get run after each workflow execution and can be used to perform additional actions or build metrics/statistics for all workflows in your app.

The following example illustrates how you can use a execute workflow middleware to build prometheus metrics.

Note that you use WorkflowMiddlewarePhase.ExecuteWorkflow to specify that it runs after each workflow execution.

Important: You should call next as part of the workflow middleware to ensure that the next workflow in the chain runs.

public class MetricsMiddleware : IWorkflowMiddleware
{
    private readonly ConcurrentHashSet<string>() _suspendedWorkflows =
        new ConcurrentHashSet<string>();

    private readonly Counter _completed;
    private readonly Counter _suspended;

    public MetricsMiddleware()
    {
        _completed = Prometheus.Metrics.CreateCounter(
            "workflow_completed", "Workflow completed");

        _suspended = Prometheus.Metrics.CreateCounter(
            "workflow_suspended", "Workflow suspended");
    }

    public WorkflowMiddlewarePhase Phase =>
        WorkflowMiddlewarePhase.ExecuteWorkflow;

    public Task HandleAsync(
        WorkflowInstance workflow,
        WorkflowDelegate next)
    {
        switch (workflow.Status)
        {
            case WorkflowStatus.Complete:
                if (_suspendedWorkflows.TryRemove(workflow.Id))
                {
                    _suspended.Dec();
                }
                _completed.Inc();
                break;
            case WorkflowStatus.Suspended:
                _suspended.Inc();
                break;
        }

        return next();
    }
}

v3.3

03 Nov 02:08
Compare
Choose a tag to compare

Workflow Core 3.3.0

Workflow Middleware

Workflows can be extended with Middleware that run before/after workflows start/complete as well as around workflow steps to provide flexibility in implementing cross-cutting concerns such as log correlation, retries, and other use-cases.

This is done by implementing and registering IWorkflowMiddleware for workflows or IWorkflowStepMiddleware for steps.

Step Middleware

Step middleware lets you run additional code around the execution of a given step and alter its behavior. Implementing a step middleware should look familiar to anyone familiar with ASP.NET Core's middleware pipeline or HttpClient's DelegatingHandler middleware.

Usage

First, create your own middleware class that implements IWorkflowStepMiddleware. Here's an example of a middleware that adds workflow ID and step ID to the log correlation context of every workflow step in your app.

Important: You must make sure to call next() as part of your middleware. If you do not do this, your step will never run.

public class LogCorrelationStepMiddleware : IWorkflowStepMiddleware
{
    private readonly ILogger<LogCorrelationStepMiddleware> _log;

    public LogCorrelationStepMiddleware(
        ILogger<LogCorrelationStepMiddleware> log)
    {
        _log = log;
    }

    public async Task<ExecutionResult> HandleAsync(
        IStepExecutionContext context,
        IStepBody body,
        WorkflowStepDelegate next)
    {
        var workflowId = context.Workflow.Id;
        var stepId = context.Step.Id;

        // Uses log scope to add a few attributes to the scope
        using (_log.BeginScope("{@WorkflowId}", workflowId))
        using (_log.BeginScope("{@StepId}", stepId))
        {
            // Calling next ensures step gets executed
            return await next();
        }
    }
}

Here's another example of a middleware that uses the Polly dotnet resiliency library to implement retries on workflow steps based off a custom retry policy.

public class PollyRetryStepMiddleware : IWorkflowStepMiddleware
{
    private const string StepContextKey = "WorkflowStepContext";
    private const int MaxRetries = 3;
    private readonly ILogger<PollyRetryStepMiddleware> _log;

    public PollyRetryMiddleware(ILogger<PollyRetryStepMiddleware> log)
    {
        _log = log;
    }

    // Consult Polly's docs for more information on how to build
    // retry policies:
    // https://github.com/App-vNext/Polly
    public IAsyncPolicy<ExecutionResult> GetRetryPolicy() =>
        Policy<ExecutionResult>
            .Handle<TimeoutException>()
            .RetryAsync(
                MaxRetries,
                (result, retryCount, context) =>
                    UpdateRetryCount(
                        result.Exception,
                        retryCount,
                        context[StepContextKey] as IStepExecutionContext)
            );

    public async Task<ExecutionResult> HandleAsync(
        IStepExecutionContext context,
        IStepBody body,
        WorkflowStepDelegate next
    )
    {
        return await GetRetryPolicy().ExecuteAsync(
            ctx => next(),
            // The step execution context gets passed down so that
            // the step is accessible within the retry policy
            new Dictionary<string, object>
            {
                { StepContextKey, context }
            });
    }

    private Task UpdateRetryCount(
        Exception exception,
        int retryCount,
        IStepExecutionContext stepContext)
    {
        var stepInstance = stepContext.ExecutionPointer;
        stepInstance.RetryCount = retryCount;
        return Task.CompletedTask;
    }
}

Pre/Post Workflow Middleware

Workflow middleware run either before a workflow starts or after a workflow completes and can be used to hook into the workflow lifecycle or alter the workflow itself before it is started.

Pre Workflow Middleware

These middleware get run before the workflow is started and can potentially alter properties on the WorkflowInstance.

The following example illustrates setting the Description property on the WorkflowInstance using a middleware that interprets the data on the passed workflow. This is useful in cases where you want the description of the workflow to be derived from the data passed to the workflow.

Note that you use WorkflowMiddlewarePhase.PreWorkflow to specify that it runs before the workflow starts.

Important: You should call next as part of the workflow middleware to ensure that the next workflow in the chain runs.

// AddDescriptionWorkflowMiddleware.cs
public class AddDescriptionWorkflowMiddleware : IWorkflowMiddleware
{
    public WorkflowMiddlewarePhase Phase =>
        WorkflowMiddlewarePhase.PreWorkflow;

    public Task HandleAsync(
        WorkflowInstance workflow,
        WorkflowDelegate next
    )
    {
        if (workflow.Data is IDescriptiveWorkflowParams descriptiveParams)
        {
            workflow.Description = descriptiveParams.Description;
        }

        return next();
    }
}

// IDescriptiveWorkflowParams.cs
public interface IDescriptiveWorkflowParams
{
    string Description { get; }
}

// MyWorkflowParams.cs
public MyWorkflowParams : IDescriptiveWorkflowParams
{
    public string Description => $"Run task '{TaskName}'";

    public string TaskName { get; set; }
}

Exception Handling in Pre Workflow Middleware

Pre workflow middleware exception handling gets treated differently from post workflow middleware. Since the middleware runs before the workflow starts, any exceptions thrown within a pre workflow middleware will bubble up to the StartWorkflow method and it is up to the caller of StartWorkflow to handle the exception and act accordingly.

public async Task MyMethodThatStartsAWorkflow()
{
    try
    {
        await host.StartWorkflow("HelloWorld", 1, null);
    }
    catch(Exception ex)
    {
        // Handle the exception appropriately
    }
}

Post Workflow Middleware

These middleware get run after the workflow has completed and can be used to perform additional actions for all workflows in your app.

The following example illustrates how you can use a post workflow middleware to print a summary of the workflow to console.

Note that you use WorkflowMiddlewarePhase.PostWorkflow to specify that it runs after the workflow completes.

Important: You should call next as part of the workflow middleware to ensure that the next workflow in the chain runs.

public class PrintWorkflowSummaryMiddleware : IWorkflowMiddleware
{
    private readonly ILogger<PrintWorkflowSummaryMiddleware> _log;

    public PrintWorkflowSummaryMiddleware(
        ILogger<PrintWorkflowSummaryMiddleware> log
    )
    {
        _log = log;
    }

    public WorkflowMiddlewarePhase Phase =>
        WorkflowMiddlewarePhase.PostWorkflow;

    public Task HandleAsync(
        WorkflowInstance workflow,
        WorkflowDelegate next
    )
    {
        if (!workflow.CompleteTime.HasValue)
        {
            return next();
        }

        var duration = workflow.CompleteTime.Value - workflow.CreateTime;
        _log.LogInformation($@"Workflow {workflow.Description} completed in {duration:g}");

        foreach (var step in workflow.ExecutionPointers)
        {
            var stepName = step.StepName;
            var stepDuration = (step.EndTime - step.StartTime) ?? TimeSpan.Zero;
            _log.LogInformation($"  - Step {stepName} completed in {stepDuration:g}");
        }

        return next();
    }
}

Exception Handling in Post Workflow Middleware

Post workflow middleware exception handling gets treated differently from pre workflow middleware. At the time that the workflow completes, your workflow has ran already so an uncaught exception would be difficult to act on.

By default, if a workflow middleware throws an exception, it will be logged and the workflow will complete as normal. This behavior can be changed, however.

To override the default post workflow error handling for all workflows in your app, just register a new IWorkflowMiddlewareErrorHandler in the dependency injection framework with your custom behavior as follows.

// CustomMiddlewareErrorHandler.cs
public class CustomHandler : IWorkflowMiddlewareErrorHandler
{
    public Task HandleAsync(Exception ex)
    {
        // Handle your error asynchronously
    }
}

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // Other workflow configuration
    services.AddWorkflow();

    // Should go after .AddWorkflow()
    services.AddTransient<IWorkflowMiddlewareErrorHandler, CustomHandler>();
}

Registering Middleware

In order for middleware to take effect, they must be registered with the built-in dependency injection framework using the convenience helpers.

Note: Middleware will be run in the order that they are registered with middleware that are registered earlier running earlier in the chain and finishing later in the chain. For pre/post workflow middleware, all pre middleware will be run before a workflow starts and all post middleware will be run after a workflow completes.

...
Read more

v3.2

08 Aug 15:52
Compare
Choose a tag to compare

Workflow Core 3.2

  • Option to run foreach in non-parallel mode #597
  • StartWith no longer required #598
  • Concurrency issue #608
  • Option to auto-delete complete workflows on redis #613