{"id":22350795,"url":"https://github.com/stidsborg/cleipnir.resilientfunctions","last_synced_at":"2025-04-05T21:07:15.033Z","repository":{"id":41310039,"uuid":"417517177","full_name":"stidsborg/Cleipnir.ResilientFunctions","owner":"stidsborg","description":"Implement resilient .NET code using ordinary functions \u0026 actions","archived":false,"fork":false,"pushed_at":"2024-11-30T07:12:24.000Z","size":4373,"stargazers_count":44,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-05T09:07:17.125Z","etag":null,"topics":["csharp","dotnet","durable-execution","fault-tolerant","micro-service","orchestrator","process-manager","resiliency","resilient-functions","saga","saga-pattern","workflow-as-code","workflow-engine"],"latest_commit_sha":null,"homepage":"","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/stidsborg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2021-10-15T13:52:52.000Z","updated_at":"2024-11-30T07:12:27.000Z","dependencies_parsed_at":"2023-10-18T10:46:22.114Z","dependency_job_id":"f8d19321-9c70-4ace-a4ee-772131eaf5cf","html_url":"https://github.com/stidsborg/Cleipnir.ResilientFunctions","commit_stats":{"total_commits":736,"total_committers":1,"mean_commits":736.0,"dds":0.0,"last_synced_commit":"2695119a508170537408f651ce18d10382854183"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stidsborg%2FCleipnir.ResilientFunctions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stidsborg%2FCleipnir.ResilientFunctions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stidsborg%2FCleipnir.ResilientFunctions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stidsborg%2FCleipnir.ResilientFunctions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stidsborg","download_url":"https://codeload.github.com/stidsborg/Cleipnir.ResilientFunctions/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247399876,"owners_count":20932876,"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":["csharp","dotnet","durable-execution","fault-tolerant","micro-service","orchestrator","process-manager","resiliency","resilient-functions","saga","saga-pattern","workflow-as-code","workflow-engine"],"created_at":"2024-12-04T12:10:59.289Z","updated_at":"2025-04-05T21:07:15.012Z","avatar_url":"https://github.com/stidsborg.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![.NET](https://github.com/stidsborg/Cleipnir.ResilientFunctions/actions/workflows/dotnet.yml/badge.svg?no-cache)](https://github.com/stidsborg/Cleipnir.ResilientFunctions/actions/workflows/dotnet.yml)\n[![NuGet](https://img.shields.io/nuget/dt/Cleipnir.ResilientFunctions.svg)](https://www.nuget.org/packages/Cleipnir.ResilientFunctions)\n[![NuGet](https://img.shields.io/nuget/vpre/Cleipnir.ResilientFunctions.svg)](https://www.nuget.org/packages/Cleipnir.ResilientFunctions)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/stidsborg/Cleipnir.ResilientFunctions/blob/main/Docs/cleipnir.png\" alt=\"logo\" /\u003e\n  \u003cbr\u003e\n  Simply making crash-resilient code simple\n  \u003cbr\u003e\n\u003c/p\u003e\n\n# Cleipnir.Flows\nLooking for Cleipnir.Flows, which provides better support for ASP.NET and generic hosted services?\n\n[Github Repo](http://cleipnir.net/)\n\n\n# Cleipnir Resilient Functions\n**Providing a simple way to ensure your code gets run - until you say it is done!**\n\nResilient Functions is a simple and intuitive .NET framework for managing the execution of functions which must complete in their entirety despite: failures, restarts, deployments, versioning etc. \n\nIt automatically retries a function invocation until it completes potentially across process restarts and physical nodes. \n\nThe framework also supports postponing/suspending invocations or failing invocations for manually handling. Furthermore, versioning is natively supported.\n\nIt requires a minimal amount of setup to get started and seamlessly scales with multiple running instances.\n\nCrucially, all this allows the **saga pattern / process manager pattern** to be implemented in a simple yet powerful way. \n\nOut-of-the-box you also get:\n* synchronized invocation across multiple process instances\n* cloud independence \u0026 support for multiple databases\n* simple debuggability \u0026 testability\n* easy versioning of functions\n* native support for rpc and message-based communication\n* graceful-shutdown\n\n| What it is not? |\n| --- |\n| Unlike other saga frameworks Resilient Functions does not require a message-broker to operate.\u003cbr /\u003e It is a fully self-contained solution - which operates on top of a database of choice or in-memory when testing.\u003cbr /\u003e|\n\n## Getting Started\nOnly three steps needs to be performed to get started.\n\nFirstly, install the relevant nuget package (using either Postgres, SqlServer, MariaDB or Azure Blob-storage as persistence layer). I.e.\n```console\ndotnet add package Cleipnir.ResilientFunctions.PostgreSQL\n```\n\nSecondly, setup the framework:\n```csharp\n var store = new PostgreSqlFunctionStore(ConnStr);\n await store.Initialize();\n var functionsRegistry = new FunctionsRegistry(\n   store,\n   new Settings(\n     unhandledExceptionHandler: e =\u003e Console.WriteLine($\"Unhandled framework exception occured: '{e}'\"),\n     leaseLength: TimeSpan.FromSeconds(5)\n   )\n );\n```\n\nFinally, register and invoke a function using the framework:\n```csharp\nvar actionRegistration = functionsRegistry.RegisterAction(\n  flowType: \"OrderProcessor\",\n  async (Order order, Workflow workflow) =\u003e \n  { \n    var effect = workflow.Effect;  \n    var transactionId = effect.Capture(\"TransactionId\", Guid.NewGuid);    \n    await _paymentProviderClient.Reserve(order.CustomerId, transactionId, order.TotalPrice);\n\n    await effect.Capture(\n      \"ShipProducts\",\n      work: () =\u003e _logisticsClient.ShipProducts(order.CustomerId, order.ProductIds),\n      ResiliencyLevel.AtMostOnce\n    );\n\n    await _paymentProviderClient.Capture(transactionId);\n    await _emailClient.SendOrderConfirmation(order.CustomerId, order.ProductIds);\n  }\n);\n\nvar order = new Order(\n  OrderId: \"MK-4321\",\n  CustomerId: Guid.NewGuid(),\n  ProductIds: new[] { Guid.NewGuid(), Guid.NewGuid() },\n  TotalPrice: 123.5M\n);\n\nawait actionRegistration.Invoke(order.OrderId, order);\n```\n\nCongrats, any non-completed Order flows are now automatically restarted by the framework.\n\n### Message-based solution:\nIt is also possible to implement message-based flows using the framework.\nI.e. awaiting 2 external messages before completing an invocation can be accomplished as follows:\n```csharp\n var rAction = functionsRegistry.RegisterAction(\n  flowType: \"MessageWaitingFunc\",\n  async (string param, Workflow workflow) =\u003e \n  {\n    var messages = await workflow.Messages;\n    await messages\n      .OfTypes\u003cFundsReserved, InventoryLocked\u003e()\n      .Take(2)\n      .Completion();\n  }\n);\n```\n\n## Show me more code\nIn the following chapter several stand-alone examples are presented. \n\n### Hello-World\nFirstly, the compulsory, ‘*hello world*’-example can be realized as follows:\n\n```csharp\nvar store = new InMemoryFunctionStore();\nvar functions = new FunctionsRegistry(store, unhandledExceptionHandler: Console.WriteLine);\n\nvar rFunc = functions.RegisterFunc(\n  flowType: \"HelloWorld\",\n  inner: (string param) =\u003e param.ToUpper()\n).Invoke;\n\nvar returned = await rFunc(flowInstance: \"\", param: \"hello world\");\nConsole.WriteLine($\"Returned: '{returned}'\");\n```\n[Source Code](https://github.com/stidsborg/Cleipnir.ResilientFunctions/blob/main/Samples/Sample.ConsoleApp/Simple/HelloWorldExample.cs)\n\n### HTTP-call \u0026 database\nAllright, not useful, here are a couple of simple, but common, use-cases.\n\nInvoking a HTTP-endpoint and storing the response in a database table:\n```csharp\npublic static async Task RegisterAndInvoke(IDbConnection connection, IFunctionStore store)\n{\n  var functions = new FunctionsRegistry(store, new Settings(UnhandledExceptionHandler: Console.WriteLine));\n  var httpClient = new HttpClient();\n\n  var rAction = functions.RegisterAction(\n    flowType: \"HttpAndDatabaseSaga\",\n    inner: async (Guid id) =\u003e\n    {\n      var response = await httpClient.PostAsync(URL, new StringContent(id.ToString()));\n      response.EnsureSuccessStatusCode();\n      var content = await response.Content.ReadAsStringAsync();\n      await connection.ExecuteAsync(\n        \"UPDATE Entity SET State=@State WHERE Id=@Id\",\n        new {State = content, Id = id}\n      );\n    }).Invoke;\n\n  var id = Guid.NewGuid();\n  await rAction(flowInstance: id.ToString(), param: id);\n}\n```\n[Source Code](https://github.com/stidsborg/Cleipnir.ResilientFunctions/blob/main/Samples/Sample.ConsoleApp/Simple/SimpleHttpAndDbExample.cs)\n\n### Sending customer emails\nConsider a travel agency which wants to send a promotional email to its customers:\n```csharp\npublic static class EmailSenderSaga\n{\n  public static async Task Start(MailAndRecipients mailAndRecipients, Workflow workflow)\n  {\n    var state = workflow.States.CreateOrGet\u003cState\u003e();  \n    var (recipients, subject, content) = mailAndRecipients;\n\n    using var client = new SmtpClient();\n    await client.ConnectAsync(\"mail.smtpbucket.com\", 8025);\n        \n    for (var atRecipient = state.AtRecipient; atRecipient \u003c mailAndRecipients.Recipients.Count; atRecipient++)\n    {\n      var recipient = recipients[atRecipient];\n      var message = new MimeMessage();\n      message.To.Add(new MailboxAddress(recipient.Name, recipient.Address));\n      message.From.Add(new MailboxAddress(\"The Travel Agency\", \"offers@thetravelagency.co.uk\"));\n\n      message.Subject = subject;\n      message.Body = new TextPart(TextFormat.Html) { Text = content };\n      await client.SendAsync(message);\n\n      state.AtRecipient = atRecipient;\n      await state.Save();\n    }\n  }\n\n  public class State : FlowState\n  {\n    public int AtRecipient { get; set; }\n  }\n}\n```\n[Source Code](https://github.com/stidsborg/Cleipnir.ResilientFunctions/tree/main/Samples/Sample.ConsoleApp/EmailOffers)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstidsborg%2Fcleipnir.resilientfunctions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstidsborg%2Fcleipnir.resilientfunctions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstidsborg%2Fcleipnir.resilientfunctions/lists"}