事务性发件箱模式
This commit is contained in:
		
							parent
							
								
									8da853a351
								
							
						
					
					
						commit
						f007572e7f
					
				| @ -29,9 +29,11 @@ | ||||
|     <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.2" /> | ||||
|     <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.3.0" /> | ||||
|     <PackageVersion Include="Microsoft.Extensions.Logging.Debug" Version="9.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.3" /> | ||||
|  | ||||
| @ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.DistributedLock", | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.DistributedLock.Dapr", "libraries\HelloShop.DistributedLock.Dapr\HelloShop.DistributedLock.Dapr.csproj", "{37F01A0B-50D6-48BA-8A20-FBADB5524CD7}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.EventBus.Logging", "libraries\HelloShop.EventBus.Logging\HelloShop.EventBus.Logging.csproj", "{ACBA2FA9-ADBB-4FCD-A303-6D8D4B2131AA}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| @ -125,6 +127,10 @@ Global | ||||
| 		{37F01A0B-50D6-48BA-8A20-FBADB5524CD7}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{37F01A0B-50D6-48BA-8A20-FBADB5524CD7}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{37F01A0B-50D6-48BA-8A20-FBADB5524CD7}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{ACBA2FA9-ADBB-4FCD-A303-6D8D4B2131AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{ACBA2FA9-ADBB-4FCD-A303-6D8D4B2131AA}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{ACBA2FA9-ADBB-4FCD-A303-6D8D4B2131AA}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{ACBA2FA9-ADBB-4FCD-A303-6D8D4B2131AA}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| @ -148,6 +154,7 @@ Global | ||||
| 		{5D403BF6-9267-4B0F-AEB3-2143C8BBA8CD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} | ||||
| 		{86729635-8E31-4C53-81AE-7C410C848219} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} | ||||
| 		{37F01A0B-50D6-48BA-8A20-FBADB5524CD7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} | ||||
| 		{ACBA2FA9-ADBB-4FCD-A303-6D8D4B2131AA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {845545A8-2006-46C3-ABD7-5BDF63F3858C} | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| 
 | ||||
| namespace HelloShop.OrderingService.Entities.EventLogs | ||||
| namespace HelloShop.EventBus.Logging | ||||
| { | ||||
|     public enum DistributedEventStatus { NotPublished, InProgress, Published, PublishedFailed } | ||||
| 
 | ||||
| @ -20,7 +20,7 @@ namespace HelloShop.OrderingService.Entities.EventLogs | ||||
| 
 | ||||
|         public int TimesSent { get; set; } | ||||
| 
 | ||||
|         public DateTimeOffset CreationTime { get; set; } = DateTimeOffset.UtcNow; | ||||
|         public DateTimeOffset CreationTime { get; init; } = TimeProvider.System.GetUtcNow(); | ||||
| 
 | ||||
|         public required Guid TransactionId { get; set; } | ||||
| 
 | ||||
| @ -0,0 +1,20 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| 
 | ||||
| namespace HelloShop.EventBus.Logging | ||||
| { | ||||
|     public static class DistributedEventLogExtensions | ||||
|     { | ||||
|         public static void UseDistributedEventLogs(this ModelBuilder builder) => builder.ApplyConfiguration(new EventLogEntityTypeConfiguration()); | ||||
| 
 | ||||
|         public static IServiceCollection AddDistributedEventLogs<TContext>([NotNull] this IServiceCollection services) where TContext : DbContext | ||||
|         { | ||||
|             services.AddTransient<IDistributedEventLogService, DistributedEventLogService<TContext>>().AddHostedService<DistributedEventWorker>(); | ||||
|             return services; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Storage; | ||||
| 
 | ||||
| namespace HelloShop.EventBus.Logging | ||||
| { | ||||
|     public class DistributedEventLogService<TContext>(TContext dbContext) : IDistributedEventLogService where TContext : DbContext | ||||
|     { | ||||
|         public async Task<IEnumerable<DistributedEventLog>> RetrieveEventLogsPendingToPublishAsync(Guid transactionId, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             return await dbContext.Set<DistributedEventLog>().Where(e => e.TransactionId == transactionId && e.Status == DistributedEventStatus.NotPublished).ToListAsync(cancellationToken: cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<IEnumerable<DistributedEventLog>> RetrieveEventLogsFailedToPublishAsync(CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             return await dbContext.Set<DistributedEventLog>().Where(e => e.Status == DistributedEventStatus.PublishedFailed).ToListAsync(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public async Task SaveEventAsync(DistributedEvent @event, IDbContextTransaction transaction, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var eventLog = new DistributedEventLog(@event, transaction.TransactionId); | ||||
|             dbContext.Database.UseTransaction(transaction.GetDbTransaction()); | ||||
|             await dbContext.Set<DistributedEventLog>().AddAsync(eventLog, cancellationToken); | ||||
| 
 | ||||
|             await dbContext.SaveChangesAsync(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public async Task UpdateEventStatusAsync(Guid eventId, DistributedEventStatus status, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var eventLogEntry = dbContext.Set<DistributedEventLog>().Single(ie => ie.EventId == eventId); | ||||
| 
 | ||||
|             eventLogEntry.Status = status; | ||||
| 
 | ||||
|             if (status == DistributedEventStatus.InProgress) | ||||
|             { | ||||
|                 eventLogEntry.TimesSent++; | ||||
|             } | ||||
| 
 | ||||
|             await dbContext.SaveChangesAsync(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public Task MarkEventAsPublishedAsync(Guid eventId, CancellationToken cancellationToken = default) => UpdateEventStatusAsync(eventId, DistributedEventStatus.Published, cancellationToken); | ||||
| 
 | ||||
|         public Task MarkEventAsInProgressAsync(Guid eventId, CancellationToken cancellationToken = default) => UpdateEventStatusAsync(eventId, DistributedEventStatus.InProgress, cancellationToken); | ||||
| 
 | ||||
|         public Task MarkEventAsFailedAsync(Guid eventId, CancellationToken cancellationToken = default) => UpdateEventStatusAsync(eventId, DistributedEventStatus.PublishedFailed, cancellationToken); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
| 
 | ||||
| namespace HelloShop.EventBus.Logging | ||||
| { | ||||
|     public class DistributedEventWorker(IServiceScopeFactory serviceScopeFactory) : BackgroundService | ||||
|     { | ||||
|         protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|         { | ||||
|             while (!stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 using var scope = serviceScopeFactory.CreateScope(); | ||||
| 
 | ||||
|                 var eventBus = scope.ServiceProvider.GetRequiredService<IEventBus>(); | ||||
|                 var eventLogService = scope.ServiceProvider.GetRequiredService<IDistributedEventLogService>(); | ||||
|                 var logger = scope.ServiceProvider.GetRequiredService<ILogger<DistributedEventWorker>>(); | ||||
| 
 | ||||
|                 try | ||||
|                 { | ||||
|                     var failedEventLogs = await eventLogService.RetrieveEventLogsFailedToPublishAsync(stoppingToken); | ||||
| 
 | ||||
|                     foreach (var eventLog in failedEventLogs) | ||||
|                     { | ||||
|                         DistributedEvent @event = eventLog.DistributedEvent; | ||||
| 
 | ||||
|                         try | ||||
|                         { | ||||
|                             await eventLogService.MarkEventAsInProgressAsync(@event.Id, stoppingToken); | ||||
|                             await eventBus.PublishAsync(@event, stoppingToken); | ||||
|                             await eventLogService.MarkEventAsPublishedAsync(@event.Id, stoppingToken); | ||||
|                         } | ||||
|                         catch (Exception ex) | ||||
|                         { | ||||
|                             logger.LogError(ex, "Publish through event bus failed for {EventId}.", @event.Id); | ||||
|                             await eventLogService.MarkEventAsFailedAsync(@event.Id, stoppingToken); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     logger.LogError(ex, "An error occurred while retrieving failed event logs."); | ||||
|                 } | ||||
| 
 | ||||
|                 await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -2,24 +2,23 @@ | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using HelloShop.OrderingService.Entities.EventLogs; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||||
| using System.Text.Json; | ||||
| 
 | ||||
| namespace HelloShop.OrderingService.Infrastructure.EntityConfigurations.EventLogs | ||||
| namespace HelloShop.EventBus.Logging | ||||
| { | ||||
|     public class DistributedEventLogEntityTypeConfiguration : IEntityTypeConfiguration<DistributedEventLog> | ||||
|     public class EventLogEntityTypeConfiguration : IEntityTypeConfiguration<DistributedEventLog> | ||||
|     { | ||||
|         private static readonly JsonSerializerOptions s_jsonSerializerOptions = new(JsonSerializerDefaults.Web); | ||||
| 
 | ||||
|         public void Configure(EntityTypeBuilder<DistributedEventLog> builder) | ||||
|         { | ||||
|             builder.HasKey(x => x.EventId); | ||||
|             builder.Property(x => x.EventTypeName).HasMaxLength(32); | ||||
|             builder.Property(x => x.EventTypeName).HasMaxLength(64); | ||||
|             builder.Property(x => x.Status).HasConversion<string>(); | ||||
| 
 | ||||
|             builder.Property(x => x.DistributedEvent).HasConversion(v => JsonSerializer.Serialize(v, v.GetType(), s_jsonSerializerOptions), v => JsonSerializer.Deserialize<DistributedEvent>(v, s_jsonSerializerOptions)!); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net9.0</TargetFramework> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <Nullable>enable</Nullable> | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\HelloShop.EventBus.Abstractions\HelloShop.EventBus.Abstractions.csproj" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
| @ -0,0 +1,23 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using Microsoft.EntityFrameworkCore.Storage; | ||||
| 
 | ||||
| namespace HelloShop.EventBus.Logging | ||||
| { | ||||
|     public interface IDistributedEventLogService | ||||
|     { | ||||
|         Task<IEnumerable<DistributedEventLog>> RetrieveEventLogsPendingToPublishAsync(Guid transactionId, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task<IEnumerable<DistributedEventLog>> RetrieveEventLogsFailedToPublishAsync(CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task SaveEventAsync(DistributedEvent @event, IDbContextTransaction transaction, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task MarkEventAsPublishedAsync(Guid eventId, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task MarkEventAsInProgressAsync(Guid eventId, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task MarkEventAsFailedAsync(Guid eventId, CancellationToken cancellationToken = default); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								libraries/HelloShop.EventBus.Logging/ResilientTransaction.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								libraries/HelloShop.EventBus.Logging/ResilientTransaction.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| 
 | ||||
| namespace HelloShop.EventBus.Logging | ||||
| { | ||||
|     public class ResilientTransaction(DbContext dbContext) | ||||
|     { | ||||
|         public static ResilientTransaction New(DbContext context) => new(context); | ||||
| 
 | ||||
|         public async Task ExecuteAsync(Func<Task> action) | ||||
|         { | ||||
|             var strategy = dbContext.Database.CreateExecutionStrategy(); | ||||
|             await strategy.ExecuteAsync(async () => | ||||
|             { | ||||
|                 await using var transaction = await dbContext.Database.BeginTransactionAsync(); | ||||
|                 await action(); | ||||
|                 await transaction.CommitAsync(); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -7,7 +7,7 @@ namespace HelloShop.EventBus.RabbitMQ | ||||
|     { | ||||
|         public string ExchangeName { get; set; } = "event-bus-exchange"; | ||||
| 
 | ||||
|         public required string QueueName { get; set; } | ||||
|         public required string QueueName { get; set; } = "event-bus-queue"; | ||||
| 
 | ||||
|         public int RetryCount { get; set; } = 10; | ||||
|     } | ||||
|  | ||||
| @ -41,7 +41,7 @@ namespace HelloShop.OrderingService.Behaviors | ||||
| 
 | ||||
|                         _logger.LogInformation("Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName); | ||||
|                     } | ||||
| 
 | ||||
|                     await dbContext.SaveChangesAsync(cancellationToken); | ||||
|                     await transaction.CommitAsync(); | ||||
| 
 | ||||
|                     await eventService.PublishEventsThroughEventBusAsync(transaction.TransactionId, cancellationToken); | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using HelloShop.EventBus.Dapr; | ||||
| using HelloShop.EventBus.Logging; | ||||
| using HelloShop.OrderingService.Behaviors; | ||||
| using HelloShop.OrderingService.Constants; | ||||
| using HelloShop.OrderingService.Infrastructure; | ||||
| @ -67,7 +68,7 @@ namespace HelloShop.OrderingService.Extensions | ||||
|             builder.Services.AddHostedService<GracePeriodWorker>(); | ||||
|             builder.Services.AddHostedService<PaymentWorker>(); | ||||
| 
 | ||||
|             builder.Services.AddTransient<IDistributedEventService, DistributedEventService<OrderingServiceDbContext>>(); | ||||
|             builder.Services.AddDistributedEventLogs<OrderingServiceDbContext>().AddTransient<IDistributedEventService, DistributedEventService>(); | ||||
| 
 | ||||
|             builder.Services.AddOpenApi(); | ||||
| 
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\..\libraries\HelloShop.EventBus.Dapr\HelloShop.EventBus.Dapr.csproj" /> | ||||
|     <ProjectReference Include="..\..\libraries\HelloShop.EventBus.Logging\HelloShop.EventBus.Logging.csproj" /> | ||||
|     <ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|  | ||||
| @ -1,9 +1,12 @@ | ||||
| @HelloShop.OrderingService_HostAddress = https://localhost:8105 | ||||
| @HelloWorld.OrderingService_HostAddress = https://localhost:8105 | ||||
| @AccessToken = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3NDI4Njg1ODAsImV4cCI6MTc2NDkwMDU4MCwiaWF0IjoxNzQyODY4NTgwfQ.6Wm6S4CNtKi9lGqxam4_ZnDebnTXVxycDubbv0DLy2c | ||||
| 
 | ||||
| POST {{HelloShop.OrderingService_HostAddress}}/api/orders | ||||
| ### | ||||
| 
 | ||||
| POST {{HelloWorld.OrderingService_HostAddress}}/api/orders | ||||
| Content-Type: application/json | ||||
| x-request-id: 8ddc1737-6a31-4b4f-bccd-63d0966348a1 | ||||
| Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3MjA1NzY3NDYsImV4cCI6MTc0MjYwODc0NiwiaWF0IjoxNzIwNTc2NzQ2fQ.ju_D3zeGLKqJYVckbb8Y3yNkp40nOqRAJrdOsISs4d4 | ||||
| x-request-id: 1ddc1737-6a32-4b4f-bccd-63d0966348a3 | ||||
| Authorization : Bearer {{AccessToken}} | ||||
| 
 | ||||
| { | ||||
|   "country": "USA", | ||||
| @ -36,12 +39,20 @@ Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwi | ||||
| 
 | ||||
| ### | ||||
| 
 | ||||
| GET {{HelloShop.OrderingService_HostAddress}}/api/orders | ||||
| GET {{HelloWorld.OrderingService_HostAddress}}/api/orders | ||||
| Content-Type: application/json | ||||
| Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3MjA1NzY3NDYsImV4cCI6MTc0MjYwODc0NiwiaWF0IjoxNzIwNTc2NzQ2fQ.ju_D3zeGLKqJYVckbb8Y3yNkp40nOqRAJrdOsISs4d4 | ||||
| Authorization : Bearer {{AccessToken}} | ||||
| 
 | ||||
| ### | ||||
| 
 | ||||
| GET {{HelloShop.OrderingService_HostAddress}}/api/orders/1 | ||||
| GET {{HelloWorld.OrderingService_HostAddress}}/api/orders/1 | ||||
| Content-Type: application/json | ||||
| Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3MjA1NzY3NDYsImV4cCI6MTc0MjYwODc0NiwiaWF0IjoxNzIwNTc2NzQ2fQ.ju_D3zeGLKqJYVckbb8Y3yNkp40nOqRAJrdOsISs4d4 | ||||
| Authorization : Bearer {{AccessToken}} | ||||
| 
 | ||||
| ### | ||||
| 
 | ||||
| 
 | ||||
| PUT {{HelloWorld.OrderingService_HostAddress}}/api/orders/ship/1 | ||||
| Content-Type: application/json | ||||
| x-request-id: 1ddc1737-6a32-4b4f-bccd-63d0966348a1 | ||||
| Authorization : Bearer {{AccessToken}} | ||||
|  | ||||
							
								
								
									
										347
									
								
								src/HelloShop.OrderingService/Infrastructure/Migrations/20250328130944_AddEventLogTable.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										347
									
								
								src/HelloShop.OrderingService/Infrastructure/Migrations/20250328130944_AddEventLogTable.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,347 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using HelloShop.OrderingService.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace HelloShop.OrderingService.Infrastructure.Migrations | ||||
| { | ||||
|     [DbContext(typeof(OrderingServiceDbContext))] | ||||
|     [Migration("20250328130944_AddEventLogTable")] | ||||
|     partial class AddEventLogTable | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "9.0.3") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
| 
 | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.EventBus.Logging.DistributedEventLog", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("EventId") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("event_id"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("CreationTime") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("creation_time"); | ||||
| 
 | ||||
|                     b.Property<string>("DistributedEvent") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("distributed_event"); | ||||
| 
 | ||||
|                     b.Property<string>("EventTypeName") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(64) | ||||
|                         .HasColumnType("character varying(64)") | ||||
|                         .HasColumnName("event_type_name"); | ||||
| 
 | ||||
|                     b.Property<string>("Status") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("status"); | ||||
| 
 | ||||
|                     b.Property<int>("TimesSent") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("times_sent"); | ||||
| 
 | ||||
|                     b.Property<Guid>("TransactionId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("transaction_id"); | ||||
| 
 | ||||
|                     b.HasKey("EventId") | ||||
|                         .HasName("pk_distributed_event_log"); | ||||
| 
 | ||||
|                     b.ToTable("distributed_event_log", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Buyers.Buyer", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(16) | ||||
|                         .HasColumnType("character varying(16)") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_buyer"); | ||||
| 
 | ||||
|                     b.ToTable("buyer", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Buyers.PaymentMethod", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<string>("Alias") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(16) | ||||
|                         .HasColumnType("character varying(16)") | ||||
|                         .HasColumnName("alias"); | ||||
| 
 | ||||
|                     b.Property<int>("BuyerId") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("buyer_id"); | ||||
| 
 | ||||
|                     b.Property<string>("CardHolderName") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(16) | ||||
|                         .HasColumnType("character varying(16)") | ||||
|                         .HasColumnName("card_holder_name"); | ||||
| 
 | ||||
|                     b.Property<string>("CardNumber") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(16) | ||||
|                         .HasColumnType("character varying(16)") | ||||
|                         .HasColumnName("card_number"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset?>("Expiration") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("expiration"); | ||||
| 
 | ||||
|                     b.Property<string>("SecurityNumber") | ||||
|                         .HasMaxLength(6) | ||||
|                         .HasColumnType("character varying(6)") | ||||
|                         .HasColumnName("security_number"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_payment_method"); | ||||
| 
 | ||||
|                     b.HasIndex("BuyerId") | ||||
|                         .HasDatabaseName("ix_payment_method_buyer_id"); | ||||
| 
 | ||||
|                     b.ToTable("payment_method", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Idempotency.ClientRequest", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(64) | ||||
|                         .HasColumnType("character varying(64)") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("Time") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("time"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_client_request"); | ||||
| 
 | ||||
|                     b.ToTable("client_request", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Orders.Order", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<int>("BuyerId") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("buyer_id"); | ||||
| 
 | ||||
|                     b.Property<string>("Description") | ||||
|                         .HasMaxLength(64) | ||||
|                         .HasColumnType("character varying(64)") | ||||
|                         .HasColumnName("description"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("OrderDate") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("order_date"); | ||||
| 
 | ||||
|                     b.Property<string>("OrderStatus") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("order_status"); | ||||
| 
 | ||||
|                     b.Property<int?>("PaymentMethodId") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("payment_method_id"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_order"); | ||||
| 
 | ||||
|                     b.HasIndex("BuyerId") | ||||
|                         .HasDatabaseName("ix_order_buyer_id"); | ||||
| 
 | ||||
|                     b.HasIndex("PaymentMethodId") | ||||
|                         .HasDatabaseName("ix_order_payment_method_id"); | ||||
| 
 | ||||
|                     b.ToTable("order", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Orders.OrderItem", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<int>("OrderId") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("order_id"); | ||||
| 
 | ||||
|                     b.Property<string>("PictureUrl") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("picture_url"); | ||||
| 
 | ||||
|                     b.Property<int>("ProductId") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("product_id"); | ||||
| 
 | ||||
|                     b.Property<string>("ProductName") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(16) | ||||
|                         .HasColumnType("character varying(16)") | ||||
|                         .HasColumnName("product_name"); | ||||
| 
 | ||||
|                     b.Property<decimal>("UnitPrice") | ||||
|                         .HasColumnType("numeric") | ||||
|                         .HasColumnName("unit_price"); | ||||
| 
 | ||||
|                     b.Property<int>("Units") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("units"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_order_item"); | ||||
| 
 | ||||
|                     b.HasIndex("OrderId") | ||||
|                         .HasDatabaseName("ix_order_item_order_id"); | ||||
| 
 | ||||
|                     b.ToTable("order_item", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Buyers.PaymentMethod", b => | ||||
|                 { | ||||
|                     b.HasOne("HelloShop.OrderingService.Entities.Buyers.Buyer", null) | ||||
|                         .WithMany("PaymentMethods") | ||||
|                         .HasForeignKey("BuyerId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_payment_method_buyer_buyer_id"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Orders.Order", b => | ||||
|                 { | ||||
|                     b.HasOne("HelloShop.OrderingService.Entities.Buyers.Buyer", null) | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("BuyerId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_order_buyer_buyer_id"); | ||||
| 
 | ||||
|                     b.HasOne("HelloShop.OrderingService.Entities.Buyers.PaymentMethod", null) | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("PaymentMethodId") | ||||
|                         .OnDelete(DeleteBehavior.Restrict) | ||||
|                         .HasConstraintName("fk_order_payment_method_payment_method_id"); | ||||
| 
 | ||||
|                     b.OwnsOne("HelloShop.OrderingService.Entities.Orders.Address", "Address", b1 => | ||||
|                         { | ||||
|                             b1.Property<int>("OrderId") | ||||
|                                 .HasColumnType("integer") | ||||
|                                 .HasColumnName("id"); | ||||
| 
 | ||||
|                             b1.Property<string>("City") | ||||
|                                 .IsRequired() | ||||
|                                 .HasMaxLength(16) | ||||
|                                 .HasColumnType("character varying(16)") | ||||
|                                 .HasColumnName("City"); | ||||
| 
 | ||||
|                             b1.Property<string>("Country") | ||||
|                                 .IsRequired() | ||||
|                                 .HasMaxLength(8) | ||||
|                                 .HasColumnType("character varying(8)") | ||||
|                                 .HasColumnName("Country"); | ||||
| 
 | ||||
|                             b1.Property<string>("State") | ||||
|                                 .IsRequired() | ||||
|                                 .HasMaxLength(16) | ||||
|                                 .HasColumnType("character varying(16)") | ||||
|                                 .HasColumnName("State"); | ||||
| 
 | ||||
|                             b1.Property<string>("Street") | ||||
|                                 .IsRequired() | ||||
|                                 .HasMaxLength(32) | ||||
|                                 .HasColumnType("character varying(32)") | ||||
|                                 .HasColumnName("Street"); | ||||
| 
 | ||||
|                             b1.Property<string>("ZipCode") | ||||
|                                 .IsRequired() | ||||
|                                 .HasMaxLength(6) | ||||
|                                 .HasColumnType("character varying(6)") | ||||
|                                 .HasColumnName("ZipCode"); | ||||
| 
 | ||||
|                             b1.HasKey("OrderId"); | ||||
| 
 | ||||
|                             b1.ToTable("order"); | ||||
| 
 | ||||
|                             b1.WithOwner() | ||||
|                                 .HasForeignKey("OrderId") | ||||
|                                 .HasConstraintName("fk_order_order_id"); | ||||
|                         }); | ||||
| 
 | ||||
|                     b.Navigation("Address") | ||||
|                         .IsRequired(); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Orders.OrderItem", b => | ||||
|                 { | ||||
|                     b.HasOne("HelloShop.OrderingService.Entities.Orders.Order", null) | ||||
|                         .WithMany("OrderItems") | ||||
|                         .HasForeignKey("OrderId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_order_item_order_order_id"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Buyers.Buyer", b => | ||||
|                 { | ||||
|                     b.Navigation("PaymentMethods"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Orders.Order", b => | ||||
|                 { | ||||
|                     b.Navigation("OrderItems"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,41 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace HelloShop.OrderingService.Infrastructure.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddEventLogTable : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AlterColumn<string>( | ||||
|                 name: "event_type_name", | ||||
|                 table: "distributed_event_log", | ||||
|                 type: "character varying(64)", | ||||
|                 maxLength: 64, | ||||
|                 nullable: false, | ||||
|                 oldClrType: typeof(string), | ||||
|                 oldType: "character varying(32)", | ||||
|                 oldMaxLength: 32); | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AlterColumn<string>( | ||||
|                 name: "event_type_name", | ||||
|                 table: "distributed_event_log", | ||||
|                 type: "character varying(32)", | ||||
|                 maxLength: 32, | ||||
|                 nullable: false, | ||||
|                 oldClrType: typeof(string), | ||||
|                 oldType: "character varying(64)", | ||||
|                 oldMaxLength: 64); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -22,6 +22,47 @@ namespace HelloShop.OrderingService.Infrastructure.Migrations | ||||
| 
 | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.EventBus.Logging.DistributedEventLog", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("EventId") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("event_id"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("CreationTime") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("creation_time"); | ||||
| 
 | ||||
|                     b.Property<string>("DistributedEvent") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("distributed_event"); | ||||
| 
 | ||||
|                     b.Property<string>("EventTypeName") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(64) | ||||
|                         .HasColumnType("character varying(64)") | ||||
|                         .HasColumnName("event_type_name"); | ||||
| 
 | ||||
|                     b.Property<string>("Status") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("status"); | ||||
| 
 | ||||
|                     b.Property<int>("TimesSent") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("times_sent"); | ||||
| 
 | ||||
|                     b.Property<Guid>("TransactionId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("transaction_id"); | ||||
| 
 | ||||
|                     b.HasKey("EventId") | ||||
|                         .HasName("pk_distributed_event_log"); | ||||
| 
 | ||||
|                     b.ToTable("distributed_event_log", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Buyers.Buyer", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
| @ -92,47 +133,6 @@ namespace HelloShop.OrderingService.Infrastructure.Migrations | ||||
|                     b.ToTable("payment_method", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.EventLogs.DistributedEventLog", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("EventId") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("event_id"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("CreationTime") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("creation_time"); | ||||
| 
 | ||||
|                     b.Property<string>("DistributedEvent") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("distributed_event"); | ||||
| 
 | ||||
|                     b.Property<string>("EventTypeName") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(32) | ||||
|                         .HasColumnType("character varying(32)") | ||||
|                         .HasColumnName("event_type_name"); | ||||
| 
 | ||||
|                     b.Property<string>("Status") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("status"); | ||||
| 
 | ||||
|                     b.Property<int>("TimesSent") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("times_sent"); | ||||
| 
 | ||||
|                     b.Property<Guid>("TransactionId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("transaction_id"); | ||||
| 
 | ||||
|                     b.HasKey("EventId") | ||||
|                         .HasName("pk_distributed_event_log"); | ||||
| 
 | ||||
|                     b.ToTable("distributed_event_log", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.OrderingService.Entities.Idempotency.ClientRequest", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Logging; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Reflection; | ||||
| 
 | ||||
| @ -13,6 +14,7 @@ namespace HelloShop.OrderingService.Infrastructure | ||||
|             base.OnModelCreating(builder); | ||||
| 
 | ||||
|             builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); | ||||
|             builder.UseDistributedEventLogs(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,88 +2,42 @@ | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using HelloShop.OrderingService.Entities.EventLogs; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using HelloShop.EventBus.Logging; | ||||
| using HelloShop.OrderingService.Infrastructure; | ||||
| 
 | ||||
| namespace HelloShop.OrderingService.Services | ||||
| { | ||||
|     public class DistributedEventService<TContext>(TContext dbContext, IEventBus distributedEventBus, ILogger<DistributedEventService<TContext>> logger) : IDistributedEventService, IDisposable where TContext : DbContext | ||||
|     public class DistributedEventService(ILogger<DistributedEventService> logger, IEventBus eventBus, OrderingServiceDbContext dbContext, IDistributedEventLogService eventLogService) : IDistributedEventService | ||||
|     { | ||||
|         private volatile bool _disposedValue; | ||||
| 
 | ||||
|         public async Task UpdateEventStatusAsync(Guid eventId, DistributedEventStatus status, CancellationToken cancellationToken = default) | ||||
|         public async Task PublishEventsThroughEventBusAsync(Guid transactionId, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var eventLogEntry = dbContext.Set<DistributedEventLog>().Single(ie => ie.EventId == eventId); | ||||
|             var pendingLogEvents = await eventLogService.RetrieveEventLogsPendingToPublishAsync(transactionId, cancellationToken); | ||||
| 
 | ||||
|             eventLogEntry.Status = status; | ||||
| 
 | ||||
|             if (status == DistributedEventStatus.InProgress) | ||||
|             foreach (var eventLog in pendingLogEvents) | ||||
|             { | ||||
|                 eventLogEntry.TimesSent++; | ||||
|                 logger.LogInformation("Publishing distributed event: {EventId} from {EventType}.", eventLog.EventId, eventLog.EventTypeName); | ||||
| 
 | ||||
|                 try | ||||
|                 { | ||||
|                     await eventLogService.MarkEventAsInProgressAsync(eventLog.EventId, cancellationToken); | ||||
|                     await eventBus.PublishAsync(eventLog.DistributedEvent, cancellationToken); | ||||
|                     await eventLogService.MarkEventAsPublishedAsync(eventLog.EventId, cancellationToken); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     logger.LogError(ex, "Error publishing distributed event: {EventId}.", eventLog.EventId); | ||||
|                     await eventLogService.MarkEventAsFailedAsync(eventLog.EventId, cancellationToken); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             await dbContext.SaveChangesAsync(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<IEnumerable<DistributedEventLog>> RetrieveEventLogsPendingToPublishAsync(Guid transactionId, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var result = await dbContext.Set<DistributedEventLog>().Where(e => e.TransactionId == transactionId && e.Status == DistributedEventStatus.NotPublished).ToListAsync(cancellationToken: cancellationToken); | ||||
| 
 | ||||
|             return result.Count != 0 ? result.OrderBy(o => o.CreationTime) : []; | ||||
|         } | ||||
| 
 | ||||
|         public async Task AddAndSaveEventAsync(DistributedEvent @event, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var transaction = dbContext.Database.CurrentTransaction ?? throw new InvalidOperationException("This method must be called within a transaction scope."); | ||||
|             logger.LogInformation("Creating and saving distributed event: {EventId} from {EventType}.", @event.Id, @event.GetType().Name); | ||||
| 
 | ||||
|             var eventLog = new DistributedEventLog(@event, transaction.TransactionId); | ||||
|             var transaction = dbContext.Database.CurrentTransaction ?? await dbContext.Database.BeginTransactionAsync(cancellationToken); | ||||
| 
 | ||||
|             await dbContext.AddAsync(eventLog, cancellationToken); | ||||
| 
 | ||||
|             await dbContext.SaveChangesAsync(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public async Task PublishEventsThroughEventBusAsync(Guid transactionId, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             var pendingEventLogs = await RetrieveEventLogsPendingToPublishAsync(transactionId, cancellationToken); | ||||
| 
 | ||||
|             foreach (var eventLog in pendingEventLogs) | ||||
|             { | ||||
|                 logger.LogInformation("Publishing integration event {EventId} {DistributedEvent}", eventLog.EventId, eventLog.DistributedEvent); | ||||
| 
 | ||||
|                 try | ||||
|                 { | ||||
|                     await UpdateEventStatusAsync(eventLog.EventId, DistributedEventStatus.InProgress, cancellationToken); | ||||
|                     await distributedEventBus.PublishAsync(eventLog.DistributedEvent, cancellationToken); | ||||
|                     await UpdateEventStatusAsync(eventLog.EventId, DistributedEventStatus.Published, cancellationToken); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     logger.LogError(ex, "Error publishing distributed event {EventId}", eventLog.EventId); | ||||
| 
 | ||||
|                     await UpdateEventStatusAsync(eventLog.EventId, DistributedEventStatus.PublishedFailed, cancellationToken); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void Dispose(bool disposing) | ||||
|         { | ||||
|             if (!_disposedValue) | ||||
|             { | ||||
|                 if (disposing) | ||||
|                 { | ||||
|                     dbContext.Dispose(); | ||||
|                 } | ||||
| 
 | ||||
|                 _disposedValue = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void Dispose() | ||||
|         { | ||||
|             Dispose(disposing: true); | ||||
|             GC.SuppressFinalize(this); | ||||
|             await eventLogService.SaveEventAsync(@event, transaction, cancellationToken); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -2,18 +2,13 @@ | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using HelloShop.OrderingService.Entities.EventLogs; | ||||
| 
 | ||||
| namespace HelloShop.OrderingService.Services | ||||
| { | ||||
|     public interface IDistributedEventService | ||||
|     { | ||||
|         Task<IEnumerable<DistributedEventLog>> RetrieveEventLogsPendingToPublishAsync(Guid transactionId, CancellationToken cancellationToken = default); | ||||
|         Task PublishEventsThroughEventBusAsync(Guid transactionId, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task AddAndSaveEventAsync(DistributedEvent @event, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task UpdateEventStatusAsync(Guid eventId, DistributedEventStatus status, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|         Task PublishEventsThroughEventBusAsync(Guid transactionId, CancellationToken cancellationToken = default); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,10 +6,11 @@ using HelloShop.EventBus.Abstractions; | ||||
| using HelloShop.ProductService.DistributedEvents.Events; | ||||
| using HelloShop.ProductService.Entities.Products; | ||||
| using HelloShop.ProductService.Infrastructure; | ||||
| using HelloShop.ProductService.Services; | ||||
| 
 | ||||
| namespace HelloShop.ProductService.DistributedEvents.EventHandling | ||||
| { | ||||
|     public class OrderAwaitingValidationDistributedEventHandler(ProductServiceDbContext dbContext, IEventBus distributedEventBus, IDistributedLock distributedLock, ILogger<OrderAwaitingValidationDistributedEventHandler> logger) : IDistributedEventHandler<OrderAwaitingValidationDistributedEvent> | ||||
|     public class OrderAwaitingValidationDistributedEventHandler(ProductServiceDbContext dbContext, IDistributedEventService distributedEventService, IDistributedLock distributedLock, ILogger<OrderAwaitingValidationDistributedEventHandler> logger) : IDistributedEventHandler<OrderAwaitingValidationDistributedEvent> | ||||
|     { | ||||
|         public async Task HandleAsync(OrderAwaitingValidationDistributedEvent @event) | ||||
|         { | ||||
| @ -30,7 +31,8 @@ namespace HelloShop.ProductService.DistributedEvents.EventHandling | ||||
| 
 | ||||
|             DistributedEvent confirmedEvent = confirmedOrderStockItems.All(c => c.Value) ? new OrderStockConfirmedDistributedEvent(@event.OrderId) : new OrderStockRejectedDistributedEvent(@event.OrderId); | ||||
| 
 | ||||
|             await distributedEventBus.PublishAsync(confirmedEvent); | ||||
|             await distributedEventService.SaveEventAndDbContextChangesAsync(confirmedEvent); | ||||
|             await distributedEventService.PublishThroughEventBusAsync(confirmedEvent); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\..\libraries\HelloShop.DistributedLock.Dapr\HelloShop.DistributedLock.Dapr.csproj" /> | ||||
|     <ProjectReference Include="..\..\libraries\HelloShop.EventBus.Dapr\HelloShop.EventBus.Dapr.csproj" /> | ||||
|     <ProjectReference Include="..\..\libraries\HelloShop.EventBus.Logging\HelloShop.EventBus.Logging.csproj" /> | ||||
|     <ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|  | ||||
							
								
								
									
										155
									
								
								src/HelloShop.ProductService/Infrastructure/Migrations/20250328131028_AddEventLogTable.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/HelloShop.ProductService/Infrastructure/Migrations/20250328131028_AddEventLogTable.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,155 @@ | ||||
| // <auto-generated /> | ||||
| using System; | ||||
| using HelloShop.ProductService.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace HelloShop.ProductService.Infrastructure.Migrations | ||||
| { | ||||
|     [DbContext(typeof(ProductServiceDbContext))] | ||||
|     [Migration("20250328131028_AddEventLogTable")] | ||||
|     partial class AddEventLogTable | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "9.0.3") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
| 
 | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.EventBus.Logging.DistributedEventLog", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("EventId") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("event_id"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("CreationTime") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("creation_time"); | ||||
| 
 | ||||
|                     b.Property<string>("DistributedEvent") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("distributed_event"); | ||||
| 
 | ||||
|                     b.Property<string>("EventTypeName") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(64) | ||||
|                         .HasColumnType("character varying(64)") | ||||
|                         .HasColumnName("event_type_name"); | ||||
| 
 | ||||
|                     b.Property<string>("Status") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("status"); | ||||
| 
 | ||||
|                     b.Property<int>("TimesSent") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("times_sent"); | ||||
| 
 | ||||
|                     b.Property<Guid>("TransactionId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("transaction_id"); | ||||
| 
 | ||||
|                     b.HasKey("EventId") | ||||
|                         .HasName("pk_distributed_event_log"); | ||||
| 
 | ||||
|                     b.ToTable("distributed_event_log", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Brand", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(32) | ||||
|                         .HasColumnType("character varying(32)") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_brand"); | ||||
| 
 | ||||
|                     b.ToTable("brand", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Product", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<int>("AvailableStock") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("available_stock"); | ||||
| 
 | ||||
|                     b.Property<int>("BrandId") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("brand_id"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("CreationTime") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("creation_time"); | ||||
| 
 | ||||
|                     b.Property<string>("Description") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("description"); | ||||
| 
 | ||||
|                     b.Property<string>("ImageUrl") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(256) | ||||
|                         .HasColumnType("character varying(256)") | ||||
|                         .HasColumnName("image_url"); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(64) | ||||
|                         .HasColumnType("character varying(64)") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.Property<decimal>("Price") | ||||
|                         .HasColumnType("numeric") | ||||
|                         .HasColumnName("price"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_product"); | ||||
| 
 | ||||
|                     b.HasIndex("BrandId") | ||||
|                         .HasDatabaseName("ix_product_brand_id"); | ||||
| 
 | ||||
|                     b.ToTable("product", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Product", b => | ||||
|                 { | ||||
|                     b.HasOne("HelloShop.ProductService.Entities.Products.Brand", "Brand") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("BrandId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_product_brand_brand_id"); | ||||
| 
 | ||||
|                     b.Navigation("Brand"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,41 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace HelloShop.ProductService.Infrastructure.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddEventLogTable : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "distributed_event_log", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     event_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     event_type_name = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false), | ||||
|                     distributed_event = table.Column<string>(type: "text", nullable: false), | ||||
|                     status = table.Column<string>(type: "text", nullable: false), | ||||
|                     times_sent = table.Column<int>(type: "integer", nullable: false), | ||||
|                     creation_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false), | ||||
|                     transaction_id = table.Column<Guid>(type: "uuid", nullable: false) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_distributed_event_log", x => x.event_id); | ||||
|                 }); | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "distributed_event_log"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -22,6 +22,47 @@ namespace HelloShop.ProductService.Infrastructure.Migrations | ||||
| 
 | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.EventBus.Logging.DistributedEventLog", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("EventId") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("event_id"); | ||||
| 
 | ||||
|                     b.Property<DateTimeOffset>("CreationTime") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("creation_time"); | ||||
| 
 | ||||
|                     b.Property<string>("DistributedEvent") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("distributed_event"); | ||||
| 
 | ||||
|                     b.Property<string>("EventTypeName") | ||||
|                         .IsRequired() | ||||
|                         .HasMaxLength(64) | ||||
|                         .HasColumnType("character varying(64)") | ||||
|                         .HasColumnName("event_type_name"); | ||||
| 
 | ||||
|                     b.Property<string>("Status") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("status"); | ||||
| 
 | ||||
|                     b.Property<int>("TimesSent") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("times_sent"); | ||||
| 
 | ||||
|                     b.Property<Guid>("TransactionId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("transaction_id"); | ||||
| 
 | ||||
|                     b.HasKey("EventId") | ||||
|                         .HasName("pk_distributed_event_log"); | ||||
| 
 | ||||
|                     b.ToTable("distributed_event_log", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Brand", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Logging; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using System.Reflection; | ||||
| 
 | ||||
| @ -13,5 +14,6 @@ public class ProductServiceDbContext(DbContextOptions<ProductServiceDbContext> o | ||||
|         base.OnModelCreating(builder); | ||||
| 
 | ||||
|         builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); | ||||
|         builder.UseDistributedEventLogs(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| using HelloShop.DistributedLock.Dapr; | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using HelloShop.EventBus.Dapr; | ||||
| using HelloShop.EventBus.Logging; | ||||
| using HelloShop.ProductService.Constants; | ||||
| using HelloShop.ProductService.Infrastructure; | ||||
| using HelloShop.ProductService.Services; | ||||
| @ -47,6 +48,7 @@ builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthor | ||||
| builder.AddDaprEventBus().AddSubscriptionFromAssembly(); | ||||
| builder.Services.AddDaprDistributedLock(); | ||||
| builder.Services.AddSingleton(TimeProvider.System); | ||||
| builder.Services.AddDistributedEventLogs<ProductServiceDbContext>().AddTransient<IDistributedEventService, DistributedEventService>(); | ||||
| // End addd extensions services to the container. | ||||
| 
 | ||||
| var app = builder.Build(); | ||||
|  | ||||
| @ -0,0 +1,37 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| using HelloShop.EventBus.Logging; | ||||
| using HelloShop.ProductService.Infrastructure; | ||||
| 
 | ||||
| namespace HelloShop.ProductService.Services | ||||
| { | ||||
|     public class DistributedEventService(ILogger<DistributedEventService> logger, IEventBus eventBus, ProductServiceDbContext dbContext, IDistributedEventLogService eventLogService) : IDistributedEventService | ||||
|     { | ||||
|         public async Task PublishThroughEventBusAsync(DistributedEvent @event) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await eventLogService.MarkEventAsInProgressAsync(@event.Id); | ||||
|                 await eventBus.PublishAsync(@event); | ||||
|                 await eventLogService.MarkEventAsPublishedAsync(@event.Id); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 logger.LogError(ex, "Publish through event bus failed for {EventId}", @event.Id); | ||||
|                 await eventLogService.MarkEventAsFailedAsync(@event.Id); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public async Task SaveEventAndDbContextChangesAsync(DistributedEvent @event) | ||||
|         { | ||||
|             await ResilientTransaction.New(dbContext).ExecuteAsync(async () => | ||||
|             { | ||||
|                 var transaction = dbContext.Database.CurrentTransaction ?? await dbContext.Database.BeginTransactionAsync(); | ||||
|                 await dbContext.SaveChangesAsync(); | ||||
|                 await eventLogService.SaveEventAsync(@event, transaction); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| // Copyright (c) HelloShop Corporation. All rights reserved. | ||||
| // See the license file in the project root for more information. | ||||
| 
 | ||||
| using HelloShop.EventBus.Abstractions; | ||||
| 
 | ||||
| namespace HelloShop.ProductService.Services | ||||
| { | ||||
|     public interface IDistributedEventService | ||||
|     { | ||||
|         Task SaveEventAndDbContextChangesAsync(DistributedEvent @event); | ||||
| 
 | ||||
|         Task PublishThroughEventBusAsync(DistributedEvent @event); | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 hello
						hello