Skip to content

Instantly share code, notes, and snippets.

@moostafaa
Created December 30, 2025 13:53
Show Gist options
  • Select an option

  • Save moostafaa/c7b6758e49295826360c06492175bb4c to your computer and use it in GitHub Desktop.

Select an option

Save moostafaa/c7b6758e49295826360c06492175bb4c to your computer and use it in GitHub Desktop.
Two phase sample implementation
public interface ITransactionParticipant
{
Task<PrepareResult> PrepareAsync(Guid transactionId, TransactionContext context);
Task CommitAsync(Guid transactionId);
Task RollbackAsync(Guid transactionId);
}
public enum PrepareResult
{
Prepared,
Aborted
}
public class TransactionContext
{
public Guid TransactionId { get; set; }
public DateTime CreatedAt { get; set; }
public Dictionary<string, object> Data { get; set; } = new();
}
public class Order
{
public Guid Id { get; set; }
public Guid TransactionId { get; set; }
public string CustomerId { get; set; }
public string Status { get; set; }
public DateTime CreatedAt { get; set; }
}
public class ProductStock
{
public int Id { get; set; }
public string ProductId { get; set; }
public int AvailableQuantity { get; set; }
public int ReservedQuantity { get; set; }
}
public class OrderDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseInMemoryDatabase("OrderDb");
}
public class InventoryDbContext : DbContext
{
public DbSet<ProductStock> ProductStocks { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseInMemoryDatabase("InventoryDb");
}
public class OrderParticipant : ITransactionParticipant
{
public async Task<PrepareResult> PrepareAsync(Guid transactionId, TransactionContext context)
{
try
{
using var db = new OrderDbContext();
using var transaction = await db.Database.BeginTransactionAsync();
var customerId = context.Data["CustomerId"].ToString();
var order = new Order
{
Id = Guid.NewGuid(),
TransactionId = transactionId,
CustomerId = customerId,
Status = "PREPARED",
CreatedAt = DateTime.UtcNow
};
db.Orders.Add(order);
await db.SaveChangesAsync();
await transaction.CommitAsync();
Console.WriteLine($"[Order] Transaction {transactionId} PREPARED (Order Created)");
return PrepareResult.Prepared;
}
catch (Exception ex)
{
Console.WriteLine($"[Order] Transaction {transactionId} ABORTED: {ex.Message}");
return PrepareResult.Aborted;
}
}
public async Task CommitAsync(Guid transactionId)
{
using var db = new OrderDbContext();
var order = await db.Orders.FirstOrDefaultAsync(o => o.TransactionId == transactionId);
if (order != null && order.Status == "PREPARED")
{
order.Status = "COMMITTED";
await db.SaveChangesAsync();
Console.WriteLine($"[Order] Transaction {transactionId} COMMITTED");
}
else if (order == null)
{
Console.WriteLine($"[Order] Transaction {transactionId} not found for commit.");
}
}
public async Task RollbackAsync(Guid transactionId)
{
using var db = new OrderDbContext();
var order = await db.Orders.FirstOrDefaultAsync(o => o.TransactionId == transactionId);
if (order != null && order.Status == "PREPARED")
{
db.Orders.Remove(order);
await db.SaveChangesAsync();
Console.WriteLine($"[Order] Transaction {transactionId} ROLLBACKED (Order Deleted)");
}
}
}
public class InventoryParticipant : ITransactionParticipant
{
public async Task<PrepareResult> PrepareAsync(Guid transactionId, TransactionContext context)
{
try
{
using var db = new InventoryDbContext();
using var transaction = await db.Database.BeginTransactionAsync();
var productId = context.Data["ProductId"].ToString();
var quantity = (int)context.Data["Quantity"];
// Note: In a real SQL DB, use UPDLOCK hints to prevent race conditions here.
var stock = await db.ProductStocks.FirstOrDefaultAsync(p => p.ProductId == productId);
if (stock == null || stock.AvailableQuantity < quantity)
{
Console.WriteLine($"[Inventory] Insufficient stock for {productId}. Aborting.");
return PrepareResult.Aborted;
}
stock.AvailableQuantity -= quantity;
stock.ReservedQuantity += quantity;
await db.SaveChangesAsync();
await transaction.CommitAsync();
Console.WriteLine($"[Inventory] Transaction {transactionId} PREPARED (Stock Reserved)");
return PrepareResult.Prepared;
}
catch (Exception ex)
{
Console.WriteLine($"[Inventory] Transaction {transactionId} ABORTED: {ex.Message}");
return PrepareResult.Aborted;
}
}
public async Task CommitAsync(Guid transactionId)
{
Console.WriteLine($"[Inventory] Transaction {transactionId} COMMITTED (Reservation Finalized)");
await Task.CompletedTask;
}
public async Task RollbackAsync(Guid transactionId)
{
Console.WriteLine($"[Inventory] Transaction {transactionId} ROLLBACKED (Stock Restored - Logic requires persistence to be perfect)");
await Task.CompletedTask;
}
}
public class TwoPhaseCommitCoordinator
{
private readonly List<ITransactionParticipant> _participants;
public TwoPhaseCommitCoordinator(List<ITransactionParticipant> participants)
{
_participants = participants;
}
public async Task<bool> ExecuteTransactionAsync(TransactionContext context)
{
var transactionId = context.TransactionId;
var preparedParticipants = new List<ITransactionParticipant>();
Console.WriteLine($"========================================");
Console.WriteLine($"[Coordinator] Starting PREPARE phase for {transactionId}");
foreach (var participant in _participants)
{
var result = await participant.PrepareAsync(transactionId, context);
if (result == PrepareResult.Aborted)
{
Console.WriteLine($"[Coordinator] PREPARE FAILED for {transactionId}. Initiating Rollback...");
await AbortAsync(transactionId, preparedParticipants);
return false;
}
preparedParticipants.Add(participant);
}
Console.WriteLine($"[Coordinator] All participants PREPARED for {transactionId}");
Console.WriteLine($"[Coordinator] Starting COMMIT phase for {transactionId}");
try
{
foreach (var participant in _participants)
await participant.CommitAsync(transactionId);
Console.WriteLine($"[Coordinator] Transaction {transactionId} COMMITTED successfully");
Console.WriteLine($"========================================");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"[Coordinator] COMMIT FAILED: {ex.Message}");
Console.WriteLine($"[Coordinator] CRITICAL ERROR: Transaction is in doubt. Manual intervention may be required.");
throw;
}
}
private async Task AbortAsync(Guid transactionId, List<ITransactionParticipant> preparedParticipants)
{
foreach (var participant in preparedParticipants)
{
try
{
await participant.RollbackAsync(transactionId);
}
catch (Exception ex)
{
Console.WriteLine($"[Coordinator] Rollback failed for participant: {ex.Message}");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment