使用分布式事件实现订单支付库存通知

This commit is contained in:
hello 2024-10-08 22:08:14 +08:00
parent c16d6ece15
commit d47ff517ad
8 changed files with 159 additions and 0 deletions

View File

@ -0,0 +1,42 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.DistributedEvents.EventHandling
{
public class OrderPaymentSucceededDistributedEventHandler(OrderingServiceDbContext dbContext, IDistributedEventBus distributedEventBus) : IDistributedEventHandler<OrderPaymentSucceededDistributedEvent>
{
public async Task HandleAsync(OrderPaymentSucceededDistributedEvent @event)
{
var strategy = dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var transaction = await dbContext.Database.BeginTransactionAsync();
DbSet<Order> orders = dbContext.Set<Order>();
Order order = await orders.FindAsync(@event.OrderId) ?? throw new Exception($"Order with id {@event.OrderId} not found");
await orders.Entry(order).Collection(i => i.OrderItems).LoadAsync();
order.OrderStatus = OrderStatus.Paid;
await dbContext.SaveChangesAsync();
await transaction.CommitAsync();
var orderStockList = order.OrderItems.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.Units));
var integrationEvent = new OrderPaidDistributedEvent(order.Id, orderStockList);
await distributedEventBus.PublishAsync(integrationEvent);
});
}
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{
public record OrderPaidDistributedEvent(int OrderId, IEnumerable<OrderStockItem> OrderStockItems) : DistributedEvent;
}

View File

@ -0,0 +1,14 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{
public record OrderPaymentFailedDistributedEvent : DistributedEvent
{
public int OrderId { get; }
public OrderPaymentFailedDistributedEvent(int orderId) => OrderId = orderId;
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{
public record OrderPaymentSucceededDistributedEvent(int OrderId) : DistributedEvent;
}

View File

@ -47,6 +47,7 @@ namespace HelloShop.OrderingService.Extensions
}); });
builder.Services.AddHostedService<GracePeriodWorker>(); builder.Services.AddHostedService<GracePeriodWorker>();
builder.Services.AddHostedService<PaymentWorker>();
} }
public static WebApplication MapApplicationEndpoints(this WebApplication app) public static WebApplication MapApplicationEndpoints(this WebApplication app)

View File

@ -0,0 +1,50 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.Workers
{
public class PaymentWorker(IServiceScopeFactory serviceScopeFactory, ILogger<PaymentWorker> logger) : BackgroundService
{
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (logger.IsEnabled(LogLevel.Information))
{
logger.LogInformation("{Worker} background task is doing background work.", GetType().Name);
}
using var scope = serviceScopeFactory.CreateAsyncScope();
var dbContext = scope.ServiceProvider.GetRequiredService<OrderingServiceDbContext>();
var distributedEventBus = scope.ServiceProvider.GetRequiredService<IDistributedEventBus>();
bool paymentSucceeded = Random.Shared.NextDouble() > 0.3;
try
{
var orders = await dbContext.Set<Order>().Include(o => o.OrderItems).Where(o => o.OrderStatus == OrderStatus.StockConfirmed).ToListAsync(stoppingToken);
foreach (var order in orders)
{
DistributedEvent @event = paymentSucceeded ? new OrderPaymentSucceededDistributedEvent(order.Id) : new OrderPaymentFailedDistributedEvent(order.Id);
await distributedEventBus.PublishAsync(@event, stoppingToken);
}
}
catch (Exception ex)
{
logger.LogError(ex, "Error occurred executing {Worker} background task.", GetType().Name);
}
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ProductService.DistributedEvents.Events;
using HelloShop.ProductService.Entities.Products;
using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.EventHandling
{
public class OrderPaidDistributedEventHandler(ProductServiceDbContext dbContext) : IDistributedEventHandler<OrderPaidDistributedEvent>
{
public async Task HandleAsync(OrderPaidDistributedEvent @event)
{
foreach (var orderStockItem in @event.OrderStockItems)
{
var product = await dbContext.Set<Product>().FindAsync(orderStockItem.ProductId) ?? throw new Exception($"Product with id {orderStockItem.ProductId} not found");
product.AvailableStock -= orderStockItem.Units;
}
await dbContext.SaveChangesAsync();
}
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{
public record OrderPaidDistributedEvent(int OrderId, IEnumerable<OrderStockItem> OrderStockItems) : DistributedEvent;
}