Cleipnir.ResilientFunctions 2.0.1

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Cleipnir.ResilientFunctions --version 2.0.1                
NuGet\Install-Package Cleipnir.ResilientFunctions -Version 2.0.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Cleipnir.ResilientFunctions" Version="2.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Cleipnir.ResilientFunctions --version 2.0.1                
#r "nuget: Cleipnir.ResilientFunctions, 2.0.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Cleipnir.ResilientFunctions as a Cake Addin
#addin nuget:?package=Cleipnir.ResilientFunctions&version=2.0.1

// Install Cleipnir.ResilientFunctions as a Cake Tool
#tool nuget:?package=Cleipnir.ResilientFunctions&version=2.0.1                

Cleipnir's Resilient Functions

Realizing the saga-pattern by providing a simple way to ensure your code gets run - until you say it is done!

Resilient 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.

It automatically retries a function invocation until it completes potentially across process restarts and physical nodes.

The framework also supports postponing/suspending invocations or failing invocations for manually handling. Furthermore, versioning is natively supported.

It requires a minimal amount of setup to get started and seamlessly scales with multiple running instances.

Crucially, all this allows the saga pattern to be implemented in a simple yet powerful way.

Out-of-the-box you also get:

  • synchronized invocation across multiple process instances
  • cloud independence & support for multiple databases
  • simple debuggability & testability
  • easy versioning of functions
  • native support for rpc and message-based communication
  • graceful-shutdown
What it is not?
Unlike other saga frameworks Resilient Functions does not require a message-broker to operate.<br /> It is a fully self-contained solution - which operates on top of a database of choice or in-memory when testing.<br />

Sections

Getting Started

Only three steps needs to be performed to get started.

Firstly, install the relevant nuget package (using either Postgres, SqlServer, MySQL or Azure Blob-storage as persistence layer). I.e.

dotnet add package Cleipnir.ResilientFunctions.PostgreSQL

Secondly, setup the framework:

 var store = new PostgreSqlFunctionStore(ConnStr);
 await store.Initialize();
 var functionsRegistry = new FunctionsRegistry(
   store,
   new Settings(
     unhandledExceptionHandler: e => Console.WriteLine($"Unhandled framework exception occured: '{e}'"),
     leaseLength: TimeSpan.FromSeconds(5)
   )
 );

Finally, register and invoke a function using the framework:

var rAction = functionsRegistry.RegisterAction(
  functionTypeId: "OrderProcessor",
  async (Order order, OrderState state) => 
  {
    await _paymentProviderClient.Reserve(order.CustomerId, state.TransactionId, order.TotalPrice);

    await state.DoAtMostOnce(
      workStatus: s => s.ProductsShippedStatus,
      work: () => _logisticsClient.ShipProducts(order.CustomerId, order.ProductIds)
    );

    await _paymentProviderClient.Capture(state.TransactionId);
    await _emailClient.SendOrderConfirmation(order.CustomerId, order.ProductIds);
  }
);

var order = new Order(
  OrderId: "MK-4321",
  CustomerId: Guid.NewGuid(),
  ProductIds: new[] { Guid.NewGuid(), Guid.NewGuid() },
  TotalPrice: 123.5M
);
await rAction.Invoke(order.OrderId, order);

Congrats, any non-completed Order flows are now automatically restarted by the framework.

Message-based solution:

It is also possible to implement message-based flows using the framework. I.e. awaiting 2 external messages before completing an invocation can be accomplished as follows:

 var rAction = functionsRegistry.RegisterAction(
  functionTypeId: "MessageWaitingFunc",
  async (string param, Workflow workflow) => 
  {
    var messages = await workflow.Messages;
    await messages
      .OfTypes<FundsReserved, InventoryLocked>()
      .Take(2)
      .Completion();
  }
);

Show me more code

In the following chapter several stand-alone examples are presented.

Hello-World

Firstly, the compulsory, ‘hello world’-example can be realized as follows:

var store = new InMemoryFunctionStore();
var functions = new FunctionsRegistry(store, unhandledExceptionHandler: Console.WriteLine);

var rFunc = functions.RegisterFunc(
  functionTypeId: "HelloWorld",
  inner: (string param) => param.ToUpper()
).Invoke;

var returned = await rFunc(functionInstanceId: "", param: "hello world");
Console.WriteLine($"Returned: '{returned}'");

Source Code

HTTP-call & database

Allright, not useful, here are a couple of simple, but common, use-cases.

Invoking a HTTP-endpoint and storing the response in a database table:

