### Install Cleipnir.Flows Nuget Package Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Install the Cleipnir.Flows nuget package for your chosen persistence layer (Postgres, SqlServer, or MariaDB). ```powershell Install-Package Cleipnir.Flows.PostgresSql ``` -------------------------------- ### Run Flows Immediately Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Starts a flow and waits for its completion. Use this when immediate synchronous execution is required. ```csharp // Run immediately (synchronous — waits for completion) await flows.Run("ORD-001", order); ``` -------------------------------- ### Start Cleipnir Flow from Controller Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Initiate a Cleipnir flow from an ASP.NET Core controller. This example shows an OrderController using Flows to run an OrderFlow. ```csharp [ApiController] [Route("[controller]")] public class OrderController(Flows orderFlows) : ControllerBase { [HttpPost] public async Task Post(Order order) => await orderFlows.Run(order.OrderId, order); } ``` -------------------------------- ### Implement Message-Brokered Flow with Cleipnir Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Implement a message-brokered flow by inheriting from Flow and using 'Publish' and 'Message' methods to interact with a message bus. This example demonstrates an OrderFlow for a message-driven scenario. ```csharp public class OrderFlow(IBus bus) : Flow { public override async Task Run(Order order) { var transactionId = await Capture(Guid.NewGuid); await PublishReserveFunds(order, transactionId); await Message(); await PublishShipProducts(order); var trackAndTraceNumber = (await Message()).TrackAndTraceNumber; await PublishCaptureFunds(order, transactionId); await Message(); await PublishSendOrderConfirmationEmail(order, trackAndTraceNumber); await Message(); } } ``` -------------------------------- ### Run Order Processing Flow in C# Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Implement a sequential order processing flow using Cleipnir.NET's message-based primitives. This example demonstrates sending various messages and awaiting corresponding events for a robust and recoverable process. ```csharp public override async Task Run(Order order) { Log.Logger.Information($"ORDER_PROCESSOR: Processing of order '{order.OrderId}' started"); var transactionId = await Capture(Guid.NewGuid); await _bus.Send(new ReserveFunds(order.OrderId, order.TotalPrice, transactionId, order.CustomerId)); await Message(); await _bus.Send(new ShipProducts(order.OrderId, order.CustomerId, order.ProductIds)); await Message(); await _bus.Send(new CaptureFunds(order.OrderId, order.CustomerId, transactionId)); await Message(); await _bus.Send(new SendOrderConfirmationEmail(order.OrderId, order.CustomerId)); await Message(); Log.Logger.ForContext().Information($"Processing of order '{order.OrderId}' completed"); } ``` -------------------------------- ### Create FlowsContainer with In-Memory Store Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Demonstrates console or test usage of FlowsContainer with an in-memory store. Requires explicit service provider setup. ```csharp // Console / test usage with in-memory store var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); var container = FlowsContainer.Create( serviceProvider: serviceCollection.BuildServiceProvider() // functionStore defaults to InMemoryFunctionStore // options defaults to Options.Default ); var flows = new Flows(nameof(OrderFlow), container); await flows.Run("ORD-001", new Order("ORD-001", Guid.NewGuid(), new[] { Guid.NewGuid() }, 99.95m)); // Anonymous registration (no DI source generation needed) var anonymousFlows = container.RegisterAnonymousFlow( flowFactory: () => new OrderFlow(paymentClient, logisticsClient, emailClient), flowName: "OrderFlow" ); container.Dispose(); // or await container.ShutdownGracefully(TimeSpan.FromSeconds(30)); ``` -------------------------------- ### Implement RPC Flow with Cleipnir Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Implement an RPC-based flow by inheriting from Flow and using the 'Capture' method for operations. This example shows an OrderFlow interacting with payment, logistics, and email clients. ```csharp public class OrderFlow( IPaymentProviderClient paymentProviderClient, IEmailClient emailClient, ILogisticsClient logisticsClient ) : Flow { public override async Task Run(Order order) { var transactionId = await Capture(Guid.NewGuid); await Capture(() => paymentProviderClient.Reserve(order.CustomerId, transactionId, order.TotalPrice)); var trackAndTrace = await Capture( () => logisticsClient.ShipProducts(order.CustomerId, order.ProductIds), ResiliencyLevel.AtMostOnce ); await Capture(() => paymentProviderClient.Capture(transactionId)); await Capture(() => emailClient.SendOrderConfirmation(order.CustomerId, trackAndTrace, order.ProductIds)); } } ``` -------------------------------- ### Bridge MassTransit Consumer to a Flow Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Example of a MassTransit consumer that forwards received messages to a Cleipnir flow using SendMessage. ```csharp // Example: MassTransit consumer bridging into a flow public class FundsReservedConsumer(Flows flows) : IConsumer { public Task Consume(ConsumeContext ctx) => flows.SendMessage(ctx.Message.OrderId, ctx.Message, idempotencyKey: ctx.MessageId.ToString()); } ``` -------------------------------- ### Message-Brokered Order Flow in C# Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md An example of an order flow using message brokering. It demonstrates capturing transaction IDs, publishing messages, and waiting for responses like FundsReserved and ProductsShipped. ```csharp public class OrderFlow(IBus bus) : Flow { public override async Task Run(Order order) { var transactionId = await Capture(Guid.NewGuid); //generated transaction id is fixed after this statement await PublishReserveFunds(order, transactionId); await Message(); //execution is suspended until a funds reserved message is received await PublishShipProducts(order); var trackAndTraceNumber = (await Message()).TrackAndTraceNumber; await PublishCaptureFunds(order, transactionId); await Message(); await PublishSendOrderConfirmationEmail(order, trackAndTraceNumber); await Message(); } private Task PublishReserveFunds(Order order, Guid transactionId) => Capture(async () => await bus.Publish(new ReserveFunds(order.OrderId, order.TotalPrice, transactionId, order.CustomerId))); } ``` -------------------------------- ### RPC Order Flow in C# Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md An example of an order flow using Remote Procedure Calls (RPC). It demonstrates capturing external calls to services like payment providers and logistics clients with at-most-once invocation semantics. ```csharp public class OrderFlow( IPaymentProviderClient paymentProviderClient, IEmailClient emailClient, ILogisticsClient logisticsClient ) : Flow { public override async Task Run(Order order) { var transactionId = await Capture(Guid.NewGuid); //generated transaction id is fixed after this statement await Capture(() => paymentProviderClient.Reserve(order.CustomerId, transactionId, order.TotalPrice)); var trackAndTrace = await Capture( () => logisticsClient.ShipProducts(order.CustomerId, order.ProductIds), ResiliencyLevel.AtMostOnce ); //capture may also have at-most-once invocation semantics await Capture(() => paymentProviderClient.Capture(transactionId)); await Capture(() => emailClient.SendOrderConfirmation(order.CustomerId, trackAndTrace, order.ProductIds)); } } ``` -------------------------------- ### Configure Flow Options with ASP.NET Core Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Override global Cleipnir flow options using the Options method during ASP.NET Core registration. This example sets a custom lease length. ```csharp builder.Services.AddFlows(c => c .UsePostgresStore(connectionString) .WithOptions(new Options(leaseLength: TimeSpan.FromSeconds(60))) .RegisterFlow() ); ``` -------------------------------- ### Bridge Wolverine Handler to a Flow Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Example of a Wolverine handler that forwards received messages to a Cleipnir flow using SendMessage. ```csharp // Example: Wolverine handler public class FundsReservedHandler(Flows flows) { public Task Handle(FundsReserved msg) => flows.SendMessage(msg.OrderId, msg); } ``` -------------------------------- ### Bridge Rebus Handler to a Flow Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Example of a Rebus handler that forwards received messages to a Cleipnir flow using SendMessage. ```csharp // Example: Rebus handler public class FundsReservedHandler(Flows flows) : IHandleMessages { public Task Handle(FundsReserved msg) => flows.SendMessage(msg.OrderId, msg); } ``` -------------------------------- ### Bridge NServiceBus Handler to a Flow Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Example of an NServiceBus handler that forwards received messages to a Cleipnir flow using SendMessage. ```csharp // Example: NServiceBus handler public class FundsReservedHandler(Flows flows) : IHandleMessages { public Task Handle(FundsReserved msg, IMessageHandlerContext ctx) => flows.SendMessage(msg.OrderId, msg, idempotencyKey: ctx.MessageId); } ``` -------------------------------- ### Get Flow ControlPanel Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Retrieves the ControlPanel for a given flow instance ID. Returns null if the flow does not exist. Essential for inspecting and repairing flows. ```csharp // Get the control panel (null if the flow doesn't exist) var cp = await flows.ControlPanel("ORD-001"); if (cp is null) return NotFound(); ``` -------------------------------- ### Initialize and Update Git Submodules Source: https://github.com/stidsborg/cleipnir.net/blob/main/GitCommands.txt Use these commands after an initial clone to initialize and update submodules. ```bash git submodule init git submodule update ``` -------------------------------- ### Configure Cleipnir.NET Options Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Set global framework configuration options, including lease management, watchdog behavior, and message pull frequency. This configuration is applied to all flows unless overridden by FlowOptions. ```csharp var options = new Options( unhandledExceptionHandler: ex => logger.LogError(ex, "Cleipnir unhandled exception"), retentionPeriod: TimeSpan.FromDays(30), // delete completed flows after 30 days retentionCleanUpFrequency: TimeSpan.FromHours(1), // cleanup check every hour leaseLength: TimeSpan.FromSeconds(60), // auto-renewed lease per flow execution enableWatchdogs: true, // reschedule crashed/postponed flows watchdogCheckFrequency: TimeSpan.FromSeconds(1), // how often watchdog polls messagesPullFrequency: TimeSpan.FromMilliseconds(250), messagesDefaultMaxWaitForCompletion: TimeSpan.FromSeconds(5), maxParallelRetryInvocations: 100, delayStartup: TimeSpan.FromSeconds(5) // wait before watchdog activates ); // Used in Program.cs builder.Services.AddFlows(c => c .UsePostgresStore(connectionString) .WithOptions(options) .RegisterFlow() ); ``` -------------------------------- ### Configure Cleipnir Flows in Program.cs Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Add Cleipnir Flows to your service collection in Program.cs, specifying the persistence store and registering your flow types. ```csharp builder.Services.AddFlows(c => c .UsePostgresStore(connectionString) .RegisterFlow() ); ``` -------------------------------- ### Register Flows with ASP.NET Core using PostgreSQL Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Configure Cleipnir flows in an ASP.NET Core application using the PostgreSQL store. Includes options and flow registration. ```csharp // Program.cs — PostgreSQL store builder.Services.AddFlows(c => c .UsePostgresStore("Server=localhost;Port=5432;Userid=postgres;Password=Pa55word!;Database=flows;") .WithOptions(new Options( leaseLength: TimeSpan.FromSeconds(5), maxParallelRetryInvocations: 50, retentionPeriod: TimeSpan.FromDays(30) )) .RegisterFlow() .RegisterFlow() .GracefulShutdown(enable: true) ); ``` -------------------------------- ### Kafka Integration: Batch Message Delivery to Flows Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Forward batched Kafka messages to Cleipnir flows using SendMessages. Consumer offsets are committed only after messages are persisted. ```csharp var flowsContainer = FlowsContainer.Create(); var flows = flowsContainer.RegisterAnonymousFlow(flowFactory: () => new InboxFlow()); _ = ConsumeMessages( batchSize: 10, topic: "orders", handler: messages => flows.SendMessages( messages.Select(msg => new BatchedMessage(msg.OrderId, msg)).ToList() ) ); async Task ConsumeMessages(int batchSize, string topic, Func, Task> handler) { var config = new ConsumerConfig { BootstrapServers = "localhost:9092", GroupId = "order-consumer-group", AutoOffsetReset = AutoOffsetReset.Earliest }; using var consumer = new ConsumerBuilder(config).Build(); consumer.Subscribe(topic); var messages = new List(); while (messages.Count < batchSize) { var result = consumer.Consume(TimeSpan.FromSeconds(1)); if (result != null) messages.Add(JsonSerializer.Deserialize(result.Message.Value)!); } await handler(messages); consumer.Commit(); } ``` -------------------------------- ### Manually Restarting a Failed Flow with Control Panel Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md This C# code shows how to manually restart a failed Cleipnir flow using its control panel. It demonstrates removing a specific effect (`shipProductsEffectId`) and then scheduling a restart, allowing for state changes before re-execution. ```csharp var controlPanel = await flows.ControlPanel(order.OrderId); await controlPanel!.Effects.Remove(shipProductsEffectId); await controlPanel.ScheduleRestart().Completion(); ``` -------------------------------- ### Reset and Reinitialize Git Submodules Source: https://github.com/stidsborg/cleipnir.net/blob/main/GitCommands.txt Deinitialize and then update submodules to reset changes. ```bash git submodule deinit -f . git submodule update --init ``` -------------------------------- ### Update Submodule and Commit Immediately Source: https://github.com/stidsborg/cleipnir.net/blob/main/GitCommands.txt Combines updating the submodule to the latest commit and then staging and committing that change to the main repository. ```bash git submodule update --remote git add Cleipnir.ResilientFunctions git commit -m "Updated Cleipnir.ResilientFunctions to the latest commit" ``` -------------------------------- ### Bulk Schedule Flows Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Efficiently schedules multiple flow instances in parallel, dividing work across available replicas. Suitable for high-throughput scenarios. ```csharp // Bulk schedule — divides work across replicas var bulkWork = orders.Select(o => new BulkWork(o.OrderId, o)); await flows.BulkSchedule(bulkWork); ``` -------------------------------- ### Define Flow with Parameter in C# Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Inherit from `Flow` and override `Run` to define a workflow that accepts a parameter. Use `Capture` to memoize operations like payment processing and shipping. ```csharp public class OrderFlow( IPaymentProviderClient paymentClient, ILogisticsClient logisticsClient, IEmailClient emailClient ) : Flow { public override async Task Run(Order order) { var transactionId = await Capture(Guid.NewGuid); // fixed after first execution await Capture(() => paymentClient.Reserve(order.CustomerId, transactionId, order.TotalPrice)); await Capture(() => logisticsClient.ShipProducts(order.CustomerId, order.ProductIds)); await Capture(() => paymentClient.Capture(transactionId)); await Capture(() => emailClient.SendOrderConfirmation(order.CustomerId, order.ProductIds)); } } ``` -------------------------------- ### Flows - Typed Flow Entry Point Source: https://context7.com/stidsborg/cleipnir.net/llms.txt The generic `Flows<>` classes serve as the primary runtime API for initiating, scheduling, messaging, and managing flows. Different variants cater to flows with no parameters, a single parameter, or both a parameter and a result. ```APIDOC ## Flows / Flows / Flows ### Description The generic `Flows<>` classes are the main runtime API for starting, scheduling, messaging, and controlling flows. Each variant covers a different flow signature: parameterless, with parameter, or with parameter and result. ### Methods #### Run Starts a flow and waits for its completion. ```csharp await flows.Run("ORD-001", order); ``` #### Schedule Schedules a flow for immediate background execution. ```csharp var scheduled = await flows.Schedule("ORD-001", order); var result = await scheduled.Completion(timeout: TimeSpan.FromSeconds(30)); ``` #### ScheduleAt Schedules a flow for execution at a specific date and time. ```csharp await flows.ScheduleAt("ORD-001", order, DateTime.UtcNow.AddHours(2)); ``` #### ScheduleIn Schedules a flow for execution after a specified duration. ```csharp await flows.ScheduleIn("ORD-001", order, TimeSpan.FromHours(2)); ``` #### BulkSchedule Schedules multiple flows in bulk, distributing the work across available replicas. ```csharp var bulkWork = orders.Select(o => new BulkWork(o.OrderId, o)); await flows.BulkSchedule(bulkWork); ``` #### SendMessage Sends a message to a specific flow instance. ```csharp await flows.SendMessage("ORD-001", new FundsReserved("ORD-001"), idempotencyKey: "FundsReserved-ORD-001"); ``` #### SendMessages Sends messages in a batch to multiple flow instances. ```csharp await flows.SendMessages(new List { new("ORD-001", new FundsReserved("ORD-001")), new("ORD-002", new FundsReserved("ORD-002")), }); ``` #### Interrupt Interrupts suspended flows, resuming their execution. ```csharp await flows.Interrupt(new[] { (FlowInstance)"ORD-001", "ORD-002" }); ``` ``` -------------------------------- ### Define Flow with Parameter and Result in C# Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Inherit from `Flow` and override `Run` to define a workflow that accepts a parameter and returns a result. Use `Capture` for operations like generating codes and `Message` to wait for external input. ```csharp public class SmsVerificationFlow : Flow { public override async Task Run(string customerPhoneNumber) { for (var i = 0; i < 5; i++) { var code = await Capture($"SendSms#{i}", async () => { var generated = GenerateOneTimeCode(); await SendSms(customerPhoneNumber, generated); return generated; }); var response = await Message(); if (response.Code == code) return MostRecentAttempt.Success; } return MostRecentAttempt.MaxAttemptsExceeded; } } ``` -------------------------------- ### Basic Order Processing Flow Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md A straightforward implementation of an order processing flow without explicit resilience against crashes or restarts. ```csharp public async Task ProcessOrder(Order order) { await _paymentProviderClient.Reserve(order.TransactionId, order.CustomerId, order.TotalPrice); await _logisticsClient.ShipProducts(order.CustomerId, order.ProductIds); await _paymentProviderClient.Capture(order.TransactionId); await _emailClient.SendOrderConfirmation(order.CustomerId, order.ProductIds); } ``` -------------------------------- ### Schedule Flows for Background Execution Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Schedules a flow for immediate background execution or for a future time. Returns a handle to track completion. ```csharp // Schedule for immediate background execution (fire-and-forget with handle) var scheduled = await flows.Schedule("ORD-001", order); var result = await scheduled.Completion(timeout: TimeSpan.FromSeconds(30)); // Schedule for future execution await flows.ScheduleAt("ORD-001", order, DateTime.UtcNow.AddHours(2)); await flows.ScheduleIn("ORD-001", order, TimeSpan.FromHours(2)); ``` -------------------------------- ### Configure Flow Options Manually Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Manually configure FlowOptions when constructing Flows<> instances. This allows per-flow overrides for retention, watchdogs, and parallelism. ```csharp // Or pass FlowOptions directly when constructing Flows<> manually var orderFlows = new Flows( "OrderFlow", flowsContainer, options: new FlowOptions( retentionPeriod: TimeSpan.FromDays(7), enableWatchdogs: true, maxParallelRetryInvocations: 20, messagesDefaultMaxWaitForCompletion: TimeSpan.FromSeconds(5) ) ); ``` -------------------------------- ### Execute Code At-Most-Once Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Use ResiliencyLevel.AtMostOnce to ensure a specific operation within a flow is executed at most once, preventing duplicate processing. ```csharp public class AtMostOnceFlow : Flow { private readonly RocketSender _rocketSender = new(); public override async Task Run(string rocketId) { await Capture( id: "FireRocket", _rocketSender.FireRocket, ResiliencyLevel.AtMostOnce ); } } ``` -------------------------------- ### Register Flows with ASP.NET Core using SQL Server Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Configure Cleipnir flows in an ASP.NET Core application using the SQL Server store. Registers a specific flow type. ```csharp // SQL Server store builder.Services.AddFlows(c => c .UseSqlServerStore("Server=localhost;Database=flows;User Id=sa;Password=Pa55word!;") .RegisterFlow() ); ``` -------------------------------- ### Sending Customer Emails with CaptureEach Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md A Cleipnir flow that sends promotional emails to a list of recipients. It uses CaptureEach to efficiently track progress and manage storage for potentially large recipient lists. ```csharp public class NewsletterFlow : Flow { public override async Task Run(MailAndRecipients mailAndRecipients) { var (recipients, subject, content) = mailAndRecipients; using var client = new SmtpClient(); await client.ConnectAsync("mail.smtpbucket.com", 8025); await recipients.CaptureEach(async recipient => { 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); }); } } ``` -------------------------------- ### FlowsContainer - Runtime Host Source: https://context7.com/stidsborg/cleipnir.net/llms.txt The FlowsContainer is the central runtime object that holds the backing store and service provider. It can be created explicitly for console applications or via Dependency Injection (DI) in ASP.NET. The `Create()` method provides a convenience factory that defaults to an in-memory store. `RegisterAnonymousFlow` allows registering a flow type without requiring DI-generated source code. ```APIDOC ## FlowsContainer ### Description Central runtime object. Holds the backing store and service provider. Created explicitly for console apps or via DI in ASP.NET. `Create()` is a convenience factory that defaults to an in-memory store. `RegisterAnonymousFlow` registers a flow type without needing DI-generated source code. ### Usage ```csharp // Console / test usage with in-memory store var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); var container = FlowsContainer.Create( serviceProvider: serviceCollection.BuildServiceProvider() // functionStore defaults to InMemoryFunctionStore // options defaults to Options.Default ); var flows = new Flows(nameof(OrderFlow), container); await flows.Run("ORD-001", new Order("ORD-001", Guid.NewGuid(), new[] { Guid.NewGuid() }, 99.95m)); // Anonymous registration (no DI source generation needed) var anonymousFlows = container.RegisterAnonymousFlow( flowFactory: () => new OrderFlow(paymentClient, logisticsClient, emailClient), flowName: "OrderFlow" ); container.Dispose(); // or await container.ShutdownGracefully(TimeSpan.FromSeconds(30)); ``` ``` -------------------------------- ### Register Flows with ASP.NET Core using MariaDB Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Configure Cleipnir flows in an ASP.NET Core application using the MariaDB store. Registers a specific flow type. ```csharp // MariaDB store builder.Services.AddFlows(c => c .UseMariaDbStore("Server=localhost;Port=3306;Database=flows;Uid=root;Pwd=Pa55word!;") .RegisterFlow() ); ``` -------------------------------- ### Resilient Order Processing Flow Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md An order processing flow implemented as a Cleipnir.NET Flow, designed to be resilient against crashes and restarts by leveraging the framework's capabilities. ```csharp public class OrderFlow(IPaymentProviderClient paymentProviderClient, IEmailClient emailClient, ILogisticsClient logisticsClient) : Flow { public override async Task Run(Order order) { Log.Logger.ForContext().Information($"ORDER_PROCESSOR: Processing of order '{order.OrderId}' started"); var transactionId = Guid.Empty; await paymentProviderClient.Reserve(order.CustomerId, transactionId, order.TotalPrice); await logisticsClient.ShipProducts(order.CustomerId, order.ProductIds); await paymentProviderClient.Capture(transactionId); await emailClient.SendOrderConfirmation(order.CustomerId, order.ProductIds); Log.Logger.ForContext().Information($"ORDER_PROCESSOR: Processing of order '{order.OrderId}' completed"); } } ``` -------------------------------- ### Register Flows with ASP.NET Core using In-Memory Store Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Configure Cleipnir flows in an ASP.NET Core application using an in-memory store, suitable for testing and development. Registers a specific flow type. ```csharp // In-memory store (tests / development) builder.Services.AddFlows(c => c .UseInMemoryStore() .RegisterFlow() ); ``` -------------------------------- ### Direct Effect Store Access with Effect Methods Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Access the durable effect store directly using `Effect.CreateOrGet` to read or create a value, or `Effect.Upsert` to overwrite it. This is useful for tracking progress in loops or memoizing values. ```csharp public class NewsletterFlow : Flow { public override async Task Run(MailAndRecipients mail) { using var client = new SmtpClient(); await client.ConnectAsync("mail.smtpbucket.com", 8025); // Persist loop cursor so a crash mid-send never re-sends to already-processed recipients var atRecipient = await Effect.CreateOrGet("AtRecipient", 0); for (; atRecipient < mail.Recipients.Count; atRecipient++) { var recipient = mail.Recipients[atRecipient]; var message = new MimeMessage(); message.To.Add(new MailboxAddress(recipient.Name, recipient.Address)); message.From.Add(new MailboxAddress("The Agency", "offers@agency.co.uk")); message.Subject = mail.Subject; message.Body = new TextPart(TextFormat.Html) { Text = mail.Content }; await client.SendAsync(message); await Effect.Upsert("AtRecipient", atRecipient + 1); // checkpoint } } } ``` -------------------------------- ### MassTransit Handler Implementation Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Implement an IConsumer for MassTransit to handle incoming MyMessage types. This handler forwards the message value and context to a SimpleFlows service. ```csharp public class SimpleFlowsHandler(SimpleFlows simpleFlows) : IConsumer { public Task Consume(ConsumeContext context) => simpleFlows.SendMessage(context.Message.Value, context.Message); } ``` -------------------------------- ### Send Multiple Messages in Batch Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Demonstrates sending a collection of messages in a single batch operation using the flows.SendMessages method. ```csharp await flows.SendMessages(batchedMessages); ``` -------------------------------- ### Push Changes to Origin Source: https://github.com/stidsborg/cleipnir.net/blob/main/GitCommands.txt Push local commits, including submodule updates, to the remote repository. ```bash git push ``` -------------------------------- ### At-least-once Order Processing with Idempotency Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md This C# code demonstrates an at-least-once order processing flow where the payment provider requires a transaction ID. The `Capture` method ensures idempotency by wrapping non-determinism inside effects, allowing safe re-execution with the same transaction ID. ```csharp public override async Task Run(Order order) { Log.Logger.Information($"ORDER_PROCESSOR: Processing of order '{order.OrderId}' started"); var transactionId = await Capture("TransactionId", Guid.NewGuid); await paymentProviderClient.Reserve(order.CustomerId, transactionId, order.TotalPrice); await logisticsClient.ShipProducts(order.CustomerId, order.ProductIds); await paymentProviderClient.Capture(transactionId); await emailClient.SendOrderConfirmation(order.CustomerId, order.ProductIds); Log.Logger.ForContext().Information($"Processing of order '{order.OrderId}' completed"); } ``` -------------------------------- ### Restart Failed Flow Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Schedule a flow to restart with a new parameter, optionally clearing previous failure states. Ensure the flow ID is known. ```csharp var controlPanel = await flows.ControlPanel(flowId); controlPanel!.Param = "valid parameter"; await controlPanel.ScheduleRestart(clearFailures: true).Completion(); ``` -------------------------------- ### Define Parameterless Flow in C# Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Inherit from `Flow` and override `Run` to define a parameterless durable workflow. Use `Capture` for side-effectful operations and `Delay` for pauses. ```csharp public class BackgroundCleanupFlow : Flow { public override async Task Run() { await Capture("Archive", () => ArchiveOldRecords()); await Delay(TimeSpan.FromDays(1)); } } ``` -------------------------------- ### ControlPanel - Repair Failed Flow Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Demonstrates repairing a failed flow by modifying its parameter, removing a problematic effect, and scheduling a restart. Clears previous failure records. ```csharp // Repair a failed flow: fix its parameter and restart cp.Param = new Order("ORD-001", cp.Param.CustomerId, cp.Param.ProductIds, 149.95m); await cp.Effects.Remove("Ship"); // remove a specific effect to force re-execution await cp.ScheduleRestart(clearFailures: true).Completion(); ``` -------------------------------- ### Wolverine Handler Implementation Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Implement a handler for Wolverine to process MyMessage. This handler invokes the SendMessage method on a SimpleFlows service. ```csharp public class SimpleFlowsHandler(SimpleFlows flows) { public Task Handle(MyMessage myMessage) => flows.SendMessage(myMessage.Value, myMessage); } ``` -------------------------------- ### Integration Test for Order Flow Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md An integration test demonstrating the execution of an OrderFlow. It sets up a service provider with stub implementations for dependencies and asserts that a transaction ID is correctly passed. ```csharp var transactionId = Guid.NewGuid(); var usedTransactionId = default(Guid?); var serviceProvider = new ServiceCollection() .AddSingleton(new OrderFlow( PaymentProviderClientTestStub.Create(reserve: (id, _, _) => { usedTransactionId = id; return Task.CompletedTask; }), EmailClientStub.Instance, LogisticsClientStub.Instance) ).BuildServiceProvider(); using var container = FlowsContainer.Create(serviceProvider); var flows = new OrderFlows(container); var testOrder = new Order("MK-54321", CustomerId: Guid.NewGuid(), ProductIds: [Guid.NewGuid()], TotalPrice: 120); await flows.Run( instanceId: testOrder.OrderId, testOrder, new InitialState(Messages: [], Effects: [new InitialEffect(Id: 0, Value: transactionId, Alias: "TransactionId")])); Assert.AreEqual(transactionId, usedTransactionId); ``` -------------------------------- ### NServiceBus Handler Implementation Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Implement IHandleMessages for NServiceBus to process MyMessage. This handler delegates the message processing to a SimpleFlows service. ```csharp public class SimpleFlowsHandler(SimpleFlows flows) : IHandleMessages { public Task Handle(MyMessage message, IMessageHandlerContext context) => flows.SendMessage(message.Value, message); } ``` -------------------------------- ### Kafka Handler for Batched Messages Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Configures a Kafka consumer to process messages in batches. Each batch is processed by a handler that sends the messages to a SimpleFlows service. ```csharp ConsumeMessages( batchSize: 10, topic, handler: messages => flows.SendMessages( messages.Select(msg => new BatchedMessage(msg.Instance, msg)).ToList() )); ``` -------------------------------- ### Idempotent Flow Execution with Capture Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md A Cleipnir flow that uses the Capture method to ensure a specific piece of work, like solving a cryptographic puzzle, is only executed once. This prevents re-execution of already completed tasks. ```csharp public class AtLeastOnceFlow : Flow { private readonly PuzzleSolverService _puzzleSolverService = new(); public override async Task Run(string hashCode) { var solution = await Capture( id: "PuzzleSolution", work: () => _puzzleSolverService.SolveCryptographicPuzzle(hashCode) ); return solution; } } ``` -------------------------------- ### Write Messages to a Flow with Idempotency Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Use MessageWriter to append domain events to a flow. Idempotency keys prevent duplicate message delivery on retries. ```csharp var writer = flows.MessageWriter("ORD-001"); // Append a single message (idempotency key prevents duplicates on retry) await writer.AppendMessage( new FundsReserved("ORD-001"), idempotencyKey: "FundsReserved-ORD-001" ); ``` -------------------------------- ### Send Messages to Flow Instances Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Sends individual or batched messages to specific flow instances. Useful for inter-flow communication or external events triggering flow actions. ```csharp // Send a message to a specific flow instance await flows.SendMessage("ORD-001", new FundsReserved("ORD-001"), idempotencyKey: "FundsReserved-ORD-001"); // Send batched messages across multiple instances await flows.SendMessages(new List { new("ORD-001", new FundsReserved("ORD-001")), new("ORD-002", new FundsReserved("ORD-002")), }); ``` -------------------------------- ### At-most-once Order Processing with Non-Idempotent Logistics API Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md This C# code illustrates an at-most-once order processing flow when the logistics API is not idempotent. It uses the `Capture` effect to ensure that `logisticsClient.ShipProducts` is called at most once, preventing duplicate shipments if the flow restarts. ```csharp public override async Task Run(Order order) { Log.Logger.Information($"ORDER_PROCESSOR: Processing of order '{order.OrderId}' started"); var transactionId = await Capture("TransactionId", Guid.NewGuid); await paymentProviderClient.Reserve(order.CustomerId, transactionId, order.TotalPrice); await Capture( id: "ShipProducts", work: () => logisticsClient.ShipProducts(order.CustomerId, order.ProductIds) ); await paymentProviderClient.Capture(transactionId); await emailClient.SendOrderConfirmation(order.CustomerId, order.ProductIds); Log.Logger.ForContext().Information($"Processing of order '{order.OrderId}' completed"); } ``` -------------------------------- ### Memoize Operations with Capture in C# Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Use `Capture` to memoize side-effectful or non-deterministic work. It records the result on the first execution and returns the stored value on subsequent replays without re-executing the function. Supports explicit or auto-generated IDs and different resiliency levels. ```csharp public class PaymentFlow(IPaymentClient payment, ILogisticsClient logistics) : Flow { public override async Task Run(Order order) { // Auto-generated ID, at-least-once (default) var transactionId = await Capture(Guid.NewGuid); // Explicit ID, at-least-once with default retry await Capture("Reserve", () => payment.Reserve(order.CustomerId, transactionId, order.TotalPrice)); // Explicit ID, at-most-once — will NOT retry if a response was never received await Capture( "Ship", () => logistics.ShipProducts(order.CustomerId, order.ProductIds), ResiliencyLevel.AtMostOnce ); // Explicit ID + custom RetryPolicy (constant 1s delay, no suspension threshold) var retryPolicy = RetryPolicy.CreateConstantDelay( interval: TimeSpan.FromSeconds(1), suspendThreshold: TimeSpan.Zero ); await Capture("Capture", () => payment.Capture(transactionId), retryPolicy); } } ``` -------------------------------- ### ControlPanel - Inspect and Control Flows Source: https://context7.com/stidsborg/cleipnir.net/llms.txt The ControlPanel allows inspection and control of running or failed flows. It provides access to the flow's status, parameters, results, and effects, and enables actions like restarting failed flows. Retrieved via `flows.ControlPanel(instanceId)`. ```APIDOC ## ControlPanel ### Description Inspect and control a running or failed flow. Retrieved via `flows.ControlPanel(instanceId)`. Exposes current status, parameters, result, effects, and restart/completion controls. Essential for repairing failed flows in production. ### Methods #### Get Control Panel Retrieves the control panel for a given flow instance. ```csharp var cp = await flows.ControlPanel("ORD-001"); if (cp is null) return NotFound(); ``` #### Read Status and Result Accesses the current status and result of the flow. ```csharp Console.WriteLine($"Status: {cp.Status}"); // Executing | Suspended | Succeeded | Failed Console.WriteLine($"Result: {cp.Result}"); ``` #### WaitForCompletion Waits for the flow to complete execution. ```csharp await cp.WaitForCompletion(allowPostponeAndSuspended: true, timeout: TimeSpan.FromSeconds(60)); ``` #### Inspect Effects Allows inspection of all effects associated with the flow. ```csharp var allEffectIds = await cp.Effects.AllIds; foreach (var id in allEffectIds) Console.WriteLine($"{id}: {await cp.Effects.GetStatus(id)}"); ``` #### Repair Failed Flow Allows repairing a failed flow by modifying its parameters and scheduling a restart. ```csharp cp.Param = new Order("ORD-001", cp.Param.CustomerId, cp.Param.ProductIds, 149.95m); await cp.Effects.Remove("Ship"); // remove a specific effect to force re-execution await cp.ScheduleRestart(clearFailures: true).Completion(); ``` #### BusyWaitUntil Waits until a specific condition is met (useful in tests). ```csharp await cp.BusyWaitUntil(c => c.Status == Status.Suspended); await cp.Refresh(); // re-read latest state from store ``` ``` -------------------------------- ### Rebus Handler Implementation Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Implement IHandleMessages for Rebus to process MyMessage. This handler sends the message to a SimpleFlows service for processing. ```csharp public class SimpleFlowsHandler(SimpleFlows simpleFlows) : IHandleMessages { public Task Handle(MyMessage msg) => simpleFlows.SendMessage(msg.Value, msg); } ``` -------------------------------- ### ControlPanel - Wait for Completion Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Blocks execution until the flow instance completes. Allows specifying a timeout and whether to consider postponed or suspended states. ```csharp // Wait for a flow to finish await cp.WaitForCompletion(allowPostponeAndSuspended: true, timeout: TimeSpan.FromSeconds(60)); ``` -------------------------------- ### Commit Submodule Updates to Main Repository Source: https://github.com/stidsborg/cleipnir.net/blob/main/GitCommands.txt Stage and commit the updated submodule reference to the main Git repository. ```bash git add Cleipnir.ResilientFunctions git commit -m "Updated Cleipnir.ResilientFunctions to the latest commit" ``` -------------------------------- ### ControlPanel - Read Status and Result Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Accesses the current status (Executing, Suspended, Succeeded, Failed) and the final result of a flow instance. ```csharp // Read status and result Console.WriteLine($"Status: {cp.Status}"); // Executing | Suspended | Succeeded | Failed Console.WriteLine($"Result: {cp.Result}"); ``` -------------------------------- ### Update Git Submodule to Latest Commit Source: https://github.com/stidsborg/cleipnir.net/blob/main/GitCommands.txt Fetch the latest commit from the submodule's remote repository. ```bash git submodule update --remote ``` -------------------------------- ### Postpone Flow Execution Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Delay the execution of a flow for a specified duration if a condition is met, without consuming in-memory resources during the delay. ```csharp public class PostponeFlow : Flow { private readonly ExternalService _externalService = new(); public override async Task Run(string orderId) { if (await Capture(() => _externalService.IsOverloaded())) await Delay(@for: TimeSpan.FromMinutes(10)); //execute rest of the flow } } ``` -------------------------------- ### Wait for External Message in C# Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Use Message to wait for the retrieval of an external message without consuming resources. Execution is suspended until the message is received. ```csharp var fundsReserved = await Message(); ``` -------------------------------- ### Emit Signal to Flow Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Append a message to a flow's message writer to signal an event, using an idempotency key to prevent duplicate messages. ```csharp var messageWriter = flows.MessageWriter(orderId); await messageWriter.AppendMessage(new FundsReserved(orderId), idempotencyKey: nameof(FundsReserved)); ``` -------------------------------- ### Capture Non-Deterministic Values in C# Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Use Capture to ensure non-deterministic or side-effectful code produces the same result after a crash or restart. It can also be used for external calls with automatic retry. ```csharp var transactionId = await Capture("TransactionId", () => Guid.NewGuid()); ``` ```csharp //or simply var transactionId = await Capture(Guid.NewGuid); ``` ```csharp //can also be used for external calls with automatic retry await Capture(() => httpClient.PostAsync("https://someurl.com", content), RetryPolicy.Default); ``` -------------------------------- ### Suspend Execution with Delay Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Use `Delay` to suspend a flow without consuming threads or memory. The flow automatically resumes after the specified duration. Set `suspend: false` for non-suspending, in-process delays, typically used in tests. ```csharp public class PostponeFlow : Flow { private readonly ExternalService _externalService = new(); public override async Task Run(string orderId) { if (await _externalService.IsOverloaded()) await Delay(TimeSpan.FromMinutes(10)); // releases thread + memory // resume here after delay await Effect.Capture("ProcessOrder", () => ProcessOrder(orderId)); } } ``` ```csharp public class FastTestFlow : Flow { public override async Task Run(string param) { await Delay(TimeSpan.FromMilliseconds(100), suspend: false); // stays in-process return 1; } } ``` -------------------------------- ### Wait for External Event with Message Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Use `Message` to suspend a flow until a specific message type arrives. Optionally, provide a filter or a deadline to return null if the message is not received in time. ```csharp public class OrderFlow(IBus bus) : Flow { public override async Task Run(Order order) { var transactionId = await Capture(Guid.NewGuid); await bus.Publish(new ReserveFunds(order.OrderId, order.TotalPrice, transactionId, order.CustomerId)); await Message(); // blocks until message arrives await bus.Publish(new ShipProducts(order.OrderId, order.CustomerId, order.ProductIds)); var shipped = await Message(); await bus.Publish(new CaptureFunds(order.OrderId, order.CustomerId, transactionId)); await Message(); await bus.Publish(new SendOrderConfirmationEmail(order.OrderId, order.CustomerId, shipped.TrackAndTrace)); await Message(); } } ``` ```csharp public class LoanFlow : Flow { public override async Task Run(LoanApplication loan) { await Effect.Capture(() => Bus.Publish(new PerformCreditCheck(loan.Id, loan.CustomerId, loan.Amount))); var deadline = await Workflow.UtcNow() + TimeSpan.FromMinutes(15); var outcomes = new List(); for (var i = 0; i < 3; i++) { var outcome = await Message(deadline); if (outcome == null) break; outcomes.Add(outcome); } await Bus.Publish( outcomes.Count >= 2 && outcomes.All(o => o.Approved) ? new LoanApplicationApproved(loan) : new LoanApplicationRejected(loan) ); } } ``` -------------------------------- ### ControlPanel - Busy Wait Until Condition Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Polls the flow's state until a specific condition is met. Primarily useful for testing scenarios where immediate state changes are expected. ```csharp // Busy-wait until a condition is met (useful in tests) await cp.BusyWaitUntil(c => c.Status == Status.Suspended); await cp.Refresh(); // re-read latest state from store ``` -------------------------------- ### Suspend Execution for a Duration in C# Source: https://github.com/stidsborg/cleipnir.net/blob/main/README.md Use Delay to suspend execution for a specified duration without consuming in-memory resources. Execution will automatically resume from the same point afterwards. ```csharp await Delay(TimeSpan.FromMinutes(5)); ``` -------------------------------- ### ControlPanel - Inspect Effects Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Retrieves all effect IDs associated with a flow instance and their current statuses. Useful for debugging and understanding flow side effects. ```csharp // Inspect effects var allEffectIds = await cp.Effects.AllIds; foreach (var id in allEffectIds) Console.WriteLine($"{id}: {await cp.Effects.GetStatus(id)}"); ``` -------------------------------- ### Interrupt Suspended Flows Source: https://context7.com/stidsborg/cleipnir.net/llms.txt Resumes flows that are currently suspended. Use this to unblock flows waiting for external conditions or manual intervention. ```csharp // Interrupt suspended flows (resumes them) await flows.Interrupt(new[] { (FlowInstance)"ORD-001", "ORD-002" }); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.