From 833328bd85a1ef01a887b27bc5ac621f6f101067 Mon Sep 17 00:00:00 2001 From: Nguyen Xuan Nhan Date: Sun, 25 Aug 2024 13:08:35 +0700 Subject: [PATCH] chore: Update project references and dependencies --- BookWorm.sln | 7 ++ src/BookWorm.AppHost/BookWorm.AppHost.csproj | 1 + src/BookWorm.AppHost/GlobalUsings.cs | 1 + src/BookWorm.AppHost/Program.cs | 24 +++-- src/BookWorm.Basket/GlobalUsings.cs | 1 + .../Infrastructure/Redis/Extension.cs | 2 +- .../Infrastructure/Redis/RedisService.cs | 4 +- .../OrderCreatedIntegrationEventHandler.cs | 11 ++- src/BookWorm.Catalog/Extensions/Extensions.cs | 2 +- src/BookWorm.Catalog/GlobalUsings.cs | 4 +- .../Infrastructure/Ai/Extension.cs | 2 +- .../Infrastructure/Blob/AzuriteService.cs | 2 +- .../Infrastructure/Data/Extension.cs | 2 +- .../FeedbackCreatedIntegrationEventHandler.cs | 6 ++ .../FeedbackDeletedIntegrationEventHandler.cs | 10 ++- .../BookWorm.Constants.csproj | 1 + .../DataSchemaLength.cs | 2 +- src/BookWorm.Constants/ServiceName.cs | 23 +++++ .../UniqueType.cs | 2 +- .../VectorType.cs | 2 +- .../DataProtection/Extension.cs | 2 +- src/BookWorm.Identity/GlobalUsings.cs | 1 + src/BookWorm.Identity/HostingExtensions.cs | 2 +- .../Options/ServiceOptions.cs | 4 - .../Extensions/Extensions.cs | 17 ++-- .../Extensions/NotificationTrace.cs | 12 +++ .../Infrastructure/SmtpOutboxDecorator.cs | 90 +++++++++++++++---- .../OrderCancelledIntegrationEventHandler.cs | 16 ++-- .../OrderCompletedIntegrationEventHandler.cs | 16 ++-- .../OrderCreatedIntegrationEventHandler.cs | 16 ++-- .../OpenTelemetry/MartenTelemetry.cs | 6 ++ .../OpenTelemetry/SmtpTelemetry.cs | 6 ++ src/BookWorm.Ordering/Constants/HeaderName.cs | 6 -- src/BookWorm.Ordering/Constants/Http.cs | 12 +++ .../Extensions/Extensions.cs | 9 +- .../Extensions/OrderingTrace.cs | 13 +++ .../Features/Buyers/Get/GetBuyerEndpoint.cs | 2 +- .../Orders/Cancel/CancelOrderEndpoint.cs | 9 +- .../Orders/Complete/CompleteOrderEndpoint.cs | 9 +- .../Orders/Create/CreateOrderEndpoint.cs | 9 +- .../Orders/EventHandlers/OrderEventHandler.cs | 10 ++- .../Orders/List/ListOrdersEndpoint.cs | 1 - .../Filters/IdempotencyFilter.cs | 16 ++-- src/BookWorm.Ordering/GlobalUsings.cs | 3 +- .../Infrastructure/Data/Extension.cs | 2 +- .../Infrastructure/Redis/Extension.cs | 2 +- .../Infrastructure/Redis/RedisService.cs | 4 +- ...etCheckoutFailedIntegrationEventHandler.cs | 14 ++- .../OpenTelemetry/MartenTelemetry.cs | 6 ++ src/BookWorm.Rating/Extensions/Extensions.cs | 8 +- .../Create/CreateFeedbackValidator.cs | 4 +- src/BookWorm.Rating/GlobalUsings.cs | 1 - ...ackCreatedFailedIntegrationEventHandler.cs | 12 ++- src/BookWorm.ServiceDefaults/Extensions.cs | 2 - src/BookWorm.Shared/BookWorm.Shared.csproj | 1 + src/BookWorm.Shared/Bus/Extension.cs | 5 +- .../BookWorm.Catalog.IntegrationTests.csproj | 1 + .../GlobalUsings.cs | 5 +- .../Application/HideFeedbackHandlerTests.cs | 6 +- 59 files changed, 326 insertions(+), 143 deletions(-) create mode 100644 src/BookWorm.Constants/BookWorm.Constants.csproj rename src/{BookWorm.Shared/Constants => BookWorm.Constants}/DataSchemaLength.cs (87%) create mode 100644 src/BookWorm.Constants/ServiceName.cs rename src/{BookWorm.Shared/Constants => BookWorm.Constants}/UniqueType.cs (78%) rename src/{BookWorm.Shared/Constants => BookWorm.Constants}/VectorType.cs (76%) create mode 100644 src/BookWorm.Notification/Extensions/NotificationTrace.cs create mode 100644 src/BookWorm.Notification/OpenTelemetry/MartenTelemetry.cs create mode 100644 src/BookWorm.Notification/OpenTelemetry/SmtpTelemetry.cs delete mode 100644 src/BookWorm.Ordering/Constants/HeaderName.cs create mode 100644 src/BookWorm.Ordering/Constants/Http.cs create mode 100644 src/BookWorm.Ordering/Extensions/OrderingTrace.cs create mode 100644 src/BookWorm.Ordering/OpenTelemetry/MartenTelemetry.cs diff --git a/BookWorm.sln b/BookWorm.sln index 7e8aca1..c8083d9 100644 --- a/BookWorm.sln +++ b/BookWorm.sln @@ -72,6 +72,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BookWorm.MailDev.Hosting", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BookWorm.Gateway", "src\BookWorm.Gateway\BookWorm.Gateway.csproj", "{9AE80189-4D7D-44F0-84C8-127DA27D7AC8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookWorm.Constants", "src\BookWorm.Constants\BookWorm.Constants.csproj", "{C7663D58-F93F-466E-8A93-A6E30396C6B6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -162,6 +164,10 @@ Global {9AE80189-4D7D-44F0-84C8-127DA27D7AC8}.Debug|Any CPU.Build.0 = Debug|Any CPU {9AE80189-4D7D-44F0-84C8-127DA27D7AC8}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AE80189-4D7D-44F0-84C8-127DA27D7AC8}.Release|Any CPU.Build.0 = Release|Any CPU + {C7663D58-F93F-466E-8A93-A6E30396C6B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7663D58-F93F-466E-8A93-A6E30396C6B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7663D58-F93F-466E-8A93-A6E30396C6B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7663D58-F93F-466E-8A93-A6E30396C6B6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -194,6 +200,7 @@ Global {D1719092-035E-4E02-ACEC-782107CFE51C} = {1A0E0FD4-396E-4DBA-B3F1-ED4ACCE50D1B} {0C0E5158-F59D-45E0-A6C5-63198A3D0492} = {1A0E0FD4-396E-4DBA-B3F1-ED4ACCE50D1B} {9AE80189-4D7D-44F0-84C8-127DA27D7AC8} = {C50229F2-978B-4961-8865-83767C55E8FB} + {C7663D58-F93F-466E-8A93-A6E30396C6B6} = {5E13A8D9-B0C4-4388-A99D-AEF14B63CA8C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {233A9FBF-DCD1-40C1-A6A5-9D37C92557BB} diff --git a/src/BookWorm.AppHost/BookWorm.AppHost.csproj b/src/BookWorm.AppHost/BookWorm.AppHost.csproj index e3053ab..9aefadb 100644 --- a/src/BookWorm.AppHost/BookWorm.AppHost.csproj +++ b/src/BookWorm.AppHost/BookWorm.AppHost.csproj @@ -27,6 +27,7 @@ + config.WithDataBindMount("../../mnt/azurite")); + storage.RunAsEmulator(config => config.WithDataBindMount("../../mnt/azurite")); } -var blobs = storage.AddBlobs("blobs"); +var blobs = storage.AddBlobs(ServiceName.Blob); -var openAi = builder.AddConnectionString("openai"); +var openAi = builder.AddConnectionString(ServiceName.OpenAi); var rabbitMq = builder - .AddRabbitMQ("eventbus") + .AddRabbitMQ(ServiceName.EventBus) .WithManagementPlugin(); -var smtpServer = builder.AddMailDev("mailserver", 1080); +var smtpServer = builder.AddMailDev(ServiceName.Mail, 1080); // Services var identityApi = builder.AddProject("identity-api") @@ -57,7 +56,6 @@ var identityEndpoint = identityApi.GetEndpoint(launchProfileName); var catalogApi = builder.AddProject("catalog-api") - .WithReference(blobs) .WithReference(rabbitMq) .WithReference(catalogDb) .WithReference(redis) diff --git a/src/BookWorm.Basket/GlobalUsings.cs b/src/BookWorm.Basket/GlobalUsings.cs index 2fbb80c..660a2ef 100644 --- a/src/BookWorm.Basket/GlobalUsings.cs +++ b/src/BookWorm.Basket/GlobalUsings.cs @@ -5,6 +5,7 @@ global using Ardalis.Result; global using BookWorm.Basket.Grpc; global using BookWorm.Basket.Infrastructure.Redis; +global using BookWorm.Constants; global using BookWorm.Core.SeedWork; global using BookWorm.Core.SharedKernel; global using BookWorm.ServiceDefaults; diff --git a/src/BookWorm.Basket/Infrastructure/Redis/Extension.cs b/src/BookWorm.Basket/Infrastructure/Redis/Extension.cs index 724cace..bda73f9 100644 --- a/src/BookWorm.Basket/Infrastructure/Redis/Extension.cs +++ b/src/BookWorm.Basket/Infrastructure/Redis/Extension.cs @@ -4,7 +4,7 @@ internal static class Extension { public static IHostApplicationBuilder AddRedisCache(this IHostApplicationBuilder builder) { - builder.AddRedisClient("redis"); + builder.AddRedisClient(ServiceName.Redis); builder.Services.AddSingleton(); diff --git a/src/BookWorm.Basket/Infrastructure/Redis/RedisService.cs b/src/BookWorm.Basket/Infrastructure/Redis/RedisService.cs index c62622d..a640e7a 100644 --- a/src/BookWorm.Basket/Infrastructure/Redis/RedisService.cs +++ b/src/BookWorm.Basket/Infrastructure/Redis/RedisService.cs @@ -5,8 +5,8 @@ public sealed class RedisService(IConfiguration configuration) : IRedisService private readonly SemaphoreSlim _connectionLock = new(1, 1); private readonly Lazy _connectionMultiplexer = new(() => - ConnectionMultiplexer.Connect( - configuration.GetConnectionString("redis") ?? throw new InvalidOperationException())); + ConnectionMultiplexer.Connect(configuration.GetConnectionString(ServiceName.Redis) ?? + throw new InvalidOperationException())); private ConnectionMultiplexer ConnectionMultiplexer => _connectionMultiplexer.Value; diff --git a/src/BookWorm.Basket/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs b/src/BookWorm.Basket/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs index e8582a4..adc51b5 100644 --- a/src/BookWorm.Basket/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs +++ b/src/BookWorm.Basket/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs @@ -4,16 +4,23 @@ namespace BookWorm.Basket.IntegrationEvents.EventHandlers; public sealed class OrderCreatedIntegrationEventHandler( IRedisService redisService, + ILogger logger, IPublishEndpoint publishEndpoint) : IConsumer { public async Task Consume(ConsumeContext context) { + var @event = context.Message; + + logger.LogInformation("[{Consumer}] - Removing basket for order {OrderId}", + nameof(OrderCreatedIntegrationEventHandler), + @event.OrderId); + var basketId = context.Message.BasketId.ToString(); var basket = await redisService.HashGetAsync(nameof(Basket), basketId); if (basket is null) { - await PublishBasketCheckoutFailed(context.Message.OrderId); + await PublishBasketCheckoutFailed(@event.OrderId); return; } @@ -23,7 +30,7 @@ public async Task Consume(ConsumeContext context) } catch (Exception) { - await PublishBasketCheckoutFailed(context.Message.OrderId); + await PublishBasketCheckoutFailed(@event.OrderId); } } diff --git a/src/BookWorm.Catalog/Extensions/Extensions.cs b/src/BookWorm.Catalog/Extensions/Extensions.cs index 2291d5c..0645a16 100644 --- a/src/BookWorm.Catalog/Extensions/Extensions.cs +++ b/src/BookWorm.Catalog/Extensions/Extensions.cs @@ -4,7 +4,7 @@ internal static class Extensions { public static void AddApplicationServices(this IHostApplicationBuilder builder) { - builder.AddRedisOutputCache("cache"); + builder.AddRedisOutputCache(ServiceName.Redis); builder.Services.AddGrpc(); diff --git a/src/BookWorm.Catalog/GlobalUsings.cs b/src/BookWorm.Catalog/GlobalUsings.cs index 76772f2..53458d9 100644 --- a/src/BookWorm.Catalog/GlobalUsings.cs +++ b/src/BookWorm.Catalog/GlobalUsings.cs @@ -1,4 +1,4 @@ -global using System.Diagnostics.CodeAnalysis; +global using System.Diagnostics.CodeAnalysis; global using System.Text.Json.Serialization; global using Ardalis.GuardClauses; global using Ardalis.Result; @@ -11,7 +11,6 @@ global using BookWorm.ServiceDefaults; global using BookWorm.Shared.ActivityScope; global using BookWorm.Shared.Bus; -global using BookWorm.Shared.Constants; global using BookWorm.Shared.Converters; global using BookWorm.Shared.Endpoints; global using BookWorm.Shared.Exceptions; @@ -33,5 +32,6 @@ global using Azure; global using Azure.Storage.Blobs; global using Azure.Storage.Blobs.Models; +global using BookWorm.Constants; global using Polly; global using Polly.Registry; diff --git a/src/BookWorm.Catalog/Infrastructure/Ai/Extension.cs b/src/BookWorm.Catalog/Infrastructure/Ai/Extension.cs index f013567..29954b5 100644 --- a/src/BookWorm.Catalog/Infrastructure/Ai/Extension.cs +++ b/src/BookWorm.Catalog/Infrastructure/Ai/Extension.cs @@ -8,7 +8,7 @@ public static IHostApplicationBuilder AddAi(this IHostApplicationBuilder builder { var modelName = builder.Configuration["AiOptions:OpenAi:EmbeddingName"] ?? "text-embedding-3-small"; - builder.AddAzureOpenAIClient("openai"); + builder.AddAzureOpenAIClient(ServiceName.OpenAi); builder.Services.AddOpenAITextEmbeddingGeneration(modelName); builder.Services.AddSingleton(); diff --git a/src/BookWorm.Catalog/Infrastructure/Blob/AzuriteService.cs b/src/BookWorm.Catalog/Infrastructure/Blob/AzuriteService.cs index 566d1c4..1740ad7 100644 --- a/src/BookWorm.Catalog/Infrastructure/Blob/AzuriteService.cs +++ b/src/BookWorm.Catalog/Infrastructure/Blob/AzuriteService.cs @@ -3,7 +3,7 @@ public sealed class AzuriteService(ResiliencePipelineProvider pipeline, IConfiguration configuration) : IAzuriteService { - private readonly BlobContainerClient _container = new(configuration.GetConnectionString("blobs"), + private readonly BlobContainerClient _container = new(configuration.GetConnectionString(ServiceName.Blob), nameof(Catalog)); private readonly ResiliencePipeline _policy = pipeline.GetPipeline(nameof(Blob)); diff --git a/src/BookWorm.Catalog/Infrastructure/Data/Extension.cs b/src/BookWorm.Catalog/Infrastructure/Data/Extension.cs index a31cf73..c6657d2 100644 --- a/src/BookWorm.Catalog/Infrastructure/Data/Extension.cs +++ b/src/BookWorm.Catalog/Infrastructure/Data/Extension.cs @@ -9,7 +9,7 @@ public static IHostApplicationBuilder AddPersistence(this IHostApplicationBuilde { builder.Services.AddMigration(); - builder.AddNpgsqlDbContext("catalogdb", configureDbContextOptions: + builder.AddNpgsqlDbContext(ServiceName.Database.Catalog, configureDbContextOptions: dbContextOptionsBuilder => { dbContextOptionsBuilder diff --git a/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackCreatedIntegrationEventHandler.cs b/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackCreatedIntegrationEventHandler.cs index 73f7df3..b9cb25c 100644 --- a/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackCreatedIntegrationEventHandler.cs +++ b/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackCreatedIntegrationEventHandler.cs @@ -5,12 +5,18 @@ namespace BookWorm.Catalog.IntegrationEvents.EventHandlers; public sealed class FeedbackCreatedIntegrationEventHandler( IRepository repository, + ILogger logger, IPublishEndpoint publishEndpoint) : IConsumer { public async Task Consume(ConsumeContext context) { var @event = context.Message; + logger.LogInformation("[{Consumer}] - Adding rating {Rating} to book {BookId}", + nameof(FeedbackCreatedIntegrationEventHandler), + @event.Rating, + @event.BookId); + var book = await repository.GetByIdAsync(@event.BookId); if (book is null) diff --git a/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackDeletedIntegrationEventHandler.cs b/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackDeletedIntegrationEventHandler.cs index 79f6108..07bf029 100644 --- a/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackDeletedIntegrationEventHandler.cs +++ b/src/BookWorm.Catalog/IntegrationEvents/EventHandlers/FeedbackDeletedIntegrationEventHandler.cs @@ -3,13 +3,19 @@ namespace BookWorm.Catalog.IntegrationEvents.EventHandlers; -internal sealed class FeedbackDeletedIntegrationEventHandler(IRepository repository) - : IConsumer +internal sealed class FeedbackDeletedIntegrationEventHandler( + IRepository repository, + ILogger logger) : IConsumer { public async Task Consume(ConsumeContext context) { var @event = context.Message; + logger.LogInformation("[{Consumer}] - Removing rating {Rating} from book {BookId}", + nameof(FeedbackDeletedIntegrationEventHandler), + @event.Rating, + @event.BookId); + var book = await repository.GetByIdAsync(@event.BookId); if (book is null) diff --git a/src/BookWorm.Constants/BookWorm.Constants.csproj b/src/BookWorm.Constants/BookWorm.Constants.csproj new file mode 100644 index 0000000..a696898 --- /dev/null +++ b/src/BookWorm.Constants/BookWorm.Constants.csproj @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/BookWorm.Shared/Constants/DataSchemaLength.cs b/src/BookWorm.Constants/DataSchemaLength.cs similarity index 87% rename from src/BookWorm.Shared/Constants/DataSchemaLength.cs rename to src/BookWorm.Constants/DataSchemaLength.cs index 658feb6..022f8c0 100644 --- a/src/BookWorm.Shared/Constants/DataSchemaLength.cs +++ b/src/BookWorm.Constants/DataSchemaLength.cs @@ -1,4 +1,4 @@ -namespace BookWorm.Shared.Constants; +namespace BookWorm.Constants; public static class DataSchemaLength { diff --git a/src/BookWorm.Constants/ServiceName.cs b/src/BookWorm.Constants/ServiceName.cs new file mode 100644 index 0000000..fd81361 --- /dev/null +++ b/src/BookWorm.Constants/ServiceName.cs @@ -0,0 +1,23 @@ +namespace BookWorm.Constants; + +public static class ServiceName +{ + public const string EventBus = "eventbus"; + + public const string Redis = "redis"; + + public const string OpenAi = "openai"; + + public const string Blob = "blob"; + + public const string Mail = "mailserver"; + + public static class Database + { + public const string Catalog = "catalogdb"; + public const string Ordering = "orderingdb"; + public const string Identity = "identitydb"; + public const string Notification = "notificationdb"; + public const string Rating = "ratingdb"; + } +} diff --git a/src/BookWorm.Shared/Constants/UniqueType.cs b/src/BookWorm.Constants/UniqueType.cs similarity index 78% rename from src/BookWorm.Shared/Constants/UniqueType.cs rename to src/BookWorm.Constants/UniqueType.cs index fbcfa60..8efc6b0 100644 --- a/src/BookWorm.Shared/Constants/UniqueType.cs +++ b/src/BookWorm.Constants/UniqueType.cs @@ -1,4 +1,4 @@ -namespace BookWorm.Shared.Constants; +namespace BookWorm.Constants; public static class UniqueType { diff --git a/src/BookWorm.Shared/Constants/VectorType.cs b/src/BookWorm.Constants/VectorType.cs similarity index 76% rename from src/BookWorm.Shared/Constants/VectorType.cs rename to src/BookWorm.Constants/VectorType.cs index b5b3dea..2403a27 100644 --- a/src/BookWorm.Shared/Constants/VectorType.cs +++ b/src/BookWorm.Constants/VectorType.cs @@ -1,4 +1,4 @@ -namespace BookWorm.Shared.Constants; +namespace BookWorm.Constants; public static class VectorType { diff --git a/src/BookWorm.Identity/DataProtection/Extension.cs b/src/BookWorm.Identity/DataProtection/Extension.cs index 9083f68..8c20114 100644 --- a/src/BookWorm.Identity/DataProtection/Extension.cs +++ b/src/BookWorm.Identity/DataProtection/Extension.cs @@ -7,7 +7,7 @@ internal static class Extension { public static IHostApplicationBuilder AddRedisDataProtection(this IHostApplicationBuilder builder) { - var conn = builder.Configuration.GetConnectionString("redis"); + var conn = builder.Configuration.GetConnectionString(ServiceName.Redis); if (string.IsNullOrWhiteSpace(conn)) { diff --git a/src/BookWorm.Identity/GlobalUsings.cs b/src/BookWorm.Identity/GlobalUsings.cs index cb237b6..40bfc49 100644 --- a/src/BookWorm.Identity/GlobalUsings.cs +++ b/src/BookWorm.Identity/GlobalUsings.cs @@ -2,6 +2,7 @@ global using Duende.IdentityServer.Models; global using Microsoft.EntityFrameworkCore; global using System.Diagnostics; +global using BookWorm.Constants; global using BookWorm.Identity.Data; global using BookWorm.Identity.Data.CompliedModels; global using BookWorm.Identity.DataProtection; diff --git a/src/BookWorm.Identity/HostingExtensions.cs b/src/BookWorm.Identity/HostingExtensions.cs index 9f332ae..0ef0cd5 100644 --- a/src/BookWorm.Identity/HostingExtensions.cs +++ b/src/BookWorm.Identity/HostingExtensions.cs @@ -16,7 +16,7 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde builder.Services.AddMigration(); - builder.AddNpgsqlDbContext("identitydb", + builder.AddNpgsqlDbContext(ServiceName.Database.Identity, configureDbContextOptions: dbContextOptionsBuilder => dbContextOptionsBuilder.UseNpgsql() .UseModel(ApplicationDbContextModel.Instance)); diff --git a/src/BookWorm.Identity/Options/ServiceOptions.cs b/src/BookWorm.Identity/Options/ServiceOptions.cs index 6351e69..70717ff 100644 --- a/src/BookWorm.Identity/Options/ServiceOptions.cs +++ b/src/BookWorm.Identity/Options/ServiceOptions.cs @@ -11,8 +11,4 @@ public sealed class ServiceOptions public string Basket { get; set; } = string.Empty; public string Rating { get; set; } = string.Empty; - - public string StoreFront { get; set; } = string.Empty; - - public string BackOffice { get; set; } = string.Empty; } diff --git a/src/BookWorm.Notification/Extensions/Extensions.cs b/src/BookWorm.Notification/Extensions/Extensions.cs index c3b76a7..955abb9 100644 --- a/src/BookWorm.Notification/Extensions/Extensions.cs +++ b/src/BookWorm.Notification/Extensions/Extensions.cs @@ -1,4 +1,7 @@ -namespace BookWorm.Notification.Extensions; +using BookWorm.Constants; +using BookWorm.Notification.OpenTelemetry; + +namespace BookWorm.Notification.Extensions; internal static class Extensions { @@ -9,7 +12,7 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder) builder.AddRabbitMqEventBus(typeof(Program)); - var emailConn = new UriBuilder(builder.Configuration.GetConnectionString("mailserver") ?? + var emailConn = new UriBuilder(builder.Configuration.GetConnectionString(ServiceName.Mail) ?? throw new InvalidOperationException()); var defaultFromEmail = builder.Configuration["Smtp:Email"]; @@ -28,14 +31,18 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder) .AddTimeout(TimeSpan.FromSeconds(10))); builder.Services.AddOpenTelemetry() - .WithMetrics(t => t.AddMeter("Smtp")) - .WithTracing(t => t.AddSource("Smtp")); + .WithMetrics(t => t + .AddMeter(SmtpTelemetry.ActivityName) + .AddMeter(MartenTelemetry.ActivityName)) + .WithTracing(t => t + .AddSource(SmtpTelemetry.ActivityName) + .AddSource(MartenTelemetry.ActivityName)); builder.Services.AddMarten(_ => { var options = new StoreOptions(); - var dbConn = builder.Configuration.GetConnectionString("notificationdb"); + var dbConn = builder.Configuration.GetConnectionString(ServiceName.Database.Notification); Guard.Against.NullOrEmpty(dbConn); options.Connection(dbConn); diff --git a/src/BookWorm.Notification/Extensions/NotificationTrace.cs b/src/BookWorm.Notification/Extensions/NotificationTrace.cs new file mode 100644 index 0000000..d423f94 --- /dev/null +++ b/src/BookWorm.Notification/Extensions/NotificationTrace.cs @@ -0,0 +1,12 @@ +namespace BookWorm.Notification.Extensions; + +internal static partial class NotificationTrace +{ + [LoggerMessage(EventId = 0, Level = LogLevel.Information, + Message = "[{Service}] Sending email to {To} with subject {Subject}")] + public static partial void LogEmailSending(ILogger logger, string? service, string? to, string? subject); + + [LoggerMessage(EventId = 1, Level = LogLevel.Error, + Message = "[{Service}] Failed to process email to {To} with error: {Error}")] + public static partial void LogEmailFailed(ILogger logger, string? service, string? to, string? error); +} diff --git a/src/BookWorm.Notification/Infrastructure/SmtpOutboxDecorator.cs b/src/BookWorm.Notification/Infrastructure/SmtpOutboxDecorator.cs index 19e3916..e77dc1d 100644 --- a/src/BookWorm.Notification/Infrastructure/SmtpOutboxDecorator.cs +++ b/src/BookWorm.Notification/Infrastructure/SmtpOutboxDecorator.cs @@ -1,4 +1,7 @@ -namespace BookWorm.Notification.Infrastructure; +using BookWorm.Notification.Extensions; +using BookWorm.Notification.OpenTelemetry; + +namespace BookWorm.Notification.Infrastructure; public sealed class SmtpOutboxDecorator( ISmtpService smtpService, @@ -7,28 +10,61 @@ public sealed class SmtpOutboxDecorator( { public async Task SendEmailAsync(EmailMetadata emailMetadata, CancellationToken cancellationToken = default) { - logger.LogInformation("[{Service}] Sending email to {To} with subject {Subject}", - nameof(SmtpOutboxDecorator), emailMetadata.To, emailMetadata.Subject); + NotificationTrace.LogEmailSending(logger, nameof(SmtpOutboxDecorator), emailMetadata.To, emailMetadata.Subject); - using var activity = new ActivitySource("Smtp") + using var emailActivity = new ActivitySource(SmtpTelemetry.ActivityName) .StartActivity($"Sending email to {emailMetadata.To} with subject {emailMetadata.Subject}", ActivityKind.Client); + using var martenActivity = new ActivitySource(MartenTelemetry.ActivityName) + .StartActivity($"Storing email to {emailMetadata.To} with subject {emailMetadata.Subject}"); + + emailActivity?.AddTag("mail.to", emailMetadata.To); + emailActivity?.AddTag("mail.subject", emailMetadata.Subject); + var emailOutbox = new EmailOutbox { Body = emailMetadata.Body, Subject = emailMetadata.Subject, To = emailMetadata.To, IsSent = false }; - if (activity is not null) + try { - activity.AddTag("mail.to", emailMetadata.To); - activity.AddTag("mail.subject", emailMetadata.Subject); + await StoreEmailOutboxAsync(emailOutbox, martenActivity, cancellationToken); + await SendEmailAndMarkAsSentAsync(emailMetadata, emailOutbox, emailActivity, cancellationToken); } + catch (Exception ex) + { + emailActivity?.SetStatus(ActivityStatusCode.Error); + martenActivity?.SetStatus(ActivityStatusCode.Error); + NotificationTrace.LogEmailFailed(logger, nameof(SmtpOutboxDecorator), emailMetadata.To, + ex.Message); + } + finally + { + await UpdateEmailOutboxAsync(emailOutbox, martenActivity, cancellationToken); + } + } - session.Store(emailOutbox); - - await session.SaveChangesAsync(cancellationToken); + private async Task StoreEmailOutboxAsync(EmailOutbox emailOutbox, Activity? martenActivity, + CancellationToken cancellationToken) + { + try + { + session.Store(emailOutbox); + await session.SaveChangesAsync(cancellationToken); + } + catch (Exception ex) + { + if (martenActivity is not null) + { + TagException(ex, martenActivity); + } + } + } + private async Task SendEmailAndMarkAsSentAsync(EmailMetadata emailMetadata, EmailOutbox emailOutbox, + Activity? emailActivity, CancellationToken cancellationToken) + { try { await smtpService.SendEmailAsync(emailMetadata, cancellationToken); @@ -36,21 +72,37 @@ public async Task SendEmailAsync(EmailMetadata emailMetadata, CancellationToken } catch (Exception ex) { - logger.LogError(ex, "[{Service}] Failed to send email to {To} with subject {Subject}", - nameof(SmtpOutboxDecorator), emailMetadata.To, emailMetadata.Subject); - - if (activity is not null) + if (emailActivity is not null) { - activity.AddTag("exception.message", ex.Message); - activity.AddTag("exception.stacktrace", ex.ToString()); - activity.AddTag("exception.type", ex.GetType().FullName); - activity.SetStatus(ActivityStatusCode.Error); + TagException(ex, emailActivity); } } - finally + } + + private async Task UpdateEmailOutboxAsync(EmailOutbox emailOutbox, Activity? martenActivity, + CancellationToken cancellationToken) + { + try { session.Update(emailOutbox); await session.SaveChangesAsync(cancellationToken); } + catch (Exception ex) + { + if (martenActivity is not null) + { + TagException(ex, martenActivity); + } + + logger.LogError(ex, "[{Event}] - Failed to update email outbox", nameof(SmtpOutboxDecorator)); + } + } + + private static void TagException(Exception ex, Activity activity) + { + activity.AddTag("exception.message", ex.Message); + activity.AddTag("exception.stacktrace", ex.ToString()); + activity.AddTag("exception.type", ex.GetType().FullName); + activity.SetStatus(ActivityStatusCode.Error); } } diff --git a/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCancelledIntegrationEventHandler.cs b/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCancelledIntegrationEventHandler.cs index 750531c..2625554 100644 --- a/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCancelledIntegrationEventHandler.cs +++ b/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCancelledIntegrationEventHandler.cs @@ -2,20 +2,26 @@ namespace BookWorm.Notification.IntegrationEvents.EventHandlers; -internal sealed class OrderCancelledIntegrationEventHandler(ISmtpService smtpService) - : IConsumer +internal sealed class OrderCancelledIntegrationEventHandler( + ISmtpService smtpService, + ILogger logger) : IConsumer { public async Task Consume(ConsumeContext context) { - if (context.Message.Email is null) + var @event = context.Message; + + logger.LogInformation("[{Consumer}] Sending email to {Email} for order {OrderId}", + nameof(OrderCancelledIntegrationEventHandler), @event.Email, @event.OrderId); + + if (@event.Email is null) { return; } var metadata = new EmailMetadata( - context.Message.Email, + @event.Email, "Order Cancelled", - $"Your order has been cancelled. Order ID: {context.Message.OrderId}"); + $"Your order has been cancelled. Order ID: {@event.OrderId}"); await smtpService.SendEmailAsync(metadata); } diff --git a/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCompletedIntegrationEventHandler.cs b/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCompletedIntegrationEventHandler.cs index 6f29068..65b2351 100644 --- a/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCompletedIntegrationEventHandler.cs +++ b/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCompletedIntegrationEventHandler.cs @@ -2,20 +2,26 @@ namespace BookWorm.Notification.IntegrationEvents.EventHandlers; -internal sealed class OrderCompletedIntegrationEventHandler(ISmtpService smtpService) - : IConsumer +internal sealed class OrderCompletedIntegrationEventHandler( + ISmtpService smtpService, + ILogger logger) : IConsumer { public async Task Consume(ConsumeContext context) { - if (context.Message.Email is null) + var @event = context.Message; + + logger.LogInformation("[{Consumer}] Sending email to {Email} for order {OrderId}", + nameof(OrderCompletedIntegrationEventHandler), @event.Email, @event.OrderId); + + if (@event.Email is null) { return; } var metadata = new EmailMetadata( - context.Message.Email, + @event.Email, "Order Completed", - $"Your order has been completed. Order ID: {context.Message.OrderId}"); + $"Your order has been completed. Order ID: {@event.OrderId}"); await smtpService.SendEmailAsync(metadata); } diff --git a/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs b/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs index 5c4f2f6..6af6a31 100644 --- a/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs +++ b/src/BookWorm.Notification/IntegrationEvents/EventHandlers/OrderCreatedIntegrationEventHandler.cs @@ -2,20 +2,26 @@ namespace BookWorm.Notification.IntegrationEvents.EventHandlers; -internal sealed class OrderCreatedIntegrationEventHandler(ISmtpService smtpService) - : IConsumer +internal sealed class OrderCreatedIntegrationEventHandler( + ISmtpService smtpService, + ILogger logger) : IConsumer { public async Task Consume(ConsumeContext context) { - if (context.Message.Email is null) + var @event = context.Message; + + logger.LogInformation("[{Consumer}] Sending email to {Email} for order {OrderId}", + nameof(OrderCreatedIntegrationEventHandler), @event.Email, @event.OrderId); + + if (@event.Email is null) { return; } var metadata = new EmailMetadata( - context.Message.Email, + @event.Email, "Order Created", - $"Thank you for your order. Order will be processed soon. Order ID: {context.Message.OrderId}"); + $"Thank you for your order. Order will be processed soon. Order ID: {@event.OrderId}"); await smtpService.SendEmailAsync(metadata); } diff --git a/src/BookWorm.Notification/OpenTelemetry/MartenTelemetry.cs b/src/BookWorm.Notification/OpenTelemetry/MartenTelemetry.cs new file mode 100644 index 0000000..595886d --- /dev/null +++ b/src/BookWorm.Notification/OpenTelemetry/MartenTelemetry.cs @@ -0,0 +1,6 @@ +namespace BookWorm.Notification.OpenTelemetry; + +public static class MartenTelemetry +{ + public const string ActivityName = "Marten"; +} diff --git a/src/BookWorm.Notification/OpenTelemetry/SmtpTelemetry.cs b/src/BookWorm.Notification/OpenTelemetry/SmtpTelemetry.cs new file mode 100644 index 0000000..bb9c018 --- /dev/null +++ b/src/BookWorm.Notification/OpenTelemetry/SmtpTelemetry.cs @@ -0,0 +1,6 @@ +namespace BookWorm.Notification.OpenTelemetry; + +public static class SmtpTelemetry +{ + public const string ActivityName = "Smtp"; +} diff --git a/src/BookWorm.Ordering/Constants/HeaderName.cs b/src/BookWorm.Ordering/Constants/HeaderName.cs deleted file mode 100644 index da85e59..0000000 --- a/src/BookWorm.Ordering/Constants/HeaderName.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace BookWorm.Ordering.Constants; - -public static class HeaderName -{ - public const string IdempotencyKey = "X-Idempotency-Key"; -} diff --git a/src/BookWorm.Ordering/Constants/Http.cs b/src/BookWorm.Ordering/Constants/Http.cs new file mode 100644 index 0000000..8578317 --- /dev/null +++ b/src/BookWorm.Ordering/Constants/Http.cs @@ -0,0 +1,12 @@ +namespace BookWorm.Ordering.Constants; + +public static class Http +{ + public const string Idempotency = "X-Idempotency-Key"; + + public static class Methods + { + public const string Post = "POST"; + public const string Patch = "PATCH"; + } +} diff --git a/src/BookWorm.Ordering/Extensions/Extensions.cs b/src/BookWorm.Ordering/Extensions/Extensions.cs index 9057a2e..6008004 100644 --- a/src/BookWorm.Ordering/Extensions/Extensions.cs +++ b/src/BookWorm.Ordering/Extensions/Extensions.cs @@ -1,4 +1,5 @@ -using GrpcBookClient = BookWorm.Catalog.Grpc.Book.BookClient; +using BookWorm.Ordering.OpenTelemetry; +using GrpcBookClient = BookWorm.Catalog.Grpc.Book.BookClient; using GrpcBasketClient = BookWorm.Basket.Grpc.Basket.BasketClient; namespace BookWorm.Ordering.Extensions; @@ -45,7 +46,7 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder) options.Events.DatabaseSchemaName = schemaName; options.DatabaseSchemaName = schemaName; - var conn = builder.Configuration.GetConnectionString("orderingdb"); + var conn = builder.Configuration.GetConnectionString(ServiceName.Database.Ordering); Guard.Against.NullOrEmpty(conn); @@ -66,6 +67,10 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder) .UseLightweightSessions() .AddAsyncDaemon(DaemonMode.Solo); + builder.Services.AddOpenTelemetry() + .WithMetrics(t => t.AddMeter(MartenTelemetry.ActivityName)) + .WithTracing(t => t.AddSource(MartenTelemetry.ActivityName)); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/BookWorm.Ordering/Extensions/OrderingTrace.cs b/src/BookWorm.Ordering/Extensions/OrderingTrace.cs new file mode 100644 index 0000000..55d771a --- /dev/null +++ b/src/BookWorm.Ordering/Extensions/OrderingTrace.cs @@ -0,0 +1,13 @@ +namespace BookWorm.Ordering.Extensions; + +internal static partial class OrderingTrace +{ + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "[[{Event}] - Order {OrderId} created")] + public static partial void LogOrderCreated(ILogger logger, string? @event, Guid orderId); + + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "[[{Event}] - Order {OrderId} completed")] + public static partial void LogOrderCompleted(ILogger logger, string? @event, Guid orderId); + + [LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "[[{Event}] - Order {OrderId} cancelled")] + public static partial void LogOrderCancelled(ILogger logger, string? @event, Guid orderId); +} diff --git a/src/BookWorm.Ordering/Features/Buyers/Get/GetBuyerEndpoint.cs b/src/BookWorm.Ordering/Features/Buyers/Get/GetBuyerEndpoint.cs index 82b760d..4805b9d 100644 --- a/src/BookWorm.Ordering/Features/Buyers/Get/GetBuyerEndpoint.cs +++ b/src/BookWorm.Ordering/Features/Buyers/Get/GetBuyerEndpoint.cs @@ -6,7 +6,7 @@ public sealed class GetBuyerEndpoint : IEndpoint, Guid, ISender> { public void MapEndpoint(IEndpointRouteBuilder app) { - app.MapGet("/buyers/{id}", async (Guid id, ISender sender) => await HandleAsync(id, sender)) + app.MapGet("/buyers/{id:guid}", async (Guid id, ISender sender) => await HandleAsync(id, sender)) .Produces>() .ProducesProblem(StatusCodes.Status404NotFound) .WithTags(nameof(Buyer)) diff --git a/src/BookWorm.Ordering/Features/Orders/Cancel/CancelOrderEndpoint.cs b/src/BookWorm.Ordering/Features/Orders/Cancel/CancelOrderEndpoint.cs index dd9a652..ada72a9 100644 --- a/src/BookWorm.Ordering/Features/Orders/Cancel/CancelOrderEndpoint.cs +++ b/src/BookWorm.Ordering/Features/Orders/Cancel/CancelOrderEndpoint.cs @@ -1,8 +1,4 @@ -using BookWorm.Ordering.Constants; -using BookWorm.Ordering.Filters; -using Microsoft.AspNetCore.Mvc; - -namespace BookWorm.Ordering.Features.Orders.Cancel; +namespace BookWorm.Ordering.Features.Orders.Cancel; public sealed class CancelOrderEndpoint : IEndpoint { @@ -10,8 +6,7 @@ public void MapEndpoint(IEndpointRouteBuilder app) { app.MapPatch("/orders/{orderId:guid}/cancel", async ( - [FromHeader(Name = HeaderName.IdempotencyKey)] - string key, + [FromIdempotencyHeader] string key, Guid orderId, ISender sender) => await HandleAsync(orderId, sender)) .AddEndpointFilter() diff --git a/src/BookWorm.Ordering/Features/Orders/Complete/CompleteOrderEndpoint.cs b/src/BookWorm.Ordering/Features/Orders/Complete/CompleteOrderEndpoint.cs index 340311f..a0b7238 100644 --- a/src/BookWorm.Ordering/Features/Orders/Complete/CompleteOrderEndpoint.cs +++ b/src/BookWorm.Ordering/Features/Orders/Complete/CompleteOrderEndpoint.cs @@ -1,8 +1,4 @@ -using BookWorm.Ordering.Constants; -using BookWorm.Ordering.Filters; -using Microsoft.AspNetCore.Mvc; - -namespace BookWorm.Ordering.Features.Orders.Complete; +namespace BookWorm.Ordering.Features.Orders.Complete; public sealed class CompleteOrderEndpoint : IEndpoint { @@ -10,8 +6,7 @@ public void MapEndpoint(IEndpointRouteBuilder app) { app.MapPatch("/orders/{orderId:guid}/complete", async ( - [FromHeader(Name = HeaderName.IdempotencyKey)] - string key, + [FromIdempotencyHeader] string key, Guid orderId, ISender sender) => await HandleAsync(orderId, sender)) .AddEndpointFilter() diff --git a/src/BookWorm.Ordering/Features/Orders/Create/CreateOrderEndpoint.cs b/src/BookWorm.Ordering/Features/Orders/Create/CreateOrderEndpoint.cs index 9d48a13..fce62a8 100644 --- a/src/BookWorm.Ordering/Features/Orders/Create/CreateOrderEndpoint.cs +++ b/src/BookWorm.Ordering/Features/Orders/Create/CreateOrderEndpoint.cs @@ -1,8 +1,4 @@ -using BookWorm.Ordering.Constants; -using BookWorm.Ordering.Filters; -using Microsoft.AspNetCore.Mvc; - -namespace BookWorm.Ordering.Features.Orders.Create; +namespace BookWorm.Ordering.Features.Orders.Create; public sealed record CreateOrderRequest(string? Note); @@ -12,8 +8,7 @@ public void MapEndpoint(IEndpointRouteBuilder app) { app.MapPost("/api/orders", async ( - [FromHeader(Name = HeaderName.IdempotencyKey)] - string key, + [FromIdempotencyHeader] string key, CreateOrderRequest request, ISender sender) => await HandleAsync(request, sender)) .AddEndpointFilter() .Produces>(StatusCodes.Status201Created) diff --git a/src/BookWorm.Ordering/Features/Orders/EventHandlers/OrderEventHandler.cs b/src/BookWorm.Ordering/Features/Orders/EventHandlers/OrderEventHandler.cs index a6b304d..105cdd5 100644 --- a/src/BookWorm.Ordering/Features/Orders/EventHandlers/OrderEventHandler.cs +++ b/src/BookWorm.Ordering/Features/Orders/EventHandlers/OrderEventHandler.cs @@ -1,4 +1,6 @@ using BookWorm.Ordering.Domain.OrderAggregate.Events; +using BookWorm.Ordering.Extensions; +using BookWorm.Ordering.OpenTelemetry; namespace BookWorm.Ordering.Features.Orders.EventHandlers; @@ -8,25 +10,25 @@ public sealed class OrderEventHandler(IDocumentSession documentSession, ILogger< { public async Task Handle(OrderCancelledEvent notification, CancellationToken cancellationToken) { - logger.LogInformation("[{Event}] - Handling {OrderId}", nameof(OrderCancelledEvent), notification.Id); + OrderingTrace.LogOrderCreated(logger, nameof(OrderCancelledEvent), notification.Id); await WriteToAggregateAsync(notification, cancellationToken); } public async Task Handle(OrderCompletedEvent notification, CancellationToken cancellationToken) { - logger.LogInformation("[{Event}] - Handling {OrderId}", nameof(OrderCompletedEvent), notification.Id); + OrderingTrace.LogOrderCompleted(logger, nameof(OrderCompletedEvent), notification.Id); await WriteToAggregateAsync(notification, cancellationToken); } public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken) { - logger.LogInformation("[{Event}] - Handling {OrderId}", nameof(OrderCreatedEvent), notification.Id); + OrderingTrace.LogOrderCreated(logger, nameof(OrderCreatedEvent), notification.Id); await WriteToAggregateAsync(notification, cancellationToken); } private async Task WriteToAggregateAsync(EventBase @event, CancellationToken cancellationToken) { - using var activity = new ActivitySource(nameof(Marten)) + using var activity = new ActivitySource(MartenTelemetry.ActivityName) .StartActivity($"EventStore: {@event.GetType().Name}"); if (activity is not null) diff --git a/src/BookWorm.Ordering/Features/Orders/List/ListOrdersEndpoint.cs b/src/BookWorm.Ordering/Features/Orders/List/ListOrdersEndpoint.cs index c6d1410..a73d806 100644 --- a/src/BookWorm.Ordering/Features/Orders/List/ListOrdersEndpoint.cs +++ b/src/BookWorm.Ordering/Features/Orders/List/ListOrdersEndpoint.cs @@ -6,7 +6,6 @@ public void MapEndpoint(IEndpointRouteBuilder app) { app.MapGet("/orders", async (ISender sender) => await HandleAsync(sender)) .Produces>>() - .ProducesProblem(StatusCodes.Status404NotFound) .WithTags(nameof(Order)) .WithName("List Orders") .MapToApiVersion(new(1, 0)) diff --git a/src/BookWorm.Ordering/Filters/IdempotencyFilter.cs b/src/BookWorm.Ordering/Filters/IdempotencyFilter.cs index c81d823..83973bd 100644 --- a/src/BookWorm.Ordering/Filters/IdempotencyFilter.cs +++ b/src/BookWorm.Ordering/Filters/IdempotencyFilter.cs @@ -1,17 +1,18 @@ using BookWorm.Ordering.Constants; +using Microsoft.AspNetCore.Mvc; namespace BookWorm.Ordering.Filters; -public sealed class IdempotencyFilter(IRedisService redisService) : IEndpointFilter +internal sealed class IdempotencyFilter(IRedisService redisService) : IEndpointFilter { public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var request = context.HttpContext.Request; var requestMethod = request.Method; var requestPath = request.Path; - var requestId = request.Headers[HeaderName.IdempotencyKey].FirstOrDefault(); + var requestId = request.Headers[Http.Idempotency].FirstOrDefault(); - if (requestMethod is not "POST" and not "PATCH") + if (requestMethod is not Http.Methods.Post and not Http.Methods.Patch) { return await next(context); } @@ -20,8 +21,8 @@ public sealed class IdempotencyFilter(IRedisService redisService) : IEndpointFil if (string.IsNullOrEmpty(requestId)) { - errors.Add(new(HeaderName.IdempotencyKey, - $"{HeaderName.IdempotencyKey} header is required for POST and PATCH requests.")); + errors.Add(new(Http.Idempotency, + $"{Http.Idempotency} header is required for POST and PATCH requests.")); throw new ValidationException(errors.AsEnumerable()); } @@ -47,3 +48,8 @@ internal sealed class Idempotent public DateTime CreatedAt { get; set; } = DateTime.UtcNow; } } + +internal sealed class FromIdempotencyHeader : FromHeaderAttribute +{ + public new string Name => Http.Idempotency; +} diff --git a/src/BookWorm.Ordering/GlobalUsings.cs b/src/BookWorm.Ordering/GlobalUsings.cs index f85ffc8..957e297 100644 --- a/src/BookWorm.Ordering/GlobalUsings.cs +++ b/src/BookWorm.Ordering/GlobalUsings.cs @@ -3,17 +3,18 @@ global using Ardalis.GuardClauses; global using Ardalis.Result; global using Ardalis.Specification; +global using BookWorm.Constants; global using BookWorm.Core.SeedWork; global using BookWorm.Core.SharedKernel; global using BookWorm.Ordering.Domain.OrderAggregate; global using BookWorm.Ordering.Features.Orders; +global using BookWorm.Ordering.Filters; global using BookWorm.Ordering.Grpc; global using BookWorm.Ordering.Infrastructure.Data; global using BookWorm.Ordering.Infrastructure.Redis; global using BookWorm.ServiceDefaults; global using BookWorm.Shared.ActivityScope; global using BookWorm.Shared.Bus; -global using BookWorm.Shared.Constants; global using BookWorm.Shared.Converters; global using BookWorm.Shared.Endpoints; global using BookWorm.Shared.Exceptions; diff --git a/src/BookWorm.Ordering/Infrastructure/Data/Extension.cs b/src/BookWorm.Ordering/Infrastructure/Data/Extension.cs index 7511408..c2e593e 100644 --- a/src/BookWorm.Ordering/Infrastructure/Data/Extension.cs +++ b/src/BookWorm.Ordering/Infrastructure/Data/Extension.cs @@ -9,7 +9,7 @@ public static IHostApplicationBuilder AddPersistence(this IHostApplicationBuilde { builder.Services.AddMigration(); - builder.AddNpgsqlDbContext("orderingdb", configureDbContextOptions: + builder.AddNpgsqlDbContext(ServiceName.Database.Ordering, configureDbContextOptions: dbContextOptionsBuilder => { dbContextOptionsBuilder diff --git a/src/BookWorm.Ordering/Infrastructure/Redis/Extension.cs b/src/BookWorm.Ordering/Infrastructure/Redis/Extension.cs index a0598cd..a32a2e0 100644 --- a/src/BookWorm.Ordering/Infrastructure/Redis/Extension.cs +++ b/src/BookWorm.Ordering/Infrastructure/Redis/Extension.cs @@ -4,7 +4,7 @@ internal static class Extension { public static IHostApplicationBuilder AddRedisCache(this IHostApplicationBuilder builder) { - builder.AddRedisClient("redis"); + builder.AddRedisClient(ServiceName.Redis); builder.Services.AddSingleton(); diff --git a/src/BookWorm.Ordering/Infrastructure/Redis/RedisService.cs b/src/BookWorm.Ordering/Infrastructure/Redis/RedisService.cs index 0e270a2..2bd0db4 100644 --- a/src/BookWorm.Ordering/Infrastructure/Redis/RedisService.cs +++ b/src/BookWorm.Ordering/Infrastructure/Redis/RedisService.cs @@ -11,8 +11,8 @@ public sealed class RedisService(IConfiguration configuration) : IRedisService private readonly SemaphoreSlim _connectionLock = new(1, 1); private readonly Lazy _connectionMultiplexer = new(() => - ConnectionMultiplexer.Connect( - configuration.GetConnectionString("redis") ?? throw new InvalidOperationException())); + ConnectionMultiplexer.Connect(configuration.GetConnectionString(ServiceName.Redis) ?? + throw new InvalidOperationException())); private ConnectionMultiplexer ConnectionMultiplexer => _connectionMultiplexer.Value; diff --git a/src/BookWorm.Ordering/IntegrationEvents/EventHandlers/BasketCheckoutFailedIntegrationEventHandler.cs b/src/BookWorm.Ordering/IntegrationEvents/EventHandlers/BasketCheckoutFailedIntegrationEventHandler.cs index 5269697..471a047 100644 --- a/src/BookWorm.Ordering/IntegrationEvents/EventHandlers/BasketCheckoutFailedIntegrationEventHandler.cs +++ b/src/BookWorm.Ordering/IntegrationEvents/EventHandlers/BasketCheckoutFailedIntegrationEventHandler.cs @@ -2,14 +2,20 @@ namespace BookWorm.Ordering.IntegrationEvents.EventHandlers; -internal sealed class BasketCheckoutFailedIntegrationEventHandler(IRepository repository) - : IConsumer +internal sealed class BasketCheckoutFailedIntegrationEventHandler( + IRepository repository, + ILogger logger) : IConsumer { public async Task Consume(ConsumeContext context) { - var order = await repository.GetByIdAsync(context.Message.OrderId); + var @event = context.Message; - Guard.Against.NotFound(context.Message.OrderId, order); + logger.LogInformation("[{Consumer}] - Rollback order with Id: {OrderId}", + nameof(BasketCheckoutFailedIntegrationEventHandler), @event.OrderId); + + var order = await repository.GetByIdAsync(@event.OrderId); + + Guard.Against.NotFound(@event.OrderId, order); await repository.DeleteAsync(order); } diff --git a/src/BookWorm.Ordering/OpenTelemetry/MartenTelemetry.cs b/src/BookWorm.Ordering/OpenTelemetry/MartenTelemetry.cs new file mode 100644 index 0000000..7d34078 --- /dev/null +++ b/src/BookWorm.Ordering/OpenTelemetry/MartenTelemetry.cs @@ -0,0 +1,6 @@ +namespace BookWorm.Ordering.OpenTelemetry; + +public static class MartenTelemetry +{ + public const string ActivityName = "Marten"; +} diff --git a/src/BookWorm.Rating/Extensions/Extensions.cs b/src/BookWorm.Rating/Extensions/Extensions.cs index 5370d6b..7de583c 100644 --- a/src/BookWorm.Rating/Extensions/Extensions.cs +++ b/src/BookWorm.Rating/Extensions/Extensions.cs @@ -1,4 +1,6 @@ -namespace BookWorm.Rating.Extensions; +using BookWorm.Constants; + +namespace BookWorm.Rating.Extensions; internal static class Extensions { @@ -16,7 +18,7 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder) builder.Services.AddProblemDetails(); builder.AddDefaultAuthentication(); - builder.AddMongoDBClient("mongodb"); + builder.AddMongoDBClient(ServiceName.Database.Rating); builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(); @@ -38,7 +40,7 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder) builder.Services.AddSingleton(serviceProvider => { - var url = builder.Configuration.GetConnectionString("mongodb"); + var url = builder.Configuration.GetConnectionString(ServiceName.Database.Rating); var client = serviceProvider.GetService(); return client!.GetDatabase(MongoUrl.Create(url).DatabaseName); }); diff --git a/src/BookWorm.Rating/Features/Create/CreateFeedbackValidator.cs b/src/BookWorm.Rating/Features/Create/CreateFeedbackValidator.cs index c9cb7b5..bd13d33 100644 --- a/src/BookWorm.Rating/Features/Create/CreateFeedbackValidator.cs +++ b/src/BookWorm.Rating/Features/Create/CreateFeedbackValidator.cs @@ -1,4 +1,6 @@ -namespace BookWorm.Rating.Features.Create; +using BookWorm.Constants; + +namespace BookWorm.Rating.Features.Create; internal sealed class CreateFeedbackValidator : AbstractValidator { diff --git a/src/BookWorm.Rating/GlobalUsings.cs b/src/BookWorm.Rating/GlobalUsings.cs index db5f274..f0774bf 100644 --- a/src/BookWorm.Rating/GlobalUsings.cs +++ b/src/BookWorm.Rating/GlobalUsings.cs @@ -7,7 +7,6 @@ global using BookWorm.ServiceDefaults; global using BookWorm.Shared.ActivityScope; global using BookWorm.Shared.Bus; -global using BookWorm.Shared.Constants; global using BookWorm.Shared.Converters; global using BookWorm.Shared.Endpoints; global using BookWorm.Shared.Exceptions; diff --git a/src/BookWorm.Rating/IntegrationEvents/EventHandlers/FeedbackCreatedFailedIntegrationEventHandler.cs b/src/BookWorm.Rating/IntegrationEvents/EventHandlers/FeedbackCreatedFailedIntegrationEventHandler.cs index d209810..0006682 100644 --- a/src/BookWorm.Rating/IntegrationEvents/EventHandlers/FeedbackCreatedFailedIntegrationEventHandler.cs +++ b/src/BookWorm.Rating/IntegrationEvents/EventHandlers/FeedbackCreatedFailedIntegrationEventHandler.cs @@ -2,12 +2,18 @@ namespace BookWorm.Rating.IntegrationEvents.EventHandlers; -internal sealed class FeedbackCreatedFailedIntegrationEventHandler(IMongoCollection collection) - : IConsumer +internal sealed class FeedbackCreatedFailedIntegrationEventHandler( + IMongoCollection collection, + ILogger logger) : IConsumer { public async Task Consume(ConsumeContext context) { - var id = ObjectId.Parse(context.Message.FeedbackId); + var @event = context.Message; + + logger.LogInformation("[{Consumer}] - Rollback feedback with Id: {FeedbackId}", + nameof(FeedbackCreatedFailedIntegrationEventHandler), @event.FeedbackId); + + var id = ObjectId.Parse(@event.FeedbackId); var feedback = await collection.Find(f => f.Id == id).FirstOrDefaultAsync(); diff --git a/src/BookWorm.ServiceDefaults/Extensions.cs b/src/BookWorm.ServiceDefaults/Extensions.cs index 6f0aec4..ee8c63e 100644 --- a/src/BookWorm.ServiceDefaults/Extensions.cs +++ b/src/BookWorm.ServiceDefaults/Extensions.cs @@ -57,7 +57,6 @@ public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicati metrics.AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation() - .AddMeter("Marten") .AddMeter(InstrumentationOptions.MeterName) .AddMeter(ActivitySourceProvider.DefaultSourceName); }) @@ -66,7 +65,6 @@ public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicati tracing.AddAspNetCoreInstrumentation() .AddGrpcClientInstrumentation() .AddHttpClientInstrumentation() - .AddSource("Marten") .AddSource(DiagnosticHeaders.DefaultListenerName) .AddSource(ActivitySourceProvider.DefaultSourceName); }); diff --git a/src/BookWorm.Shared/BookWorm.Shared.csproj b/src/BookWorm.Shared/BookWorm.Shared.csproj index 78d41c8..8e4c9b9 100644 --- a/src/BookWorm.Shared/BookWorm.Shared.csproj +++ b/src/BookWorm.Shared/BookWorm.Shared.csproj @@ -32,6 +32,7 @@ + \ No newline at end of file diff --git a/src/BookWorm.Shared/Bus/Extension.cs b/src/BookWorm.Shared/Bus/Extension.cs index a538cfb..deb62f7 100644 --- a/src/BookWorm.Shared/Bus/Extension.cs +++ b/src/BookWorm.Shared/Bus/Extension.cs @@ -1,4 +1,5 @@ -using FluentValidation; +using BookWorm.Constants; +using FluentValidation; using MassTransit; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -13,7 +14,7 @@ public static IHostApplicationBuilder AddRabbitMqEventBus( Action? configure = null) { - var messaging = builder.Configuration.GetConnectionString("eventbus"); + var messaging = builder.Configuration.GetConnectionString(ServiceName.EventBus); if (string.IsNullOrWhiteSpace(messaging)) { diff --git a/tests/BookWorm.Catalog.IntegrationTests/BookWorm.Catalog.IntegrationTests.csproj b/tests/BookWorm.Catalog.IntegrationTests/BookWorm.Catalog.IntegrationTests.csproj index 10c63f4..2a30bf9 100644 --- a/tests/BookWorm.Catalog.IntegrationTests/BookWorm.Catalog.IntegrationTests.csproj +++ b/tests/BookWorm.Catalog.IntegrationTests/BookWorm.Catalog.IntegrationTests.csproj @@ -5,6 +5,7 @@ false false true + 449294a4-1735-47a7-8510-55e20b1aa4fa diff --git a/tests/BookWorm.Catalog.IntegrationTests/GlobalUsings.cs b/tests/BookWorm.Catalog.IntegrationTests/GlobalUsings.cs index 4f09705..eb5352f 100644 --- a/tests/BookWorm.Catalog.IntegrationTests/GlobalUsings.cs +++ b/tests/BookWorm.Catalog.IntegrationTests/GlobalUsings.cs @@ -1,7 +1,4 @@ -global using System.Net; -global using System.Net.Http.Json; -global using System.Security.Claims; -global using Asp.Versioning.Http; +global using System.Security.Claims; global using MassTransit; global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Hosting; diff --git a/tests/BookWorm.Rating.UnitTests/Application/HideFeedbackHandlerTests.cs b/tests/BookWorm.Rating.UnitTests/Application/HideFeedbackHandlerTests.cs index daccc0d..d4d3565 100644 --- a/tests/BookWorm.Rating.UnitTests/Application/HideFeedbackHandlerTests.cs +++ b/tests/BookWorm.Rating.UnitTests/Application/HideFeedbackHandlerTests.cs @@ -61,10 +61,8 @@ public async Task GivenValidRequest_ShouldThrowNotFoundException_WhenFeedbackNot It.IsAny()), Times.Never); } - [Theory] - [CombinatorialData] - public async Task GivenValidRequest_ShouldNoAdditionalActions_WhenFeedbackAlreadyHidden( - [CombinatorialValues(true, false)] bool isAlreadyHidden) + [Fact] + public async Task GivenValidRequest_ShouldNoAdditionalActions_WhenFeedbackAlreadyHidden() { // Arrange var feedbackId = ObjectId.GenerateNewId();