public static async Task RegisterAndInvoke(IDbConnection connection, IFunctionStore store)
{
  var functions = new FunctionsRegistry(store, new Settings(UnhandledExceptionHandler: Console.WriteLine));
  var httpClient = new HttpClient();

  var rAction = functions.RegisterAction(
    functionTypeId: "HttpAndDatabaseSaga",
    inner: async (Guid id) =>
    {
      var response = await httpClient.PostAsync(URL, new StringContent(id.ToString()));
      response.EnsureSuccessStatusCode();
      var content = await response.Content.ReadAsStringAsync();
      await connection.ExecuteAsync(
        "UPDATE Entity SET State=@State WHERE Id=@Id",
        new {State = content, Id = id}
      );
    }).Invoke;

  var id = Guid.NewGuid();
  await rAction(functionInstanceId: id.ToString(), param: id);
}

Source Code

Sending customer emails

Consider a travel agency which wants to send a promotional email to its customers:

public static class EmailSenderSaga
{
  public static async Task Start(MailAndRecipients mailAndRecipients, State state)
  {
    var (recipients, subject, content) = mailAndRecipients;

    using var client = new SmtpClient();
    await client.ConnectAsync("mail.smtpbucket.com", 8025);
        
    for (var atRecipient = state.AtRecipient; atRecipient < mailAndRecipients.Recipients.Count; atRecipient++)
    {
      var recipient = recipients[atRecipient];
      var message = new MimeMessage();
      message.To.Add(new MailboxAddress(recipient.Name, recipient.Address));
      message.From.Add(new MailboxAddress("The Travel Agency", "offers@thetravelagency.co.uk"));

      message.Subject = subject;
      message.Body = new TextPart(TextFormat.Html) { Text = content };
      await client.SendAsync(message);

      state.AtRecipient = atRecipient;
      await state.Save();
    }
  }

  public class State : WorkflowState
  {
    public int AtRecipient { get; set; }
  }
}

Source Code

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.

NuGet packages (5)

Showing the top 5 NuGet packages that depend on Cleipnir.ResilientFunctions:

Package Downloads
Cleipnir.Flows

Cleipnir Flows is a powerful .NET framework designed for ASP.NET services, providing a straightforward workflow-as-code approach. It ensures that your code executes reliably, even in the face of failures, restarts, deployments, versioning, and other challenges. While similar to Azure Durable Functions, Cleipnir Flows is specifically tailored for ASP.NET Core.

Cleipnir.ResilientFunctions.MySQL

Resilient Functions is a .NET framework realizing the saga-pattern for .NET funcs / actions. By registering a function with the framework, it will ensure that the function invocation completes despite: failures, restarts, deployments, data migrations etc. The framework also supports failing invocations for manually handling and facilitates data migrations. It requires a minimal amount of setup to get started and seamlessly scales with multiple running instances.

Cleipnir.ResilientFunctions.SqlServer

Resilient Functions is a .NET framework realizing the saga-pattern for .NET funcs / actions. By registering a function with the framework, it will ensure that the function invocation completes despite: failures, restarts, deployments, data migrations etc. The framework also supports failing invocations for manually handling and facilitates data migrations. It requires a minimal amount of setup to get started and seamlessly scales with multiple running instances.

Cleipnir.ResilientFunctions.PostgreSQL

Resilient Functions is a .NET framework realizing the saga-pattern for .NET funcs / actions. By registering a function with the framework, it will ensure that the function invocation completes despite: failures, restarts, deployments, data migrations etc. The framework also supports failing invocations for manually handling and facilitates data migrations. It requires a minimal amount of setup to get started and seamlessly scales with multiple running instances.

Cleipnir.ResilientFunctions.AzureBlob

Resilient Functions is a .NET framework realizing the saga-pattern for .NET funcs / actions. By registering a function with the framework, it will ensure that the function invocation completes despite: failures, restarts, deployments, data migrations etc. The framework also supports failing invocations for manually handling and facilitates data migrations. It requires a minimal amount of setup to get started and seamlessly scales with multiple running instances.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.0.3 168 8/30/2024
3.0.2 144 7/25/2024
3.0.1 101 7/25/2024
3.0.0 176 7/14/2024
2.0.2 343 3/16/2024
2.0.1 217 3/15/2024
2.0.0 205 3/14/2024
1.0.16 1,278 8/30/2023
1.0.15 1,236 8/27/2023
1.0.14 1,333 8/13/2023
1.0.13 1,319 8/10/2023
1.0.12 1,391 8/2/2023
1.0.11 1,434 8/1/2023
1.0.10 1,407 7/30/2023
1.0.9 442 7/25/2023
1.0.8 1,468 7/24/2023
1.0.7 1,441 7/19/2023
1.0.6 1,431 7/12/2023
1.0.5 438 7/2/2023
1.0.4 674 4/4/2023
1.0.3 778 2/4/2023
1.0.2 916 2/1/2023
0.0.4 874 6/19/2022
0.0.3 533 5/22/2022