Cleipnir.ResilientFunctions.AzureBlob
1.0.9
Prefix Reserved
dotnet add package Cleipnir.ResilientFunctions.AzureBlob --version 1.0.9
NuGet\Install-Package Cleipnir.ResilientFunctions.AzureBlob -Version 1.0.9
<PackageReference Include="Cleipnir.ResilientFunctions.AzureBlob" Version="1.0.9" />
<PackageVersion Include="Cleipnir.ResilientFunctions.AzureBlob" Version="1.0.9" />
<PackageReference Include="Cleipnir.ResilientFunctions.AzureBlob" />
paket add Cleipnir.ResilientFunctions.AzureBlob --version 1.0.9
#r "nuget: Cleipnir.ResilientFunctions.AzureBlob, 1.0.9"
#addin nuget:?package=Cleipnir.ResilientFunctions.AzureBlob&version=1.0.9
#tool nuget:?package=Cleipnir.ResilientFunctions.AzureBlob&version=1.0.9
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.
Install-Package Cleipnir.ResilientFunctions.PostgreSQL
Secondly, setup the framework:
var store = new PostgreSqlFunctionStore(ConnStr);
await store.Initialize();
var rFunctions = new RFunctions(
store,
new Settings(
unhandledExceptionHandler: e => Console.WriteLine($"Unhandled framework exception occured: '{e}'"),
signOfLifeFrequency: TimeSpan.FromSeconds(5)
)
);
Finally, register and invoke a function using the framework:
var rAction = rFunctions.RegisterAction(
functionTypeId: "OrderProcessor",
async (Order order, OrderScrapbook scrapbook) =>
{
await _paymentProviderClient.Reserve(order.CustomerId, scrapbook.TransactionId, order.TotalPrice);
await scrapbook.DoAtMostOnce(
workStatus: s => s.ProductsShippedStatus,
work: () => _logisticsClient.ShipProducts(order.CustomerId, order.ProductIds)
);
await _paymentProviderClient.Capture(scrapbook.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 = rFunctions.RegisterAction(
functionTypeId: "MessageWaitingFunc",
async (string param, Context context) =>
{
var eventSource = await context.EventSource;
await EventSource
.OfTypes<FundsReserved, InventoryLocked>()
.Take(2)
.ToList();
}
);
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 RFunctions(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}'");
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 RFunctions(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);
}
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, Scrapbook scrapbook)
{
var (recipients, subject, content) = mailAndRecipients;
using var client = new SmtpClient();
await client.ConnectAsync("mail.smtpbucket.com", 8025);
for (var atRecipient = scrapbook.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);
scrapbook.AtRecipient = atRecipient;
await scrapbook.Save();
}
}
public class Scrapbook : RScrapbook
{
public int AtRecipient { get; set; }
}
}
Product | Versions 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
-
net6.0
- Azure.Storage.Blobs (>= 12.17.0)
- Cleipnir.ResilientFunctions (>= 1.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|