I, on the Workflow-Core
Recent work has developed a workflow needs of their own based on object-oriented and duty chain fiddle for a small frame, and later found a lightweight workflow engine wheels on github: Workflow-Core, after reading their wiki I decided to give up before their own wheels made using this open source project to transform, and will have this blog post.
Workflow-Core-based .NET Standard is a lightweight workflow engine, its GitHub address: https://github.com/danielgerlag/workflow-core , there are currently more than 1200 + a star. It provides FluentAPI, multi-tasking, persistence, and parallel processing functions, suitable for small work flow, demand for the development of the chain of responsibility.
Since Workflow-Core support long-running workflow, so Workflow-Core support persistence of the plurality of data source format, may be implemented by installing a corresponding persistence Provider various packages:
- (Provided by default, for testing and development) memory
- MongoDB
- MS SQL Server
- MySql
- Sqlite
- Redis
- PostgreSQL
To get started immediately, Nuget install one, the latest version 2.0.0:
PM> Install-Package WorkflowCore
Second, the basic use of Workflow-Core
2.1 Hello World
Here we created a .NET Core console application, first a quick demonstration of Workflow-Core Hello World, show you how to start a Workflow:
(1) defines an interface implemented Workflow IWorkflow:
public class HelloWorldWorkflow : IWorkflow { public string Id => "HelloWorld"; public int Version => 1; public void Build(IWorkflowBuilder<object> builder) { builder .StartWith<HelloWorld>() .Then<ActiveWorld>() .Then<GoodbyeWorld>(); } }
This defines a HelloWorldWorkflow, its version number is 1, which has three steps: HelloWorld, ActiveWorld and GoodbyeWorld, will be followed by the implementation.
(2) defined three classes inherited from Step StepBody categories:
public class HelloWorld : StepBody { public override ExecutionResult Run(IStepExecutionContext context) { Console.WriteLine("Hello World!"); return ExecutionResult.Next(); } } public class ActiveWorld : StepBody { public override ExecutionResult Run(IStepExecutionContext context) { Console.WriteLine("I am activing in the World!"); return ExecutionResult.Next(); } } public class GoodbyeWorld : StepBody { public override ExecutionResult Run(IStepExecutionContext context) { Console.WriteLine("Goodbye World!"); return ExecutionResult.Next(); } }
(3) ServiceCollection injected associated components Workflow-Core
private static IServiceProvider ConfigureServices() { IServiceCollection services = new ServiceCollection(); services.AddLogging(); // WorkflowCore需要用到logging service services.AddWorkflow(); var serviceProvider = services.BuildServiceProvider(); return serviceProvider; }
(4) The method of obtaining the Main Program.cs injected into the host and execute the workflow
public static void Main(string[] args) { var serviceProvider = ConfigureServices(); var host = serviceProvider.GetService<IWorkflowHost>(); host.RegisterWorkflow<HelloWorldWorkflow>(); host.Start(); // Demo1:Hello World host.StartWorkflow("HelloWorld"); Console.ReadKey(); host.Stop(); }
Here is the incoming Workflow of Id, Workflow-Core will automatically go to match the latest version of the corresponding Workflow according to Id, results are as follows:
2.2 If statements
In workflow processing, often have a lot of determination conditions, then the Workflow-Core also provides a direct function If, as shown in the following IfStatementWorkflow:
public class IfStatementWorkflow : IWorkflow<MyData> { public string Id => "if-sample"; public int Version => 1; public void Build(IWorkflowBuilder<MyData> builder) { builder .StartWith<SayHello>() .If(data => data.Counter < 3).Do(then => then .StartWith<PrintMessage>() .Input(step => step.Message, data => "Outcome is less than 3") ) .If(data => data.Counter < 5).Do(then => then .StartWith<PrintMessage>() .Input(step => step.Message, data => "Outcome is less than 5") ) .Then<SayGoodbye>(); } }
The incoming transmission MyData defined as follows:
public class MyData { public int Counter { get; set; } }
When passed in the Counter property MyData <3 or <5:00 branches have different processing logic.
2.3 MySQL persistence support
Want workflow is configured to persistent MySQL, just follow these two steps:
(1) mounted by MySQL Provider package Nuget:
PM> Install-Package WorkflowCore.Persistence.MySQL
(2) into ServiceCollection
services.AddWorkflow(x => x.UseMySQL(@"Server=127.0.0.1;Database=workflow;User=root;Password=password;", true, true));
Once started, you will find Workflow-Core automatically help you create and configure many examples of tables for persisting workflow.
2.4 Scheduled Tasks and cyclic tasks
Workflow-Core also features an integrated scheduling, and cyclic tasks:
(1) Program Tasks: a delay of 5 minutes is provided such program tasks performed in the workflow step
builder .StartWith(context => Console.WriteLine("Hello")) .Schedule(data => TimeSpan.FromSeconds(5)).Do(schedule => schedule .StartWith(context => Console.WriteLine("Doing scheduled tasks")) ) .Then(context => Console.WriteLine("Doing normal tasks"));
(2) Cyclic task: setting such a delay of 5 minute cycle tasks in a workflow step, known Counter> 5 until the end
builder .StartWith(context => Console.WriteLine("Hello")) .Recur(data => TimeSpan.FromSeconds(5), data => data.Counter > 5).Do(recur => recur .StartWith(context => Console.WriteLine("Doing recurring task")) ) .Then(context => Console.WriteLine("Carry on"));
2.5 Saga support
Learn distributed transaction program shoes should know Saga, the Workflow-Core also has support, which is a very useful feature:
(1) For example: After creating a customer information, and pushed to Salesforce ERP, if an error occurs during the push, then be compensated by the retry, and the retry time interval.
builder .StartWith<CreateCustomer>() .Then<PushToSalesforce>() .OnError(WorkflowErrorHandling.Retry, TimeSpan.FromMinutes(10)) .Then<PushToERP>() .OnError(WorkflowErrorHandling.Retry, TimeSpan.FromMinutes(10));
(2) Another example: When Task2 abnormal, Workflow-Core will help implement UndoTask2 and UndoTask1 help you to rewind data to restore the state.
builder .StartWith<LogStart>() .Saga(saga => saga .StartWith<Task1>() .CompensateWith<UndoTask1>() .Then<Task2>() .CompensateWith<UndoTask2>() .Then<Task3>() .CompensateWith<UndoTask3>() ) .OnError(Models.WorkflowErrorHandling.Retry, TimeSpan.FromMinutes(10)) .Then<LogEnd>();
More Saga example, refer to: https://github.com/danielgerlag/workflow-core/tree/master/src/samples/WorkflowCore.Sample17
Third, the use in ASP.NET Core Workflow-Core in
3.1 injection and initialization
(1) Injection: Use AddWorkflow () Extension Method
public void ConfigureServices(IServiceCollection services) { services.AddWorkflow(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
(2) Initialization:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ....... app.UseWorkflow(); }
Extended follows:
public static class ConfigureExtensions { public static IApplicationBuilder UseWorkflow(this IApplicationBuilder app) { var host = app.ApplicationServices.GetService<IWorkflowHost>(); host.RegisterWorkflow<EdcWorkflow>(); host.RegisterWorkflow<EdcDataWorkflow, EdcData>(); host.Start(); var appLifetime = app.ApplicationServices.GetService<IApplicationLifetime>(); appLifetime.ApplicationStopping.Register(() => { host.Stop(); }); return app; } }
It should be noted that: you will have to use all the Workflow Register in advance to register.
3.2 DI acquired through use
Where you want to use, either Controller or Service, via dependency injection to obtain Host, and use it:
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private IWorkflowController _workflowService; public ValuesController(IWorkflowController workflowService) { _workflowService = workflowService; } // GET api/values [HttpGet] public async Task<IEnumerable<string>> Get() { await _workflowService.StartWorkflow("EdcWorkflow"); return new string[] { "EdcWorkflow v1" }; } // GET api/values/5 [HttpGet("{id}")] public async Task<string> Get(int id) { await _workflowService.StartWorkflow("EdcDataWorkflow", new EdcData() { Id = id }); return "EdcDataWorkflow v1"; } }
Workflow these two definitions are as follows:
public class EdcWorkflow : IWorkflow { public string Id => "EdcWorkflow"; public int Version => 1; public void Build(IWorkflowBuilder<object> builder) { builder .StartWith<HelloWorld>() .Then<GoodbyeWorld>(); } } public class EdcDataWorkflow : IWorkflow<EdcData> { public string Id => "EdcDataWorkflow"; public int Version => 1; public void Build(IWorkflowBuilder<EdcData> builder) { builder .StartWith<HelloWorld>() .If(data => data.Id < 3).Do(then => then .StartWith<PrintMessage>() .Input(step => step.Message, data => "Passed Id is less than 3") ) .If(data => data.Id < 5).Do(then => then .StartWith<PrintMessage>() .Input(step => step.Message, data => "Passed Id is less than 5") ) .Then<GoodbyeWorld>(); } }
Examples of results is simple:
(1)api/values
(2)api/values/1
IV Summary
Workflow-Core is an excellent fit .NET Core lightweight workflow engine, workflow demand for the development of small and very suitable type of chain of responsibility, can save a lot of time to avoid repeating create the wheel, the time is mainly spent on business logic above . Of course, the example demonstrated here just a few of the features in, I just selected some I used it, we are interested, then you can give a star re-examine its documentation wiki on GitHub go to their application project.
Sample Code: https://github.com/EdisonChou/EDC.WorkflowCore.Sample