ZapMicro.TransactionalOutbox 1.0.0.1

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package ZapMicro.TransactionalOutbox --version 1.0.0.1
                    
NuGet\Install-Package ZapMicro.TransactionalOutbox -Version 1.0.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="ZapMicro.TransactionalOutbox" Version="1.0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ZapMicro.TransactionalOutbox" Version="1.0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="ZapMicro.TransactionalOutbox" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ZapMicro.TransactionalOutbox --version 1.0.0.1
                    
#r "nuget: ZapMicro.TransactionalOutbox, 1.0.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.
#:package ZapMicro.TransactionalOutbox@1.0.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ZapMicro.TransactionalOutbox&version=1.0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=ZapMicro.TransactionalOutbox&version=1.0.0.1
                    
Install as a Cake Tool

Build and Test

ZapMicro.TransactionalOutbox

Repository | Nuget Package

ZapMicro.TransactionalOutbox is an implementation of the Transactional Outbox pattern for .NET Core and Entity Framework Core.

Table of contents

  1. The Transactional Outbox pattern
  2. Installation
  3. Usage
    1. Defining the DbContext
    2. Implementing the Outbox Messages
    3. Implementing the Message Handlers
    4. Configuring
    5. Enqueuing an Outbox Message
    6. Samples
  4. Contributing

The Transactional Outbox pattern

In the world of microservices, the Transactional Outbox pattern helps to guarantee the data consistency between different microservices that communicate through asynchronous messages.

The scenario

Consider an e-commerce system and its order and payment microservices. When a user submits an order, it is created in Pending status until the payment is processed. Once the payment is processed the order status will be updated to

  • Confirmed, if the payment succeeded
  • Rejected, if the payment failed

When implementing the system it's important to guarantee consistency between the order status and the payment processing. The Saga pattern provides a mechanism to guarantee consistency across different microservices based on asynchronous messages handled by a message broker.

The following picture describes an example of create-order saga

Scenario Overview Figure 1: create-order saga example

The create-order saga of the figure above consists of the following steps:

  1. An order is created through synchronous communication (e.g. REST API)
  2. The order record is inserted in the database, the order status is initially set to Pending
  3. An OnOrderCreated message is sent to the message broker
  4. The pending order is returned to the client
  5. The message broker delivers the OnOrderCreated message to the Payment Service
  6. The payment service processes the payment and, if it succeeds, it ir recorded in the database
  7. If the payment succeeded an OnPaymentSucceeded message is sent to the message broker
  8. The message broker delivers the OnPaymentSucceeded message to the Order Service
  9. The order service updates the order status to Confirmed

If the payment cannot be processed then the payment service will produce an OnPaymentFailed message and when the order service receives the message it will update the order status to Rejected

The problem

Although the Saga pattern coordinates the distributed transaction across the two microservices it is still possible to fall in inconsistent scenarios. For instance, a system crush of the payment service may occur after the local transaction is committed and before the OnPaymentSucceeded message is delivered to the message broker. In this case the payment has been correctly processed but the order state will remain set to Pending.

The solution

With the Transactional Outbox pattern an Outbox Messages table is kept within each database and the local transactions will

  • create/update the entity in the database
  • crete the outbox message

A message relay will then be responsible of dequeuing the outbox message and send it to the message broker.

Installation

Install using the ZapMicro.TransactionalOutbox package

PM> Install-Package ZapMicro.TransactionalOutbox

Usage

When you install the package, it should be added to your csproj file. Alternatively, you can add it directly by adding:

<PackageReference Include="ZapMicro.TransactionalOutbox" Version="1.0.0" />

Defining the DbContext

Start with letting your DbContext class implement the ITransactionalOutboxDbContext interface. The ITransactionalOutboxDbContext interface exposes only one property: DbSet<OutboxMessage> OutboxMessages { get; }

public class OrderServiceDbContext: DbContext, ITransactionalOutboxDbContext
{
    //implement the OutboxMessages property from ITransactionalOutboxDbContext interface
    public DbSet<OutboxMessage> OutboxMessages { get; }
    
    //add all the other DbSets needed by the application
    public DbSet<Order> Orders { get; }
}

Implementing the Outbox Messages

Define an implementation of IOutboxMessage for each message that the application have to send to the message broker and add all the properties needed to create and raise the domain message.

public class OnOrderCreatedOutboxMessage: IOutboxMessage
{
    public Guid OrderId { get; set; }
    public double OrderTotal { get; set; }
} 

Implementing the Message Handlers

For each implementation of IOutboxMessage implement a message handler by extending the OutboxMessageHandlerBase<T> class. The method OnOutboxMessageCreated will be called by a background service when the related Outbox Message is dequeued.

public class OnOrderCreatedOutboxMessageHandler: OutboxMessageHandlerBase<OnOrderCreatedOutboxMessage>
{
    public override async ValueTask OnOutboxMessageCreated(OnOrderCreatedOutboxMessage outboxMessage, CancellationToken stoppingToken)
    {
        //map the outbox message to a domain message
        var onOrderCreatedDomainMessage = Map(outboxMessage);
        
        //send it to the message broker
        await SendToMessageBroker(onOrderCreatedDomainMessage);
    }
}

Configuring

Configure the services by calling the AddTransactionalOutbox extension method of the IServiceCollection interface.

builder.Services.AddTransactionalOutbox<OrderServiceDbContext>(configBuilder =>
    configBuilder.ConfigureDequeueOutboxMessagesConfiguration(new DequeueOutboxMessagesConfiguration
    {
        EmptyQueueDelayInSeconds = 1
    }).ConfigureOutboxMessageHandler<OnOrderCreatedOutboxMessageHandler, OnOrderCreatedOutboxMessage>());

Enqueuing an Outbox Message

The Outbox Messages can be enqueued bu using the IEnqueueOutboxMessageCommand service. This will be automatically injected if the AddTransactionalOutbox has been called.

private readonly IOrderRepository _repository;
        private readonly IEnqueueOutboxMessageCommand _enqueueOutboxMessageCommand;
        private readonly OrderServiceDbContext _orderServiceDbContext;

        public OrderService(IOrderRepository repository, IEnqueueOutboxMessageCommand enqueueOutboxMessageCommand, OrderServiceDbContext orderServiceDbContext)
        {
            _repository = repository;
            _enqueueOutboxMessageCommand = enqueueOutboxMessageCommand;
            _orderServiceDbContext = orderServiceDbContext;
        }


        public async Task<Order> CreateOrder(IEnumerable<OrderLine> lines, IEnumerable<Adjustment> adjustments)
        {
            await _orderServiceDbContext.Database.BeginTransactionAsync();
            var order = new Order()
            {
                Id = Guid.NewGuid(),
                Lines = lines.ToList(),
                Adjustments = adjustments.ToList()
            };

            CreateIds(order);
            await _repository.CreateAsync(order);
            await _enqueueOutboxMessageCommand.EnqueueOutboxMessageAsync(new OnOrderCreatedOutboxMessage
            {
                OrderId = order.Id,
                OrderGrandTotal = order.FinalTotal
            }, CancellationToken.None);
            
            await _orderServiceDbContext.Database.CommitTransactionAsync();
            await _orderServiceDbContext.SaveChangesAsync();
            return order;
        }
}

Samples

Samples can be found here:

Contributing

The Contributing guide can be found here

Authors

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.  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.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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