profile
viewpoint
Danil Flores dflor003 @UltimateSoftware Pembroke Pines, FL

dflor003/graphql-anywhere-mongodb 14

Uses graphql-anywhere to build mongodb queries

dflor003/decorum 3

Decorum ES7 Decorator-based Validations for JavaScript

dflor003/graphql-anywhere-mongodb-express 3

express middleware for graphql-anywhere-mongodb

dflor003/jasmine-rowtests 3

This simple jasmine plugin allows you to create multiple specs based off of different test data (i.e. row tests).

dflor003/mu-logger 1

μLogger - An asynchronous NodeJS multi-output logger optimized for microservices.

arodr967/material2 0

Material Design components for Angular

dflor003/angular2-webpack 0

A complete, yet simple, starter for Angular 2 using webpack

dflor003/AppMetrics 0

App Metrics is an open-source and cross-platform .NET library used to record and report metrics within an application.

dflor003/bluebird 0

Bluebird is a full featured promise library with unmatched performance.

push eventUltimateSoftware/workflow-core

DanilF

commit sha 8e0a5c2a8a7785d818523f7cc781d3dc8b1b656c

Add middleware runner, step executor, and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples Add async overloads for StartWorkflow and WaitForWorkflowToComplete in integration tests Add error handling of post workflow middleware Add docs for workflow middleware

view details

push time in a day

pull request commentdanielgerlag/workflow-core

Feature - Workflow Middleware

Docs fresh off the press! Let me know what you think:

https://github.com/danielgerlag/workflow-core/blob/650d1591a1b9e7be1cc04efb00d3baa296ea102e/docs/workflow-middleware.md

dflor003

comment created time in 2 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 650d1591a1b9e7be1cc04efb00d3baa296ea102e

Add middleware runner, step executor, and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples Add async overloads for StartWorkflow and WaitForWorkflowToComplete in integration tests Add error handling of post workflow middleware Add docs for workflow middleware

view details

push time in 2 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 48543988e21098bd83636e8a8e53db9b56a17563

Add middleware runner, step executor, and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples Add async overloads for StartWorkflow and WaitForWorkflowToComplete in integration tests Add error handling of post workflow middleware Add docs for workflow middleware

view details

push time in 2 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 9b478eda5d0e79f2493d60c9cc93fd61fc10d6e8

Add middleware runner, step executor, and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples Add async overloads for StartWorkflow and WaitForWorkflowToComplete in integration tests Add error handling of post workflow middleware Add docs for workflow middleware

view details

push time in 2 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha b5b09a0511d871a1f666eba4b48ce0268194b2d8

Add middleware runner, step executor, and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples Add async overloads for StartWorkflow and WaitForWorkflowToComplete in integration tests Add error handling of post workflow middleware

view details

push time in 2 days

pull request commentdanielgerlag/workflow-core

Feature - Workflow Middleware

Checked in the new exception handling for post workflow middleware. Take a look and tell me what you think. I also retrofitted the code to be able to load it from yaml as well. Updated the sample 19 with example usage as well.

dflor003

