我正在研究应用CQRS的库存微服务。我们有一个储备库,可以使用特定的reservationId进行保留。因此1个StockLine可能只有1个具有相同保留ID的保留。如果进行了2个具有相同ReservationId的预订,则该预订的预订数量会增加。在阅读方面,尽管我应该在同一张表中显示可用的库存行+所有保留作为单独的行:
现在,我为读取模型实现了乐观并发。而且效果很好。查看代码:
public class Handler
{
public async Task Handle(StockReserved @event)
{
var optionsBuilder = new DbContextOptionsBuilder<StockLinesDbContext>();
optionsBuilder.UseSqlServer("Data Source=localhost;Initial Catalog=DBName;Integrated Security=True;");
using (var context = new StockLinesDbContext(optionsBuilder.Options))
{
var available = context.StockLines.Single(p => !p.IsReserved);
var existingReservation = context.StockLines.SingleOrDefault(p => p.Id == @event.ReservationId);
if (existingReservation != null)
{
existingReservation.Quantity += @event.Amount;
}
else
{
var res = new StockLine
{
Id = Guid.NewGuid(),
Quantity = @event.Amount,
IsReserved = true,
ReservationId = @event.ReservationId,
Product = "coke"
};
context.StockLines.Add(res);
}
available.Quantity -= @event.Amount;
var saved = false;
while (!saved)
{
try
{
await context.SaveChangesAsync();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is StockLine && !((StockLine)entry.Entity).IsReserved)
{
var proposedValues = entry.CurrentValues;
var databaseValues = (StockLine)entry.GetDatabaseValues().ToObject();
((StockLine)entry.Entity).Quantity = databaseValues.Quantity - @event.Amount;
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
if (entry.Entity is StockLine && ((StockLine)entry.Entity).IsReserved)
{
var proposedValues = entry.CurrentValues;
var databaseValues = (StockLine)entry.GetDatabaseValues().ToObject();
((StockLine)entry.Entity).Quantity = databaseValues.Quantity + @event.Amount;
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
}
}
}
}
}
}
现在,我为该处理程序添加了幂等支持。这个想法是在同一事务中尝试将eventId添加到单独的表中,如果该插入失败,则意味着该事件已被处理。
using (var transaction = connection.BeginTransaction())
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText =
$"Insert INTO dbo.ReadModel_Idempotency VALUES ('{ReadModelName}', '{@event.EventId}')";
try
{
command.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine($"FAILED TO INSERT: {@event.EventId}");
return;
}
添加了Idempotency支持后,乐观并发无法按需工作,仍然存在许多不一致数据。 完整的代码可以在这里找到:https://github.com/dmitribodiu/OptimisticConcurrencyTest 我添加幂等实现后为何不起作用呢?