{"id":23905524,"url":"https://github.com/aerosonik/executionpipe","last_synced_at":"2025-04-11T03:21:00.166Z","repository":{"id":143217647,"uuid":"179488654","full_name":"aerosonik/ExecutionPipe","owner":"aerosonik","description":"Lightweight library with base abstractions and implementations for creating custom pipe of sequential or parallel  execution.","archived":false,"fork":false,"pushed_at":"2020-11-10T13:41:29.000Z","size":172,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-07T13:17:58.990Z","etag":null,"topics":["architecture","csharp","execution","execution-statistics","executor","executor-service","microservices-architecture","pipeline","solid"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aerosonik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-04T11:59:22.000Z","updated_at":"2020-12-22T22:46:52.000Z","dependencies_parsed_at":"2023-04-18T18:00:39.288Z","dependency_job_id":null,"html_url":"https://github.com/aerosonik/ExecutionPipe","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerosonik%2FExecutionPipe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerosonik%2FExecutionPipe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerosonik%2FExecutionPipe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aerosonik%2FExecutionPipe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aerosonik","download_url":"https://codeload.github.com/aerosonik/ExecutionPipe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248334043,"owners_count":21086305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","csharp","execution","execution-statistics","executor","executor-service","microservices-architecture","pipeline","solid"],"created_at":"2025-01-05T01:14:48.253Z","updated_at":"2025-04-11T03:21:00.147Z","avatar_url":"https://github.com/aerosonik.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://raw.githubusercontent.com/aerosonik/ValidationPipe/f5997cdfaff661d36939c45823e93bb613a3767d/icon.png\" alt=\"fo-dicom logo\" height=\"80\" /\u003e\n\n# ExecutionPipe\nLightweight library with base abstractions and implementations for creating custom pipe of sequential or parallel  excecutions.\n\n[![NuGet](https://img.shields.io/nuget/v/NSV.ExecutionPipe.svg)](https://www.nuget.org/packages/NSV.ExecutionPipe) \n[![Build status](https://ci.appveyor.com/api/projects/status/r3yptmhufh3dl1xc?svg=true)](https://ci.appveyor.com/project/aerosonik/validationpipe)\n\n### Installation\n\nGet latest stable version from [NuGet](https://www.nuget.org/packages/NSV.ExecutionPipe/).\n\n## General purpose\n* Use it as part of application architecture.\n* Use it to get more control of your business process implementations, instead of creating set of methods and complex logic of theirs interactions use this library and receive simple and understandable pipe that fully describes your process.\n* Decrease complexity of business logic implementations in case when application has logic layer which consists of many sub layers that eventually becomes into unmanageable code. It's easy to put many parts from differnt layers of one logical chain into one line (but it can be a tree).\n* It's fully match to S.O.L.I.D., D.R.Y. and K.I.S.S. principles\n* Design Patterns - it use \"Strategy\" pattern to describe chain of executors, and custom implementation of \"Builder\" pattern.\n\n\n## Main features\n* Class library with completed set of abstractions\n* Targets .NET Standard 2.0 and higher\n* Whole syntax is FLUENT\n* High-performance, fully asynchronous `async`/`await` API\n* Execute `Pipe` sequential or able to execute parallel\n* Full control of execution process\n* Embeded StopWatch for whole Pipe and for each Executor\n* Shared threadsafe local cache for `Pipe` and included Executors and Pipes\n* `Pipe` consists of executors\n* `Pipe` can contain nested `Pipe` (Pipe implement `IExecutor)\n* `Pipe` supports nested pipes and nested `Pipe` also supports nested pipes\n* `Executor` can be marked by `Label` for distinguish set of result\n\n## Memory optimized usage:\n`Pipe` based on queue data structure, each executor will be removed from queue after invocation, it's a good chance to let objects die in zero generation.\n\n### Base abstractions :\n\n#### Pipe\u003cM, R\u003e\nBase Pipe abstraction, implement it to create your own Pipe of execution. It will make sense if you set up pipe by using embedded functionality, adding executors, setting execution mode.\n\nM - model, R - result\n\nMembers:\n*   `AsParallel()`  - all executors in `Pipe` will execute in parallel mode\n*   `AsSequential()` - default behaviour, `Pipe` will execute all executors step by step\n*   `UseStopWatch()` - use for measure execution `Pipe` time, see result in `Elapsed` property\n*   `UseLocalCache()` - initialised local cache, for using by all `Pipe` members (executors, pipes)\n*   `UseLocalCacheThreadSafe()` - the same, but it can be used in parallel mode\n*   `If(bool condition)` - if condition is True, executors between `If(..)` and `EndIf()` will be added to pipe, otherwise they will not be added.\n*   `If(Func\u003cM, bool\u003e condition)` - this mean : if \"condition\" returns True,  executors between `If(..)` and `EndIf()` will be invoceted.\n*   `EndIf()` - this operator close the \"If\" section. (every If should be ended with \"EndIf\") \n*   `Execute(M model)` - use it in case when all executors are sync\n*   `ExecuteAsync(M model)` - use it in case when some of executors are async, you can mix sync and async executors\n*   `Finish()` - use to finish customising your `Pipe`\n*   `CreateResult(M model, PipeResult\u003cR\u003e[] results)` - implelemt it to handle all results from all executors, this method will called in end of execution chain\n\n##### ISequentialPipe\u003cM, R\u003e, IParallelPipe\u003cM, R\u003e\nPipe implement ISequentialPipe\u003cM, R\u003e, IParallelPipe\u003cM, R\u003e\n\nMethod `Pipe.AsSequential()` return `ISequentialPipe\u003cM, R\u003e`, use to setup sequence of executors\n\nMethod `Pipe.AsParallel()` return `IParallelPipe\u003cM, R\u003e`, use to sutup parallel execution of executors\n\n*   `AddExecutor(IExecutor\u003cM, R\u003e executor)` - add current executor (implementation of abstract class `Executor`)\n*   `AddExecutor(Func\u003cIExecutor\u003cM, R\u003e\u003e executor)` - add executor - use it for lazy initialization of executor, the executor will be initialized just before invocation of `Execute(..)` or `ExrcuteAsync(..)`methods.\n*   `SetSkipIf(Func\u003cM, bool\u003e condition)` - if result of condition is true, executor will be skipped\n*   `SetBreakIfFailed()` - use to break sequence of execution if current executor returns failed result (only in `ISequentialPipe\u003cM, R\u003e`)\n*   `SetAllowBreak()` - use it if you need to break sequence on current `Executor` which simultaneously returned successful result and `Break` marker (only in `ISequentialPipe\u003cM, R\u003e`)\n*   `SetResultHandler(Func\u003cM, PipeResult\u003cR\u003e, PipeResult\u003cR\u003e\u003e handler)` - use this method to handle result from current executor in case when sequence will break after it (only in `ISequentialPipe\u003cM, R\u003e`)\n*   `SetUseStopWatch()` - use it to determine that the current executor will count it's own time of execution\n*   `SetLabel(string label)` - set label to differ results\n*   `SetRetryIfFailed(int count, int timeOutMilliseconds)` - execution of current executor can be repeated `count`-times, in timeOutMilliseconds each time (only in `ISequentialPipe\u003cM, R\u003e`)\n\n#### Executor\nAbstract class, implement this class to create your own `Executor`\n*   `Execute(M model)` - abstract method, to use `Executor` in `Pipe` implement it\n*   `ExecuteAsync(M model)` - abstract method, to use `Executor` in `Pipe` implement it, in case your execution is asynchronious \n*   `IsAsync` - by default is `true`, and `ExecuteAsync(M model)` method will be executed, if you implement synchronious method set  `IsAsync` to `false`\n\n## Usage\nFor example, consider process that consists of several simple stages where every next stage depends on result of executed previous stage, let's implement Executor for each stage, and then  implement `Pipe` and put all executors into sequence of this pipe.\n\n#### Model and Result\n```csharp\npublic class ProcessModel //input model\n{\n  public int Id { get; set; }\n  public string Text { get; set; }\n}\n\npublic class ProcessResult\n{\n  public string ResultField { get; set; }\n}\n```\n#### Executors\nUsing `ProcessModel` and `ProcessResult` as generics arguments.\n`Executor` is base work unit in pipe, all units use the same type of model and same instance of model object, they return the same type of results, this result `ProcessResult` will be returned after execution as `Value` of struct `PipeResult\u003cR\u003e`.\n```csharp\npublic class ProcessExecutor1 : Executor\u003cProcessModel, ProcessResult\u003e\n{\n  public ProcessExecutor1()\n  {\n    IsAsync = false;  // by default is True\n  }\n  // implemented, because IsAsync is false\n  public override PipeResult\u003cProcessResult\u003e Execute(ProcessModel model)\n  {\n    Thread.Sleep(2000); // imitation of work\n    return PipeResult\u003cProcessResult\u003e // return result\n    .DefaultSuccessful // helper of structure initialization\n    .SetValue(new ProcessResult { ResultField = \"First result\" }); // The value result\n  }\n  public override Task\u003cPipeResult\u003cProcessResult\u003e\u003e ExecuteAsync(ProcessModel model)\n  {\n    throw new NotImplementedException();\n  }\n}\n\npublic class ProcessExecutor2 : Executor\u003cProcessModel, ProcessResult\u003e\n{\n  // IsAsync = true; can be simplified\n  public override PipeResult\u003cProcessResult\u003e Execute(ProcessModel model)\n  {\n    throw new NotImplementedException();\n  }\n  // implemented, because IsAsync is true, default behaviour\n  public override async Task\u003cPipeResult\u003cProcessResult\u003e\u003e ExecuteAsync(ProcessModel model)\n  {\n    await Task.Delay(3000); // imitation of work\n    return PipeResult\u003cProcessResult\u003e\n      .DefaultUnSuccessful // helper of structure initialization\n      .SetValue(new ProcessResult { ResultField = \"Second result\" });\n  }\n}\n```\n#### Pipes\nSequential execution\n```csharp\npublic class ProcessPipe : Pipe\u003cProcessModel, ProcessResult\u003e\n{\n  public ProcessPipe(ProcessExecutor1 executor1, \n     ProcessExecutor2 executor2, \n     ProcessExecutor3 executor3, \n     ProcessSubPipe subPipe) // ProcessSubPipe - should be inherited from \"Pipe\u003cProcessModel, ProcessResult\u003e\"\n  {\n    UseLocalCache() // \n    .UseStopWatch() // get info about pipe execution time\n    .AsSequential() // all executors on this level will executed sequentially, it doesn't affect on any included subpipes\n    .AddExecutor(executor1) //add executor to pipe, first in invocation queue\n      .SetLabel(\"Label of ProcessExecutor1\") // any string\n      .SetBreakIfFailed() // allow break sequense of executors if this one failed\n      .SetResultHandler((model, result) =\u003e { return model.Text != \"Text\" ? result : result.SetError(\"Text is default\"); }) // quit without invocation method 'CreateResult'\n      .SetSkipIf(m =\u003e m.Id == 0) // this executor (executor1) willnot be executed if condition is true\n    .AddExecutor(subPipe)// just sub pipe with same generic args, can contain any executors of the same generics args\n      .SetSkipIf(m =\u003e m.Id \u003c= 1) // will be executed if 'Id'  greater than 1\n    .AddExecutor(executor2) //add executor to pipe, second in invocation queue\n      .SetLabel(\"Label of ProcessExecutor2\") // any string\n      .SetRetryIfFailed(3, 1000) // retry invoke 'executor2' 3 times with 1 second delay betwen attempts\n      .SetAllowBreak() // allow break when result is successful anf flag 'Break' is true\n    .AddExecutor(executor3) //add executor to pipe, third in invocation queue\n      .SetLabel(\"ProcessExecutor3\") // any string\n      .SetUseStopWatch() // get info about invocation time, returned in result\n    .Finish();\n  }\n\n  public override PipeResult\u003cProcessResult\u003e CreateResult(ProcessModel model, PipeResult\u003cProcessResult\u003e[] results)\n  {\n    var pipeTime = Elapsed; // // stopwatch result on execution of this pipe, setuped by '.UseStopWatch()'\n    var time = results.FirstOrDefault(x =\u003e x.Label == \"ProcessExecutor3\").Elapsed; // stopwatch result on invocation of 'executor3', setuped by '.SetUseStopWatch()'\n    switch (results.AllSuccess())\n    {\n      case ExecutionResult.Successful:\n        return PipeResult\u003cProcessResult\u003e.DefaultSuccessful;\n      case ExecutionResult.Failed:\n        return PipeResult\u003cProcessResult\u003e.DefaultUnSuccessful;\n      default:\n        return PipeResult\u003cProcessResult\u003e.Default;\n    }\n  }\n}\n```\nParallel execution\n```csharp\npublic class ProcessPipe : Pipe\u003cProcessModel, ProcessResult\u003e\n{\n  public ProcessPipe(ProcessExecutor1 executor1, \n     ProcessExecutor2 executor2, \n     ProcessExecutor3 executor3, \n     ProcessSubPipe subPipe)\n  {\n    UseLocalCache() // \n    .UseStopWatch() // get info about pipe execution time\n    .AsParallel() // all executors on this level will executed parallel, it doesn't affect on any included subpipes\n    .AddExecutor(executor1) //add executor to pipe\n      .SetLabel(\"Label of ProcessExecutor1\") // any string\n      .SetSkipIf(m =\u003e m.Id == 0) // this executor (executor1) willnot be executed if condition is true\n    .AddExecutor(subPipe)// this is sub pipe\n      .SetSkipIf(m =\u003e m.Id \u003c= 0),\n    .AddExecutor(executor2) //add executor to pipe\n      .SetLabel(\"Label of ProcessExecutor2\") // any string\n    .AddExecutor(executor3) //add executor to pipe\n      .SetLabel(\"ProcessExecutor3\") // any string\n      .SetUseStopWatch() // get info about invocation time, returned in result\n    .Finish();\n  }\n  public override PipeResult\u003cProcessResult\u003e CreateResult(ProcessModel model, PipeResult\u003cProcessResult\u003e[] results)\n  {\n    // aggregate results here\n  }\n}\n```\nRun pipe\n```csharp\npublic static void Main()\n{\n  var model = new ProcessModel { Id = 2, Text = \"any text\" };// add input data model\n  var pipe = new ProcessPipe(\n    new ProcessExecutor1(), \n    new ProcessExecutor2(), \n    new ProcessExecutor3(), \n    new ProcessSubPipe()); // create pipe, better to use DI \n  var result = pipe\n    .Execute(model); // run baby run!!!\n}\n```\nOr\n```csharp\npublic static async Task Main()\n{\n  var model = new ProcessModel { Id = 2, Text = \"any text\" };\n  var pipe = new ProcessPipe(.....);\n  var result = await pipe\n    .ExecuteAsync(model); // run asynchronously\n}\n```\nUse Dependency injection\n```csharp\npublic class Process\n{\n   private Pipe\u003cProcessModel,ProcessResult\u003e _pipe;\n   public Process(Pipe\u003cProcessModel,ProcessResult\u003e pipe,\n      ProcessExecutor1 exec1,\n      ProcessExecutor2 exec2,\n      ProcessExecutor3 exec3,\n      ProcessExecutor4 exec4)\n   {\n      _pipe = pipe\n         .UseLocalCache()\n         .UseStopWatch()\n         .AsSequential()\n         .AddExecutor(exec1)\n         .AddExecutor(exec2)\n         .AddExecutor(exec3)\n         .AddExecutor(exec4)\n         .Finish();\n   }\n   public bool Run()\n   {\n      var model = new ProcessModel { Id = 2, Text = \"any text\" };\n      var result = _pipe.Execute(model);\n      if(result.Success == ExecutionResult.Successful)\n         return true;\n      return false;\n   }\n}\n```\nLazy initialization, each executor will be created only before invocation\n```csharp\npublic class Process\n{\n   private Pipe\u003cProcessModel,ProcessResult\u003e _pipe;\n   public Process(Pipe\u003cProcessModel,ProcessResult\u003e pipe,\n      Func\u003cProcessExecutor1\u003e exec1,\n      Func\u003cProcessExecutor2\u003e exec2,\n      Func\u003cProcessExecutor3\u003e exec3,\n      Func\u003cProcessExecutor4\u003e exec4)\n   {\n      _pipe = pipe\n         .UseLocalCache()\n         .UseStopWatch()\n         .AsSequential()\n         .AddExecutor(exec1)\n         .AddExecutor(exec2)\n         .AddExecutor(exec3)\n         .AddExecutor(exec4)\n         .Finish();\n   }\n   public bool Run()\n   {\n      var model = new ProcessModel { Id = 2, Text = \"any text\" };\n      var result = _pipe.Execute(model);\n      if(result.Success == ExecutionResult.Successful)\n         return true;\n      return false;\n   }\n}\n```\n\n\n### Structure `PipeResult\u003cR\u003e`\nIt is a pipe result and result returned by all executors in pipe.\n\nProperties:\n\n* `Success` - show result, can be `Initial` - executor/pipe wasn't executed, `Successful` - succsess, `Failed` - error, fail, something went wrong.\n* `Errors` - contains `string[]` for errors.\n* `Exceptions` - contains `Exception[]` for exceptions.\n* `Value` - result value of \u003c`R`\u003e type, place here result object.\n* `Elapsed` - time of execution\n* `Label` - string label\n* `Break` - set true, if You need to break sequence, use it with `SetAllowBreak()`\n\nStatic getters:\n\n* `Default` - returns default new value of structure (initial state)\n```csharp\nreturn new PipeResult\u003cT\u003e\n{\n  Value = Optional\u003cT\u003e.Default,\n  Break = false,\n  Errors = Optional\u003cstring[]\u003e.Default,\n  Exceptions = Optional\u003cException[]\u003e.Default,\n  Success = ExecutionResult.Initial\n};\n```\n* `DefaultSuccessful` - returns default value, with `Success = ExecutionResult.Successful`\n* `DefaultSuccessfulBreak` - returns default value, with `Success = ExecutionResult.Successful` and `Break = true`\n* `DefaultUnSuccessful` - returns default value, with `Success = ExecutionResult.Failed` and `Break = false`\n* `DefaultUnSuccessfulBreak` - returns default value, with `Success = ExecutionResult.Failed` and `Break = true`\n\nFluent Methods:\n\nAll these methods return type `PipeResult\u003cR\u003e`, for fluent syntax usage\n* `SetValue(T value)` - set `Value` property\n* `SetBreak(bool isbreak)` - set `Break` property, use it with `SetAllowBreak()`\n* `SetErrors(string[] errors)` - set `Errors` property\n* `SetError(string error)` - set `Errors` property with singl error\n* `SetException(Exception[] exceptions)` - set property `Exceptions`\n* `SetException(Exception exception)` - set property `Exceptions` with singl exception\n* `SetSuccessful()` - set property `Success = ExecutionResult.Successful`\n* `SetUnSuccessful()` - set property `Success = ExecutionResult.Failed`\n* `SetElapsed(TimeSpan span)` - set property  `Elapsed`\n* `SetLabel(string label)` - set property `Label`\n\nExtensions : \n* `string[] AllErrors\u003cT\u003e(this PipeResult\u003cT\u003e[] results)` - returns list of errors messages from failed results\n* `Exception[] AllExceptions\u003cT\u003e(this PipeResult\u003cT\u003e[] results)` - return list of exeptions from failed results\n* `ExecutionResult AllSuccess\u003cT\u003e(this PipeResult\u003cT\u003e[] results)` - return `Success` if all results are successful, `Initial` if all are initial, `Failed` in any other case\n* `ExecutionResult AllExecutedSuccess\u003cT\u003e(this PipeResult\u003cT\u003e[] results)` - return `Success` if all results are successful, `Initial` if all are initial, `Failed` in any other case. Method handle only results which are not `Initial`\n* `ExecutionResult AnySuccess\u003cT\u003e(this PipeResult\u003cT\u003e[] results)` - return `Success` if any of results is `Success`, if all are in initial state will returned `Initial`, `Failed` in any other case\n* `bool IsAllSuccess\u003cT\u003e(this PipeResult\u003cT\u003e[] results)` - return `Success` if all results are in `Success` state\n* `bool IsAllExecutedSuccess\u003cT\u003e(this PipeResult\u003cT\u003e[] results)` - return `Success` if all results are in `Success` state, except results wich are in `Initial` state\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faerosonik%2Fexecutionpipe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faerosonik%2Fexecutionpipe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faerosonik%2Fexecutionpipe/lists"}