comment created time in 2 days

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Linq;+using System.Threading.Tasks;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Services+{+    /// <summary>+    /// Runner with the ability to apply middleware to steps and workflows.+    /// </summary>+    public class WorkflowMiddlewareRunner : IWorkflowMiddlewareRunner

Take a look at my latest changes, I split it out and it made testing much easier and follows the single responsibility principle quite nicely. Now there's StepExecutor which executes the step with middleware and WorkflowMiddlewareRunner ONLY runs pre/post workflow. This felt the most natural.

dflor003

comment created time in 2 days

PullRequestReviewEvent

push eventUltimateSoftware/workflow-core

DanilF

commit sha 432c809e60bf1fc0958fdc6881a63f580409d983

Add middleware runner, step executor, and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples Add async overloads for StartWorkflow and WaitForWorkflowToComplete in integration tests Add error handling of post workflow middleware

view details

push time in 2 days

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Linq;+using System.Threading.Tasks;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Services+{+    /// <summary>+    /// Runner with the ability to apply middleware to steps and workflows.+    /// </summary>+    public class WorkflowMiddlewareRunner : IWorkflowMiddlewareRunner

With this being called StepExecutor now, I think it makes even more sense to split it into two classes, one for steps and one for workflow pre/post middleware. Do you agree?

dflor003

comment created time in 2 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Threading.Tasks;+using Microsoft.Extensions.Logging;+using Polly;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Sample19.Middleware+{+    public class PollyRetryMiddleware : IWorkflowStepMiddleware+    {+        private const string StepContextKey = "WorkflowStepContext";+        private const int MaxRetries = 3;+        private readonly ILogger<PollyRetryMiddleware> _log;++        public PollyRetryMiddleware(ILogger<PollyRetryMiddleware> log)+        {+            _log = log;+        }++        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(), new Dictionary<string, object>+            {+                { StepContextKey, context }+            });+        }++        private Task UpdateRetryCount(+            Exception exception,+            int retryCount,+            IStepExecutionContext stepContext)+        {+            var stepInstance = stepContext.ExecutionPointer;+            stepInstance.RetryCount = retryCount;++            _log.LogWarning(+                exception,+                "Exception occurred in step {StepId}. Retrying [{RetryCount}/{MaxCount}]",+                stepInstance.Id,+                retryCount,+                MaxRetries+            );++            // TODO: Come up with way to persist workflow

So, re: persistence of steps. In our app that uses Workflow Core, we already have a way of persisting steps so we have a work around for now. In the interest of getting this PR out sooner, I'm thinking of not tackling step persistence as part of this PR and tackling it as a separate issue. Sound good?

dflor003

comment created time in 2 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Linq;+using System.Threading.Tasks;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Services+{+    /// <summary>+    /// Runner with the ability to apply middleware to steps and workflows.+    /// </summary>+    public class WorkflowMiddlewareRunner : IWorkflowMiddlewareRunner

Sounds good. I like StepExecutor

dflor003

comment created time in 2 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

 public async Task<string> StartWorkflow<TData>(string workflowId, int? version,              wf.ExecutionPointers.Add(_pointerFactory.BuildGenesisPointer(def)); +            await _middlewareRunner.RunPreMiddleware(wf);

Hmm... that would add a bit of complexity to it. So how about we just document that the workflow does not have an id in the pre-workflow middleware and keep it prior to the initial persistence?

dflor003

comment created time in 2 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Linq;+using System.Threading.Tasks;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Services+{+    /// <summary>+    /// Runner with the ability to apply middleware to steps and workflows.+    /// </summary>+    public class WorkflowMiddlewareRunner : IWorkflowMiddlewareRunner

Hmm... Thinking about this some more. WorkflowMiddlewareAndStepRunner doesn't exactly sound well. Then I tried changing it to WorkflowStepRunner but that doesn't imply that it also runs pre/post workflow middleware. So regarding naming, I think we have these options:

  1. Keep it as WorkflowMiddlewareRunner and document that it also runs the step in the method name and comments. We could always rename RunStep -> RunStepWithMiddleware to emphasize that it runs the step and also runs middleware around it.
  2. Split the class into two classes, one for running pre/post middleware and the other for running steps with middleware. I'd probably call them WorkflowMiddlewareRunner and WorkflowStepRunner respectively. Although the major con here is that there will be two dependencies needed to be added to the executor constructor instead of one.

I'm in favor of option 1. What are your thoughts?

dflor003

comment created time in 3 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using Microsoft.Extensions.DependencyInjection;+using Microsoft.Extensions.Logging;+using System;+using Microsoft.Extensions.Logging.Console;+using WorkflowCore.Interface;+using WorkflowCore.Sample19.Middleware;+using WorkflowCore.Sample19.Steps;++namespace WorkflowCore.Sample19+{+    class Program+    {+        static void Main(string[] args)+        {+            var serviceProvider = ConfigureServices();++            // Start the workflow host+            var host = serviceProvider.GetService<IWorkflowHost>();+            host.RegisterWorkflow<FlakyConnectionWorkflow, FlakyConnectionParams>();+            host.Start();++            var workflowParams = new FlakyConnectionParams+            {+                Description = "Flaky connection workflow"+            };+            var workflowId = host.StartWorkflow("flaky-sample", workflowParams).Result;+            Console.WriteLine($"Kicked off workflow {workflowId}");++            Console.ReadLine();+            host.Stop();+        }++        private static IServiceProvider ConfigureServices()+        {+            // Setup dependency injection+            IServiceCollection services = new ServiceCollection();+            services.AddWorkflow();++            // Add step middleware+            // Note that middleware will get executed in the order in which they were registered+            services.AddWorkflowStepMiddleware<AddMetadataToLogsMiddleware>();+            services.AddWorkflowStepMiddleware<PollyRetryMiddleware>();++            // Add some pre workflow middleware+            // This middleware will run before the workflow starts+            services.AddWorkflowMiddleware<AddDescriptionWorkflowMiddleware>();

Yeah, sounds good. So leave it as is for now?

dflor003

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Linq;+using System.Threading.Tasks;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Services+{+    /// <summary>+    /// Runner with the ability to apply middleware to steps and workflows.+    /// </summary>+    public class WorkflowMiddlewareRunner : IWorkflowMiddlewareRunner

Ah gotcha. Sounds good. Will rename to reflect that. Something like WorkflowMiddlewareAndStepRunner? Or perhaps WorkflowStepRunner and the middleware portion is implied?

dflor003

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

 public async Task<string> StartWorkflow<TData>(string workflowId, int? version,              wf.ExecutionPointers.Add(_pointerFactory.BuildGenesisPointer(def)); +            await _middlewareRunner.RunPreMiddleware(wf);

Ah, it is? Is the ID always a Guid? If so, we can rely on that. I was hoping to have it run before the persistence step so that any changes to the workflow (i.e. setting the description) would be persisted along with it.

Would it make sense to persist it once before and once after?

dflor003

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using Microsoft.Extensions.DependencyInjection;+using Microsoft.Extensions.Logging;+using System;+using Microsoft.Extensions.Logging.Console;+using WorkflowCore.Interface;+using WorkflowCore.Sample19.Middleware;+using WorkflowCore.Sample19.Steps;++namespace WorkflowCore.Sample19+{+    class Program+    {+        static void Main(string[] args)+        {+            var serviceProvider = ConfigureServices();++            // Start the workflow host+            var host = serviceProvider.GetService<IWorkflowHost>();+            host.RegisterWorkflow<FlakyConnectionWorkflow, FlakyConnectionParams>();+            host.Start();++            var workflowParams = new FlakyConnectionParams+            {+                Description = "Flaky connection workflow"+            };+            var workflowId = host.StartWorkflow("flaky-sample", workflowParams).Result;+            Console.WriteLine($"Kicked off workflow {workflowId}");++            Console.ReadLine();+            host.Stop();+        }++        private static IServiceProvider ConfigureServices()+        {+            // Setup dependency injection+            IServiceCollection services = new ServiceCollection();+            services.AddWorkflow();++            // Add step middleware+            // Note that middleware will get executed in the order in which they were registered+            services.AddWorkflowStepMiddleware<AddMetadataToLogsMiddleware>();+            services.AddWorkflowStepMiddleware<PollyRetryMiddleware>();++            // Add some pre workflow middleware+            // This middleware will run before the workflow starts+            services.AddWorkflowMiddleware<AddDescriptionWorkflowMiddleware>();

I was thinking about that as well, but it would introduce some additional complexity and also probably add some additional fields to the interface. In theory, I could have some kind of field like the following on the interface:

int? Order { get; }

However, then every consumer would be forced to implement it even in cases where they don't care about the order. Do you feel the additional complexity is warranted? Are there any use-cases where just changing the order in which the middleware are defined is not sufficient?

dflor003

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Linq;+using System.Threading.Tasks;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Services+{+    /// <summary>+    /// Runner with the ability to apply middleware to steps and workflows.+    /// </summary>+    public class WorkflowMiddlewareRunner : IWorkflowMiddlewareRunner

As it stands now the workflow middleware runner does execute the step as the last "next" in the step middleware chain. This is deliberate to allow step middleware to add logic around the execution of a step and even potentially change the step's result.

Are you suggesting to not have it run the step?

dflor003

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

 public async Task<string> StartWorkflow<TData>(string workflowId, int? version,              wf.ExecutionPointers.Add(_pointerFactory.BuildGenesisPointer(def)); +            await _middlewareRunner.RunPreMiddleware(wf);

The ID of the WorkflowInstance? Yes. And with the way that the interface looks, they should have access to every property of the WorkflowInstance since it gets passed down to the Handle method.

dflor003

comment created time in 4 days

PullRequestReviewEvent

push eventUltimateSoftware/workflow-core

DanilF

commit sha 97215f6621577cefd766bf8b2fe5475c7fb050de

Add middleware runner and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples Add async overloads for StartWorkflow and WaitForWorkflowToComplete in integration tests

view details

push time in 7 days

pull request commentdanielgerlag/workflow-core

Feature - Workflow Middleware

Seems like its having trouble running some of the integration tests in appveyor and I'm not sure why. I tried to follow the same pattern as all of the other integration tests. Is there any reason you can see that these would stall?

This is the offending test: https://github.com/danielgerlag/workflow-core/pull/684/files#diff-fb926f25cd2306c89a5c425ebd0e886c8d4f8a8f35f665b34df3d3047adbde95R130

dflor003

comment created time in 7 days

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

 public void Setup()             Subject.Start();         } -        protected abstract IDistributedLockProvider CreateProvider();        +        protected abstract IDistributedLockProvider CreateProvider();          [Test]-        public async void AcquiresLock()+        public async Task AcquiresLock()

When I was running the tests, NUnit was complaining that these tests were invalid because they were async void instead of async Task. When you have async void, it does not give NUnit the hooks it needs to wait for the test to complete.

dflor003

comment created time in 7 days

PullRequestReviewEvent

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System;+using System.Collections.Generic;+using System.Threading.Tasks;+using Microsoft.Extensions.Logging;+using Polly;+using WorkflowCore.Interface;+using WorkflowCore.Models;++namespace WorkflowCore.Sample19.Middleware+{+    public class PollyRetryMiddleware : IWorkflowStepMiddleware+    {+        private const string StepContextKey = "WorkflowStepContext";+        private const int MaxRetries = 3;+        private readonly ILogger<PollyRetryMiddleware> _log;++        public PollyRetryMiddleware(ILogger<PollyRetryMiddleware> log)+        {+            _log = log;+        }++        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(), new Dictionary<string, object>+            {+                { StepContextKey, context }+            });+        }++        private Task UpdateRetryCount(+            Exception exception,+            int retryCount,+            IStepExecutionContext stepContext)+        {+            var stepInstance = stepContext.ExecutionPointer;+            stepInstance.RetryCount = retryCount;++            _log.LogWarning(+                exception,+                "Exception occurred in step {StepId}. Retrying [{RetryCount}/{MaxCount}]",+                stepInstance.Id,+                retryCount,+                MaxRetries+            );++            // TODO: Come up with way to persist workflow

I left a todo here. We should figure out if it makes sense to allow you to persist workflow steps.

dflor003

comment created time in 7 days

PullRequestReviewEvent

push eventUltimateSoftware/workflow-core

DanilF

commit sha 8078d23e046e54e22e5aafd925344e232f549c44

Add middleware runner and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples

view details

push time in 7 days

Pull request review commentdanielgerlag/workflow-core

Feature - Workflow Middleware

+using System.Threading.Tasks;+using WorkflowCore.Models;++namespace WorkflowCore.Interface+{+    /// <summary>+    /// Determines at which point to run the middleware.+    /// </summary>+    public enum WorkflowMiddlewarePhase

I had considered also having a WorkflowMiddlewarePhase.Both to enable middleware that runs both pre/post. Do you think that would be a good idea?

dflor003

comment created time in 7 days

PullRequestReviewEvent

pull request commentdanielgerlag/workflow-core

Feature - Workflow Middleware

Oh, I hadn't considered that... Could it work the same way that you specify StepType or DataType? Something like this:

Id: AddWorkflow
Version: 1
DataType: MyApp.MyDataClass, MyApp
OnPostMiddlewareException: MyApp.MyPostMiddlewareExceptionHandler, MyApp
Steps:
- Id: Hello
  StepType: MyApp.HelloWorld, MyApp
  NextStepId: Add
- Id: Add
  StepType: MyApp.AddNumbers, MyApp
  NextStepId: Bye
  Inputs:
    Value1: data.Value1
    Value2: data.Value2
  Outputs:
    Answer: step.Result
- Id: Bye
  StepType: MyApp.GoodbyeWorld, MyApp
dflor003

comment created time in 7 days

pull request commentdanielgerlag/workflow-core

Feature - Workflow Middleware

Re: Exception handling in post-workflow middleware. Here's another idea that's an offshoot of your suggestion of introducing an OnException method.

What if we add another method to IWorkflowBuilder similar to UseDefaultErrorBehavior that takes in a type that should be invoked when an exception occurs. This will get set on the WorkflowDefinition and will default to essentially a noop that catches the exception and does nothing so that there will be no issues of backwards compatibility.

Here's an example of how that could look:

public class MyWorkflow: IWorkflow<object> {
  public string Id => nameof(MyWorkflow);
  public int Version => 1;

  public void Build(IWorkflowBuilder<object> builder) =>
    builder
      .OnPostMiddlewareException<MyPostMiddlewareExceptionHandler>()
      .StartWith<SomeStep>();
}

public class MyPostMiddlewareExceptionHandler : IPostWorkflowMiddlewareExceptionHandler {
  public MyPostMiddlewareExceptionHandler(...) {
    // Will fetch from DI so you can inject whatever dependencies you
    // need in case you want to ship the error to DB or some external service
  }

  public Task Handle(Exception ex) {
    // Handle it somehow here
  }
}
dflor003

comment created time in 7 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 7debf378b72e91845d9db0a20a41b6091410ff27

Add middleware runner and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples

view details

push time in 7 days

issue commentdanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

Let's continue discussion on the PR that I just put out so that we have the code for context. #684

dflor003

comment created time in 7 days

issue commentdanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

Unfortunately, it looks like adding an OnException method to IWorkflow would break backwards compatibility with any existing workflows since they would all now have to implement that method.

As for adding an additional method to the middleware itself, would there be any benefit of this over just adding a try/catch block around the middleware body?

dflor003

comment created time in 7 days

pull request commentdanielgerlag/workflow-core

Feature - Workflow Middleware

A few things that I still need to do:

[ ] Determine how we are going to handle exceptions in post workflow middleware. Just need some direction from @danielgerlag on how this OnException should work. [ ] Update the main docs with info on middleware and links to the sample project. What area of the docs does it make the most sense to add this to?

dflor003

comment created time in 7 days

PR opened danielgerlag/workflow-core

Feature - Workflow Middleware
  • Added concept of workflow middleware and workflow step middleware
  • Added helpers to register these with DI
+1172 -38

0 comment

25 changed files

pr created time in 7 days

issue commentdanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

So are you suggesting a virtual method in IWorkflow that will get called whenever we have an exception in the post workflow middleware? With default implementation just logging the exception? If so, that sounds reasonable.

Since most of it is ready, I'm going to go ahead and submit the PR some time today and we can discuss more implementation details in the PR comments.

dflor003

comment created time in 7 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 7d42a7036a1e658faa04b6559e264c89572905a4

Add middleware runner and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples

view details

push time in 8 days

issue commentdanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

PR is almost ready! Just writing a few integration tests at this point and I've also added a sample project with a few of the use-cases in the samples folder.

Last bit I need to figure out is the exception handling bit. I was able to get it to bubble up the exception as part of StartWorkflow just fine for the pre-workflow middleware.

The post-workflow middleware, however, is a bit trickier than I thought. So if we are to fallback to the error handler defined for the workflow... What should the behavior be for the possible values of WorkflowErrorHandling?

  • Retry - Does this mean retry the entire workflow because of an error in the post workflow middleware?
  • Suspend - The workflow is already finished. What would we suspend?
  • Terminate - This one would make sense I guess. We could just set the workflow state to Terminated?
  • Compensate - Is there a concept of a compensation at the workflow level?

Thinking about how we could handle all the possible scenarios, it seems like it may get a bit hairy. I'm wondering if it would be better just to handle the error and log it? Or do you have other suggestions?

dflor003

comment created time in 8 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 7386b5b080018eaec781b41e698fe1a59fc80562

Add middleware runner and ability to run middleware around workflow steps and before/after workflow Add sample with a sample middleware for retrying and log correlation as well as workflow pre/post samples

view details

push time in 8 days

issue commentdanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

it should bubble the exception up to the caller of StartWorkflow? (in that case it could not be async)

If we're looking to bubble it up to the caller of StartWorkflow, then looks like the best place to inject the middleware would be right before publishing the WorkflowStarted event. Around line 89 in WorkflowController. Since StartWorkflow is already asynchronous and returns a task, I think we should still be able to keep the middleware async, right?

I'm thinking of putting it before it persists the workflow on line 86. That way you can modify the workflow attributes if needed. Does that make sense? Something like this:

await _middlewareRunner.RunPreMiddleware(wf);
await _persistenceStore.CreateNewWorkflow(wf);

For post middleware, probably just fallback to the error handler defined at the workflow or global levels?

Yep, sounds good. I think that would be best as well.

dflor003

comment created time in 8 days

issue commentdanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

Quick update. I was able to put together the handling for workflow step middleware fairly easily and it's working well. I'm working on workflow pre/post middleware now and need a little bit of guidance.

On the step middleware, if an exception is raised, I'm letting it bubble up to the workflow executor since that will allow it to get handled by the IWorkflowErrorHandler declared for the step.

With workflow instances, however, there is no error handler that can handle the errors and if we let errors bubble up, they will prevent the workflow from starting. What should happen in pre/post workflow middleware in the case that the middleware raises an exception?

My gut feeling is that we should prevent errors in workflow pre/post middleware from bubbling up and crashing the workflow, so I'm thinking of just logging the exceptions and proceeding with the workflow anyways. What are your thoughts?

dflor003

comment created time in 9 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 9a5b16f7aff8b2b059ab6c0b87f323972988221c

Add middleware runner and ability to run middleware around workflow steps Add sample with a sample middleware for retrying and log correlation

view details

push time in 10 days

push eventUltimateSoftware/workflow-core

DanilF

commit sha 477d9e24cd34edec5f18893b87d03e16f5743add

Add middleware runner and ability to run middleware around workflow steps

view details

push time in 10 days

create barnchUltimateSoftware/workflow-core

branch : feature/WorkflowMiddleware

created branch time in 10 days

issue commentdanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

Thanks! Glad you're on board with it.

As for persisting workflow steps, I think it would definitely be useful to be able to update it prior to the step's completion. I'll have to play around with it while putting the PR together and see how it would look but right now two options come to mind.

  1. What I mentioned about exposing some kind of UpdateStep method on IPersistenceProvider.
  2. Perhaps adding a Persist method to IStepExecutionContext that will persist any changes to the step. This could either be smart enough to detect changes to the model (i.e. via EFCore's ChangeTracker) or you could pass the step in directly. I haven't looked at the other persistence providers besides the EFCore one, so I would like to explore which option is better.

I should be able to have something testable within a week or two. Will post more questions here as they arise.

dflor003

comment created time in 11 days

issue commentdanielgerlag/workflow-core

Suspend workflow not working when running in cluster mode

I've documented the proposal in #678. Please take a look whenever you have some time.

dflor003

comment created time in 11 days

issue openeddanielgerlag/workflow-core

Feature Proposal - Workflow and Step Middleware

Proposal

My team has been experimenting with this library for a couple of projects and so far it's been quite a pleasure to work with, so thank you very much for that!

One area, though, where I see an opportunity for improvement around extensibility for various use-cases within the consuming apps. I propose adding a few additional hooks at the workflow and step level in the form of middleware to enable more extensibility for just about any use-case. Please take a look at the proposal below and if everything makes sense, I'd be happy to put together a PR to add this functionality.

These middleware would be similar in form to the type of middleware you would find around the ASP.NET Core Controller middleware or HttpClient's DelegatingHandler middleware so it should feel familiar to enough to dotnet core users.

Here's an example of a few use-cases that this could help us solve:

  • More robust retry strategies such as circuit breakers, exponential retry, and fallback using other libraries such as Polly. There are a few existing issues that this may address: #654, #508
  • Injecting log correlation information into each workflow step so that we can view logs by workflow id.
  • Appending additional metadata to workflow steps to support UIs. This would help with these issues: #672, #665
  • Provide a general, unobtrusive hook into workflows and steps to make it easier to implement additional functionality without having to code for it in every step.

Examples

A line of code is worth a million pictures, so here are a few examples of how I think these middleware could look from the consumer perspective.

Example 1 - Use a step middleware to add log correlation data to workflow steps

This is an example of how a middleware would look to add the workflow ID to log context using the Serilog logging library and their built-in logging context.

using Serilog;

public class SerilogCorrelationMiddleware : IWorkflowStepMiddleware {
  private const string WorkflowIdContextProperty = "workflowId";
  private readonly ILogger _log;

  public SerilogCorrelationMiddleware(ILogger logger) {
    _log = logger.ForContext<SerilogCorrelationMiddleware>();
  }

  public async Task<ExecutionResult> HandleAsync(
    IStepExecutionContext context, 
    IStepBody body, 
    WorkflowStepDelegate next
  ) {
    var workflowId = context.Workflow.Id;
    
    using (LogContext.PushProperty(WorkflowIdContextProperty, workflowId)) {
      return await next();
    }
  }
}

Example 2 - Use a step middleware to implement robust retry policies via Polly

This is an example of integrating a fairly popular dotnet core resiliency library called Polly into Workflow Core to provide just about any kind of retry/circuit breaker policy you could think of. The example assumes that the step makes HTTP and SQL calls and is loosely based off the Microsoft Docs here.

using Polly;

public class StepRetryMiddleware : IWorkflowStepMiddleware {
  private const string StepContextKey = "WorkflowStepContext";
  private readonly IPersistenceProvider _persistence;
  private static readonly TimeSpan[] RetryTimes = {
    TimeSpan.FromMilliseconds(1),
    TimeSpan.FromMilliseconds(2),
    TimeSpan.FromMilliseconds(3),
  };

  public StepRetryMiddleware(IPersistenceProvider persistence) {
    _persistence = persistence;
  }

  public IAsyncPolicy<ExecutionResult> GetRetryPolicy() =>
    Policy<ExecutionResult>
      .Handle<HttpRequestException>()
      .Or<SqlException>()
      .WaitAndRetryAsync(
        RetryTimes, 
        (exception, nextTime, retryCount, context) => 
          UpdateRetryCount(retryCount, context[StepContextKey] as IStepExecutionContext)
          // Based off the patterns here http://www.thepollyproject.org/2017/05/04/putting-the-context-into-polly/
      );

  public async Task<ExecutionResult> HandleAsync(
    IStepExecutionContext context, 
    IStepBody body, 
    WorkflowStepDelegate next
  ) {
    return await GetRetryPolicy().ExecuteAsync(() => next(), new Dictionary<string, object> {
      { StepContextKey, context }
    });
  }

  private async Task UpdateRetryCount(int retryCount, IStepExecutionContext stepContext) {
    var stepInstance = stepContext.ExecutionPointer;
    stepInstance.RetryCount = retryCount;
    _persistence.UpdateStep(stepInstance); // Note: I don't think this function exists, would need to discuss
  }
}

Example 3 - Registering middleware with dotnet core's native DI

Each of these middleware should be able to be registered with DI just like any other dependency of your app. It would also be nice from a developer experience perspective if we expose some configuration helpers to register it such as the following:

// Startup.cs
public class Startup {
  public void ConfigureServices(IServiceCollection services) {
    // Use a helper to register middleware
    // Note: Would need to determine if it makes sense to register those under the hood
    // as transient, scoped, or singleton.
    services
      .AddWorkflowMiddleware<SerilogCorrelationMiddleware>()
      .AddWorkflowMiddleware<StepRetryMiddleware >();
  }
}

Implementation

Workflow Step Middleware

In analyzing this, I think that the best place to add the step hooks would be within WorkflowExecutor.ExecuteStep around this call:

var result = await body.RunAsync(context);

I think it could be done one of the following ways:

  1. WorkflowExecutor would directly take in an IEnumerable<IWorkflowStepMiddleware> and implement all of the logic to build and run the middleware chain around the execution of a step body.
  2. Rather than implementing the logic directly in WorkflowExecutor, there could be a class introduced to the tune of WorkflowMiddlewareRunner that handles all of the middleware logic. This would make things easier from a testing perspective and follows the single responsibility principle a bit better.

I'm more in favor of the latter approach.

Overall Workflow Middleware

I haven't thought about this one as much as I couldn't find as many use-cases for this as for middleware around the individual steps.

Middleware at the overall workflow level would have to be split up into two parts since the entire process is not done within the context of a single method execution. You would basically need some "Beforeware" and "Afterware" to hook into before a workflow starts executing and once a workflow is done executing respectively.

Let's discuss more and see if this is even needed.

Open Questions

If you are comfortable with us submitting a PR for this functionality, there's a few open questions we'd still need to answer to see what the behavior should be.

  • What happens if a workflow step middleware raises an error? To best mimic the behavior of existing middleware implementations like controller middleware and http client delegating handlers, it sounds like it would be best to bubble the middleware up the chain. What would need to happen if it doesn't get handled by any middleware in the chain and bubbles up to the workflow executor?
  • The examples above give a rough draft of what I think the API for these middleware should look like. What are your thoughts?
  • How should we treat the DI lifetime of the middleware? Should they be scoped? Transient? Singleton? This will likely have to line up with the lifetime of the WorkflowExecutor.
  • With this pattern, a middleware can now alter the outcome of the workflow step body execution. I think that this is good and can enable some interesting new patterns, but what (if any) implications does this have on the engine as a whole?
  • Should this also include middleware for the overall workflow that gets called when it starts as well as when it finishes?
  • As far as I'm aware, the IPersistenceProvider does not have a way to update the state of an ExecutionPointer. In many of the use-cases, it would be useful to update various properties of ExecutionPointer such as ExtensionAttributes and PersistenceData. Do you think it's a good idea to add that to the interface? What would be the implications of doing so?

created time in 11 days

issue commentdanielgerlag/workflow-core

Suspend workflow not working when running in cluster mode

Yeah, I started looking at ways I could implement the queueing suspend command approach and it would be non-trivial.

I've been doing some thinking and I've got a feature proposal that may help with this as well as #672, #665, and a few other use-cases my team is looking to implement around Workflow Core. Give me a few and I will try to put the proposal together for discussion as a separate issue.

dflor003

comment created time in 11 days

issue commentdanielgerlag/workflow-core

Suspend workflow not working when running in cluster mode

Hmmm... So then that means that when running in a cluster, you can only suspend the workflow if you get lucky and happen to hit the node that happens to be running that workflow at the time. Is that correct?

It would be nice to be able to treat the suspend as a request to suspend that will get fulfilled whenever the workflow frees up.

dflor003

comment created time in 12 days

issue commentdanielgerlag/workflow-core

StartTime Not Persisted on Workflow Steps in DB When Workflow Step Starts

My specific use case is to be able to track the run time of each step in an admin UI so if a step gets stuck, we can see how long it's been running for and kill it if necessary.

The steps themselves are interacting with a 3rd party system that unfortunately does not emit events we can listen to.

For the time being, I've been able to work around it by listening to the StepStarted event and manually updating the start time based off the event time.

This workaround has been working so far so this issue can be closed, but do you think it would be a good idea to persist that for others that may have the same use case?

dflor003

comment created time in 14 days

issue openeddanielgerlag/workflow-core

Suspend workflow not working when running in cluster mode

Hi! I've been trying to suspend a workflow that has been running for quite some time that is stuck in a permanent retry loop. Unfortunately, every time it tries to suspend, IWorkflowController.suspend returns false. There are multiple steps in this workflow that are stuck retrying forever.

A few other things to note:

  • I am using MySQL for persistence
  • Redis for locking
  • Cluster mode with 2 nodes

Initially digging into the code, I see that in order to suspend, it first tries to acquire a lock as follows:

public async Task<bool> SuspendWorkflow(string workflowId)
{
  if (!await _lockProvider.AcquireLock(workflowId, new CancellationToken()))
    return false;

Does this mean that a workflow cannot be suspended when any of the steps are currently running? What happens if there are several steps running at the same time that each retry and you cannot get into a state where there are no running steps?

created time in 14 days

issue commentdanielgerlag/workflow-core

StartTime Not Persisted on Workflow Steps in DB When Workflow Step Starts

I dug through the code a bit and looks like there is no call to the persistence provider to update the start time in the workflow executor after the step is initialized and the start time gets set.

dflor003

comment created time in 15 days

issue commentdanielgerlag/workflow-core

Question - How do you set the Description field

What is the reference field supposed to be used for?

dflor003

comment created time in 15 days

startedgregsramblings/google-cloud-4-words

started time in 16 days

issue commentdanielgerlag/workflow-core

StartTime Not Persisted on Workflow Steps in DB When Workflow Step Starts

Another solution I'm considering is leveraging ILifeCycleEventPublisher to listen to the events coming from it and then manually updating the records in the DB. Would you recommend that?

dflor003

comment created time in 16 days

fork dflor003/workflow-core

Lightweight workflow engine for .NET Standard

fork in 16 days

issue openeddanielgerlag/workflow-core

StartTime Not Persisted on Workflow Steps in DB When Workflow Step Starts

It looks like the StartTime field does not persisted on ExecutionPointer when a workflow step gets initialized. The field only gets persisted to the persistence provider when the workflow step finishes or has an error. Until one of those things happens, the StartTime field's value is only in memory.

This has the effect that if you are trying to build a UI to track long-running workflows, you can not tell how long a particular step has been running for purely using the DB model.

Is there any work around for this? Are there any hooks into the workflow process I can use to persist the workflow state? If not, could I put together a PR to persist ExecutionPointer.StartTime whenever an ExecutionPointer is initialized?

created time in 16 days

issue commentdanielgerlag/workflow-core

Question - How do you set the Description field

Yeah, the main goal for me would be to provide a user-friendly description of what the workflow instance is doing.

Ideally, these descriptions should be driven by the data passed into the workflow. For example, if I have a workflow that say provisions a virtual machine, I'd like to be able to setup the description on that workflow instance that says something to the tune of Provisioning VM 'MyVirtualMachine' based off the params to the workflow.

I've been able to work around this so far by introducing an interface called IDescriptiveWorkflowParams with a Description property that the workflow params can implement. I then do a cast to IDescriptiveWorkflowParams when retrieving the workflow instance and its params and then use that Description field if provided.

dflor003

comment created time in 16 days

issue openeddanielgerlag/workflow-core

Question - How do you set the Description field

Hi! This is more of a question than an actual issue.

I see that there is a Description field on both the WorkflowDefinition and WorkflowInstance class. I read through some of the code and it seems to pass the Description value from the definition over to the instance when you start a workflow, but I could not find any way to set the Description field on the WorkflowDefinition. There don't seem to be any docs on this either. Is there any way to set this field?

For context, I'm building a light UI on top of the workflows to provide an easier way of keeping track of the statuses of all workflows and would like to encode some user-friendly information into this field.

created time in 23 days

startedyotie/micron

started time in a month

push eventdflor003/stock-profit-tracker

DanilF

commit sha a489e66b70b43e0fde6a6f5b9af3d76ddb152474

- Added angular material boilerplate - Added ngrx - Added logger - Fixed tests - Wired up shell

view details

push time in 2 months

push eventdflor003/stock-profit-tracker

DanilF

commit sha 5409c59ae918219191374a892ca5f107c173dbfd

- Setup initial angular + electron boilerplate

view details

push time in 2 months

create barnchdflor003/stock-profit-tracker

branch : master

created branch time in 3 months

created repositorydflor003/stock-profit-tracker

Electron app to track stock wins & losses over time

created time in 3 months

more