{"id":17081510,"url":"https://github.com/benfoster/flo","last_synced_at":"2025-04-12T21:07:19.018Z","repository":{"id":65414082,"uuid":"107595334","full_name":"benfoster/Flo","owner":"benfoster","description":"Lightweight library for composing chain of responsibility pipelines","archived":false,"fork":false,"pushed_at":"2019-11-22T14:02:24.000Z","size":70,"stargazers_count":83,"open_issues_count":4,"forks_count":12,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-12T21:07:12.513Z","etag":null,"topics":["asp-net","asp-net-core","csharp","flow-based-programming","pipeline-framework"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benfoster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-19T20:29:14.000Z","updated_at":"2024-10-06T17:15:06.000Z","dependencies_parsed_at":"2023-01-23T10:55:07.669Z","dependency_job_id":null,"html_url":"https://github.com/benfoster/Flo","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2FFlo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2FFlo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2FFlo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2FFlo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benfoster","download_url":"https://codeload.github.com/benfoster/Flo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631677,"owners_count":21136562,"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":["asp-net","asp-net-core","csharp","flow-based-programming","pipeline-framework"],"created_at":"2024-10-14T12:53:20.513Z","updated_at":"2025-04-12T21:07:19.000Z","avatar_url":"https://github.com/benfoster.png","language":"C#","readme":"# Flo\n\n[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/bgqvqvydhh14gk2e?svg=true)](https://ci.appveyor.com/project/benfoster/flo) [![Travis Build Status](https://travis-ci.org/benfoster/Flo.svg?branch=master)](https://travis-ci.org/benfoster/Flo) [![NuGet](https://img.shields.io/nuget/v/Flo.svg)](https://www.nuget.org/packages/Flo)\n\nFlo is a lightweight library for composing chain of responsibility pipelines. The inspiration for this project came from the need to break down a large codebase of tightly coupled behavior into smaller units for better extensibility and testability.\n\nFlo works in a similar way to middleware in ASP.NET Core with a focus on immutability and support for handlers that output a different type i.e. `Func\u003cTin, Func\u003cTOut\u003e`.\n\n## Where can I get it?\n\nFlo is available on [NuGet](https://www.nuget.org/packages/Flo) and targets NetStandard 1.3.\n\n## Usage\n\nTo create a pipeline similar to ASP.NET Core's middleware, first define the type you wish to pass between your chain of handlers:\n\n```c#\npublic class HttpContext\n{\n    public HttpRequest Request { get;set; }\n    public HttpResponse Response { get;set; }\n}\n```\n\nUse `Pipeline.Build\u003cT\u003e` to create a pipeline passing in a configuration with the handlers you wish to add. Handlers can be delegates or strongly typed (see below). The `Build` method returns a `Func\u003cT, Task\u003cT\u003e` or `Func\u003cTIn, Func\u003cTask\u003cTOut\u003e\u003e` that can safely be reused in your application.\n\n```c#\nvar pipeline = Pipeline.Build\u003cHttpContext\u003e(cfg =\u003e\n    cfg.Add((ctx, next) =\u003e { // Handler 1\n        Log.Logger.Info(\"Starting request to {path}\", ctx.Request.Path);\n        return next.Invoke(context);\n    })\n    .Add((ctx, next) =\u003e { // Handler 2\n        if (!Validator.Validate(ctx.Request)) \n        {\n            await ctx.Response.WriteAsync(\"invalid\");\n            return ctx;\n        }\n        \n        return next.Invoke(context);\n    })\n    .Add((ctx, next) =\u003e { // Handler 3\n        await ctx.Response.WriteAsync(\"Hello World\");\n        return ctx;\n    })\n);\n\nvar context = new HttpContext { Path = \"/foo\" };\ncontext = await pipeline.Invoke(context);\n```\n\n![Chain of responsibility pipeline](./docs/img/cop.svg)\n\n\n### Strongly typed handlers\n\nFlo supports strongly typed handlers. To create a strongly typed handler, implement `IHandler\u003cT\u003e` or `IHandler\u003cTIn, TOut\u003e` depending on the type of pipeline you wish to build:\n\n```c#\npublic class MerchantValidator : IHandler\u003cRequestPayment, ValidationResult\u003e\n{\n    public async Task\u003cValidationResult\u003e HandleAsync(\n        RequestPayment command, \n        Func\u003cRequestPayment, Task\u003cValidationResult\u003e\u003e next)\n    {\n        if (command.MerchantId \u003c= 0)\n            return new ValidationResult\n            {\n                ErrorCode = \"merchant_invalid\"\n            };\n        \n        return await next.Invoke(command);\n    }\n}\n```\n\nStrongly typed handlers are registered in the same way as delegate handlers:\n\n```c#\npublic static Func\u003cRequestPayment, Task\u003cValidationResult\u003e\u003e Build()\n{\n    return Pipeline.Build\u003cRequestPayment, ValidationResult\u003e(cfg =\u003e\n        cfg.Add\u003cMerchantValidator\u003e()\n        .Final(s =\u003e Task.FromResult(new ValidationResult { IsValid = true }))\n    );\n}\n```\n\n### Final Handlers \n\nThere are some differences between how the final handler is _handled_ with `Pipeline\u003cT\u003e` and `Pipeline\u003cTIn, TOut\u003e`.\n\nFor `Pipeline\u003cT\u003e` you do not need to do anything to mark the final handler as Flo will automatically feed the output your final handler into the input of the previous one, on the way back out. You can therefore safely call `next.Invoke` on your final handler without issue, for example:\n\n```c#\nvar pipeline = Pipeline.Build\u003cTestContext\u003e(cfg =\u003e\n    cfg.Add((ctx, next) =\u003e {\n        ctx.Add(\"Item1\", \"Item1Value\");\n        return next.Invoke(ctx);\n    })\n    .Add((ctx, next) =\u003e {\n        ctx.Add(\"Item2\", \"Item2Value\");\n        return next.Invoke(ctx); // no issue\n    })\n);\n```\n\nWith `Pipeline\u003cTIn, TOut\u003e` Flo can't automatically feed the final handler result into the previous handler so you should _not_ call `next.Invoke` on the final handler, for example:\n\n```c#\nvar pipeline = Pipeline.Build\u003cstring, int\u003e(cfg =\u003e\n    cfg.Add((input, next) =\u003e {\n        input += \"hello\";\n        return next.Invoke(input);\n    })\n    .Add((input, next) =\u003e {\n        input += \" world\";\n        return next.Invoke(input);\n    })\n    .Add((input, next) =\u003e {\n        return Task.FromResult(input.Length); // don't call next\n    })\n);\n```\n\nIf you do, `default(TOut)` will be returned. A convenience method `Final` is also available on both pipeline types, to make this declaration more explicit:\n\n```c#\nvar pipeline = Pipeline.Build\u003cstring, int\u003e(cfg =\u003e\n    cfg.Add((input, next) =\u003e {\n        input += \"hello\";\n        return next.Invoke(input);\n    })\n    .Add((input, next) =\u003e {\n        input += \" world\";\n        return next.Invoke(input);\n    })\n    .Final(input =\u003e {\n        return Task.FromResult(input.Length); \n    })\n);\n```\n\n### When\n\n`When` allows you to branch the pipeline based on a predicate, for example:\n\n```c#\nreturn Pipeline.Build\u003cRequestPayment, PaymentResponse\u003e(cfg =\u003e\n     cfg\n        .Add\u003cPaymentValidationHandler\u003e() \n        .When(command =\u003e command.Is3ds, inner =\u003e \n            inner.Add\u003cThreeDsLoggingHandler\u003e() \n            inner.Add\u003cThreeDsHandler\u003e() \n        )\n        .Add\u003cProcessingHandler\u003e() \n);\n```\n\nYou can add multiple handlers to the branched pipeline. For `Pipeline\u003cT\u003e` the parent pipeline will automatically continue after the inner (when) pipeline has completed. The above example would have the following order of execution:\n\n1. `PaymentValidationHandler`\n2. `ThreeDsLoggingHandler`\n3. `ThreeDsHandler`\n4. `ProcessingHandler`\n\n![When Pipeline](./docs/img/when.svg)\n\nThe response is returned in the reverse order of execution.\n\nFor `Pipeline\u003cTIn, TOut\u003e` we will only continue the parent pipeline if the inner pipeline does not return a result (`null`), for example:\n\n```c#\nvar pipeline = Pipeline.Build\u003cTestContext, TestContext\u003e(cfg =\u003e\n    cfg.When(input =\u003e true,\n        builder =\u003e builder.Add((ctx, next) =\u003e\n        {\n            return Task.FromResult(default(TestContext));\n        })\n    )\n    .Final(ctx =\u003e {\n        ctx.Add(\"Test\", \"TestValue\");\n        return Task.FromResult(ctx);\n    })\n);\n\nvar result = await pipeline.Invoke(new TestContext());\nresult.ShouldContainKey(\"Test\");\n```\n\nSince the inner handler returned `null` we assume that we should continue on to the parent. This behavior can be changed by overriding the _continuation function_.\n\n### Fork\n\n`Fork` can also be used to branch/fork the pipeline. It differs from `When` in that we never continue to the parent. In the example above, using `Fork` instead of `When` would yield the following order of execution (`ProcessingHandler` is not executed):\n\n1. `PaymentValidationHandler`\n2. `ThreeDsLoggingHandler`\n3. `ThreeDsHandler`\n\n![When Pipeline](./docs/img/fork.svg)\n\n### Using an IoC Container \n\nBy default Flo uses `Activator.CreateInstance` to create instances of your strongly typed handlers. You can either override this by specifying a `Func\u003cType, object\u003e` when you configure the pipeline:\n\n```c#\nvar pipeline = Pipeline.Build\u003cstring, string\u003e(cfg =\u003e\n    cfg.Add\u003cOverridingHandler\u003e()\n    ,type =\u003e container.TryGetInstance(type) // use StructureMap container\n); \n```\n\nAlternatively you can provide a `Func\u003cTHandler\u003e`:\n\n```c#\nvar pipeline = Pipeline.Build\u003cstring, int\u003e(cfg =\u003e\n    cfg.Add(() =\u003e new StringLengthCountHandler())\n);\n\nAll strongly typed handlers are lazily initialised to avoid unecessary overhead if a path in the pipeline is not hit.\n```\n\n## More examples\n\nYou can find more examples in the tests project. I've also started working on a more comprehensive example that demonstrates breaking up a payment gateway into pipelines [here](https://github.com/benfoster/Flo/tree/sample).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fflo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenfoster%2Fflo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fflo/lists"}