Skip to content

Commit 499abf9

Browse files
committed
bump version, release notes
1 parent 32131e1 commit 499abf9

File tree

3 files changed

+289
-5
lines changed

3 files changed

+289
-5
lines changed

ReleaseNotes/3.3.0.md

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
# Workflow Core 3.3.0
2+
3+
# Workflow Middleware
4+
5+
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](https://www.frakkingsweet.com/net-core-log-correlation-easy-access-to-headers/), [retries](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-http-call-retries-exponential-backoff-polly), and other use-cases.
6+
7+
This is done by implementing and registering `IWorkflowMiddleware` for workflows or `IWorkflowStepMiddleware` for steps.
8+
9+
## Step Middleware
10+
11+
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](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1) or [`HttpClient`'s `DelegatingHandler` middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1#outgoing-request-middleware).
12+
13+
### Usage
14+
15+
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.
16+
17+
**Important:** You must make sure to call `next()` as part of your middleware. If you do not do this, your step will never run.
18+
19+
```cs
20+
public class LogCorrelationStepMiddleware : IWorkflowStepMiddleware
21+
{
22+
private readonly ILogger<LogCorrelationStepMiddleware> _log;
23+
24+
public LogCorrelationStepMiddleware(
25+
ILogger<LogCorrelationStepMiddleware> log)
26+
{
27+
_log = log;
28+
}
29+
30+
public async Task<ExecutionResult> HandleAsync(
31+
IStepExecutionContext context,
32+
IStepBody body,
33+
WorkflowStepDelegate next)
34+
{
35+
var workflowId = context.Workflow.Id;
36+
var stepId = context.Step.Id;
37+
38+
// Uses log scope to add a few attributes to the scope
39+
using (_log.BeginScope("{@WorkflowId}", workflowId))
40+
using (_log.BeginScope("{@StepId}", stepId))
41+
{
42+
// Calling next ensures step gets executed
43+
return await next();
44+
}
45+
}
46+
}
47+
```
48+
49+
Here's another example of a middleware that uses the [Polly](https://github.com/App-vNext/Polly) dotnet resiliency library to implement retries on workflow steps based off a custom retry policy.
50+
51+
```cs
52+
public class PollyRetryStepMiddleware : IWorkflowStepMiddleware
53+
{
54+
private const string StepContextKey = "WorkflowStepContext";
55+
private const int MaxRetries = 3;
56+
private readonly ILogger<PollyRetryStepMiddleware> _log;
57+
58+
public PollyRetryMiddleware(ILogger<PollyRetryStepMiddleware> log)
59+
{
60+
_log = log;
61+
}
62+
63+
// Consult Polly's docs for more information on how to build
64+
// retry policies:
65+
// https://github.com/App-vNext/Polly
66+
public IAsyncPolicy<ExecutionResult> GetRetryPolicy() =>
67+
Policy<ExecutionResult>
68+
.Handle<TimeoutException>()
69+
.RetryAsync(
70+
MaxRetries,
71+
(result, retryCount, context) =>
72+
UpdateRetryCount(
73+
result.Exception,
74+
retryCount,
75+
context[StepContextKey] as IStepExecutionContext)
76+
);
77+
78+
public async Task<ExecutionResult> HandleAsync(
79+
IStepExecutionContext context,
80+
IStepBody body,
81+
WorkflowStepDelegate next
82+
)
83+
{
84+
return await GetRetryPolicy().ExecuteAsync(
85+
ctx => next(),
86+
// The step execution context gets passed down so that
87+
// the step is accessible within the retry policy
88+
new Dictionary<string, object>
89+
{
90+
{ StepContextKey, context }
91+
});
92+
}
93+
94+
private Task UpdateRetryCount(
95+
Exception exception,
96+
int retryCount,
97+
IStepExecutionContext stepContext)
98+
{
99+
var stepInstance = stepContext.ExecutionPointer;
100+
stepInstance.RetryCount = retryCount;
101+
return Task.CompletedTask;
102+
}
103+
}
104+
```
105+
106+
## Pre/Post Workflow Middleware
107+
108+
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.
109+
110+
### Pre Workflow Middleware
111+
112+
These middleware get run before the workflow is started and can potentially alter properties on the `WorkflowInstance`.
113+
114+
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.
115+
116+
Note that you use `WorkflowMiddlewarePhase.PreWorkflow` to specify that it runs before the workflow starts.
117+
118+
**Important:** You should call `next` as part of the workflow middleware to ensure that the next workflow in the chain runs.
119+
120+
```cs
121+
// AddDescriptionWorkflowMiddleware.cs
122+
public class AddDescriptionWorkflowMiddleware : IWorkflowMiddleware
123+
{
124+
public WorkflowMiddlewarePhase Phase =>
125+
WorkflowMiddlewarePhase.PreWorkflow;
126+
127+
public Task HandleAsync(
128+
WorkflowInstance workflow,
129+
WorkflowDelegate next
130+
)
131+
{
132+
if (workflow.Data is IDescriptiveWorkflowParams descriptiveParams)
133+
{
134+
workflow.Description = descriptiveParams.Description;
135+
}
136+
137+
return next();
138+
}
139+
}
140+
141+
// IDescriptiveWorkflowParams.cs
142+
public interface IDescriptiveWorkflowParams
143+
{
144+
string Description { get; }
145+
}
146+
147+
// MyWorkflowParams.cs
148+
public MyWorkflowParams : IDescriptiveWorkflowParams
149+
{
150+
public string Description => $"Run task '{TaskName}'";
151+
152+
public string TaskName { get; set; }
153+
}
154+
```
155+
156+
### Exception Handling in Pre Workflow Middleware
157+
158+
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.
159+
160+
```cs
161+
public async Task MyMethodThatStartsAWorkflow()
162+
{
163+
try
164+
{
165+
await host.StartWorkflow("HelloWorld", 1, null);
166+
}
167+
catch(Exception ex)
168+
{
169+
// Handle the exception appropriately
170+
}
171+
}
172+
```
173+
174+
### Post Workflow Middleware
175+
176+
These middleware get run after the workflow has completed and can be used to perform additional actions for all workflows in your app.
177+
178+
The following example illustrates how you can use a post workflow middleware to print a summary of the workflow to console.
179+
180+
Note that you use `WorkflowMiddlewarePhase.PostWorkflow` to specify that it runs after the workflow completes.
181+
182+
**Important:** You should call `next` as part of the workflow middleware to ensure that the next workflow in the chain runs.
183+
184+
```cs
185+
public class PrintWorkflowSummaryMiddleware : IWorkflowMiddleware
186+
{
187+
private readonly ILogger<PrintWorkflowSummaryMiddleware> _log;
188+
189+
public PrintWorkflowSummaryMiddleware(
190+
ILogger<PrintWorkflowSummaryMiddleware> log
191+
)
192+
{
193+
_log = log;
194+
}
195+
196+
public WorkflowMiddlewarePhase Phase =>
197+
WorkflowMiddlewarePhase.PostWorkflow;
198+
199+
public Task HandleAsync(
200+
WorkflowInstance workflow,
201+
WorkflowDelegate next
202+
)
203+
{
204+
if (!workflow.CompleteTime.HasValue)
205+
{
206+
return next();
207+
}
208+
209+
var duration = workflow.CompleteTime.Value - workflow.CreateTime;
210+
_log.LogInformation($@"Workflow {workflow.Description} completed in {duration:g}");
211+
212+
foreach (var step in workflow.ExecutionPointers)
213+
{
214+
var stepName = step.StepName;
215+
var stepDuration = (step.EndTime - step.StartTime) ?? TimeSpan.Zero;
216+
_log.LogInformation($" - Step {stepName} completed in {stepDuration:g}");
217+
}
218+
219+
return next();
220+
}
221+
}
222+
```
223+
224+
### Exception Handling in Post Workflow Middleware
225+
226+
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.
227+
228+
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.
229+
230+
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.
231+
232+
```cs
233+
// CustomMiddlewareErrorHandler.cs
234+
public class CustomHandler : IWorkflowMiddlewareErrorHandler
235+
{
236+
public Task HandleAsync(Exception ex)
237+
{
238+
// Handle your error asynchronously
239+
}
240+
}
241+
242+
// Startup.cs
243+
public void ConfigureServices(IServiceCollection services)
244+
{
245+
// Other workflow configuration
246+
services.AddWorkflow();
247+
248+
// Should go after .AddWorkflow()
249+
services.AddTransient<IWorkflowMiddlewareErrorHandler, CustomHandler>();
250+
}
251+
```
252+
253+
## Registering Middleware
254+
255+
In order for middleware to take effect, they must be registered with the built-in dependency injection framework using the convenience helpers.
256+
257+
**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.
258+
259+
```cs
260+
public class Startup
261+
{
262+
public void ConfigureServices(IServiceCollection services)
263+
{
264+
...
265+
266+
// Add workflow middleware
267+
services.AddWorkflowMiddleware<AddDescriptionWorkflowMiddleware>();
268+
services.AddWorkflowMiddleware<PrintWorkflowSummaryMiddleware>();
269+
270+
// Add step middleware
271+
services.AddWorkflowStepMiddleware<LogCorrelationStepMiddleware>();
272+
services.AddWorkflowStepMiddleware<PollyRetryMiddleware>();
273+
274+
...
275+
}
276+
}
277+
```
278+
279+
## More Information
280+
281+
See the [Workflow Middleware](https://github.com/danielgerlag/workflow-core/tree/master/src/samples/WorkflowCore.Sample19) sample for full examples of workflow middleware in action.
282+
283+
284+
Many thanks to Danil Flores @dflor003 for this contribution!

src/WorkflowCore.DSL/WorkflowCore.DSL.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5-
<Version>3.1.5</Version>
5+
<Version>3.3.0</Version>
66
<Description>DSL extenstion for Workflow Core provding support for JSON and YAML workflow definitions.</Description>
77
<Authors>Daniel Gerlag</Authors>
88
<Company />

src/WorkflowCore/WorkflowCore.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
1616
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
1717
<Description>Workflow Core is a light weight workflow engine targeting .NET Standard.</Description>
18-
<Version>3.2.6</Version>
19-
<AssemblyVersion>3.2.6.0</AssemblyVersion>
20-
<FileVersion>3.2.6.0</FileVersion>
18+
<Version>3.3.0</Version>
19+
<AssemblyVersion>3.3.0.0</AssemblyVersion>
20+
<FileVersion>3.3.0.0</FileVersion>
2121
<PackageReleaseNotes></PackageReleaseNotes>
2222
<PackageIconUrl>https://github.com/danielgerlag/workflow-core/raw/master/src/logo.png</PackageIconUrl>
23-
<PackageVersion>3.2.6</PackageVersion>
23+
<PackageVersion>3.3.0</PackageVersion>
2424
</PropertyGroup>
2525

2626
<ItemGroup>

0 commit comments

Comments
 (0)