重构代码并抽取基础构建库

This commit is contained in:
hello 2025-03-20 22:13:45 +08:00
parent 9e6b5be92c
commit ccf1e7672d
55 changed files with 398 additions and 104 deletions

View File

@ -11,6 +11,7 @@
<PackageVersion Include="Aspire.Hosting.Redis" Version="9.1.0" />
<PackageVersion Include="Aspire.Hosting.Testing" Version="9.1.0" />
<PackageVersion Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.1.0" />
<PackageVersion Include="Aspire.RabbitMQ.Client" Version="9.1.0" />
<PackageVersion Include="Aspire.StackExchange.Redis.DistributedCaching" Version="9.1.0" />
<PackageVersion Include="AutoMapper" Version="14.0.0" />
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.Dapr" Version="9.3.0" />
@ -30,8 +31,10 @@
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" 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.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" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="9.1.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery.Yarp" Version="9.1.0" />
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.3.0" />

View File

@ -33,6 +33,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloShop.FunctionalTests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.BasketService.UnitTests", "tests\HelloShop.BasketService.UnitTests\HelloShop.BasketService.UnitTests.csproj", "{BE88233A-D6EB-462B-B53C-B588A0BEFAFC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libraries", "libraries", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.EventBus.Abstractions", "libraries\HelloShop.EventBus.Abstractions\HelloShop.EventBus.Abstractions.csproj", "{9959387D-8C47-462C-808E-A1E865658C3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.EventBus.Dapr", "libraries\HelloShop.EventBus.Dapr\HelloShop.EventBus.Dapr.csproj", "{2FD0A058-A447-42C7-A00B-711B2D0B3333}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.EventBus.RabbitMQ", "libraries\HelloShop.EventBus.RabbitMQ\HelloShop.EventBus.RabbitMQ.csproj", "{5D403BF6-9267-4B0F-AEB3-2143C8BBA8CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.DistributedLock", "libraries\HelloShop.DistributedLock\HelloShop.DistributedLock.csproj", "{86729635-8E31-4C53-81AE-7C410C848219}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloShop.DistributedLock.Dapr", "libraries\HelloShop.DistributedLock.Dapr\HelloShop.DistributedLock.Dapr.csproj", "{37F01A0B-50D6-48BA-8A20-FBADB5524CD7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -93,6 +105,26 @@ Global
{BE88233A-D6EB-462B-B53C-B588A0BEFAFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE88233A-D6EB-462B-B53C-B588A0BEFAFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE88233A-D6EB-462B-B53C-B588A0BEFAFC}.Release|Any CPU.Build.0 = Release|Any CPU
{9959387D-8C47-462C-808E-A1E865658C3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9959387D-8C47-462C-808E-A1E865658C3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9959387D-8C47-462C-808E-A1E865658C3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9959387D-8C47-462C-808E-A1E865658C3F}.Release|Any CPU.Build.0 = Release|Any CPU
{2FD0A058-A447-42C7-A00B-711B2D0B3333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2FD0A058-A447-42C7-A00B-711B2D0B3333}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FD0A058-A447-42C7-A00B-711B2D0B3333}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FD0A058-A447-42C7-A00B-711B2D0B3333}.Release|Any CPU.Build.0 = Release|Any CPU
{5D403BF6-9267-4B0F-AEB3-2143C8BBA8CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D403BF6-9267-4B0F-AEB3-2143C8BBA8CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D403BF6-9267-4B0F-AEB3-2143C8BBA8CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D403BF6-9267-4B0F-AEB3-2143C8BBA8CD}.Release|Any CPU.Build.0 = Release|Any CPU
{86729635-8E31-4C53-81AE-7C410C848219}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{86729635-8E31-4C53-81AE-7C410C848219}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86729635-8E31-4C53-81AE-7C410C848219}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86729635-8E31-4C53-81AE-7C410C848219}.Release|Any CPU.Build.0 = Release|Any CPU
{37F01A0B-50D6-48BA-8A20-FBADB5524CD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -111,6 +143,11 @@ Global
{E58F82E2-2E48-459B-A40E-497F24FC6DC1} = {1AD03316-A743-4E9D-B3BC-FB9499D15141}
{6BAA9747-E0D0-41B9-8A1B-88B777498C43} = {29BE158E-825E-48AB-A02D-4E537A5DC502}
{BE88233A-D6EB-462B-B53C-B588A0BEFAFC} = {29BE158E-825E-48AB-A02D-4E537A5DC502}
{9959387D-8C47-462C-808E-A1E865658C3F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{2FD0A058-A447-42C7-A00B-711B2D0B3333} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {845545A8-2006-46C3-ABD7-5BDF63F3858C}

View File

@ -4,7 +4,7 @@
using Dapr.Client;
using System.Diagnostics;
namespace HelloShop.ServiceDefaults.DistributedLocks
namespace HelloShop.DistributedLock.Dapr
{
public class DaprDistributedLock(DaprClient daprClient) : IDistributedLock
{
@ -12,13 +12,14 @@ namespace HelloShop.ServiceDefaults.DistributedLocks
{
expiryInSeconds = expiryInSeconds == default ? 60 : expiryInSeconds;
// The CallerMemberNameAttribute is used to get the name of the calling method.
string? lockOwner = new StackTrace().GetFrame(1)?.GetMethod()?.DeclaringType?.Name;
#pragma warning disable CS0618
#pragma warning disable CS0618
TryLockResponse response = await daprClient.Lock("lockstore", resourceId, lockOwner, expiryInSeconds, cancellationToken);
#pragma warning restore CS0618
return new DaprDistributedLockResult(response);
}
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.Extensions.DependencyInjection;
namespace HelloShop.DistributedLock.Dapr
{
public static class DaprDistributedLockExtensions
{
public static IServiceCollection AddDaprDistributedLock(this IServiceCollection services)
{
services.AddSingleton<IDistributedLock, DaprDistributedLock>();
return services;
}
}
}

View File

@ -3,7 +3,7 @@
using Dapr.Client;
namespace HelloShop.ServiceDefaults.DistributedLocks
namespace HelloShop.DistributedLock.Dapr
{
#pragma warning disable CS0618
public class DaprDistributedLockResult(TryLockResponse tryLockResponse) : IDistributedLockResult
@ -15,4 +15,4 @@ namespace HelloShop.ServiceDefaults.DistributedLocks
GC.SuppressFinalize(this);
}
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapr.AspNetCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HelloShop.DistributedLock\HelloShop.DistributedLock.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.ServiceDefaults.DistributedLocks
namespace HelloShop.DistributedLock
{
public interface IDistributedLock
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.ServiceDefaults.DistributedLocks
namespace HelloShop.DistributedLock
{
public interface IDistributedLockResult : IAsyncDisposable { }
}
}

View File

@ -4,7 +4,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
namespace HelloShop.EventBus.Abstractions
{
public record DistributedEvent
{

View File

@ -5,13 +5,13 @@ using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using System.Text.Json;
namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
namespace HelloShop.EventBus.Abstractions
{
public static class DistributedEventBusBuilderExtensions
public static class EventBusBuilderExtensions
{
public static IDistributedEventBusBuilder ConfigureJsonOptions(this IDistributedEventBusBuilder eventBusBuilder, Action<JsonSerializerOptions> configure)
public static IEventBusBuilder ConfigureJsonOptions(this IEventBusBuilder eventBusBuilder, Action<JsonSerializerOptions> configure)
{
eventBusBuilder.Services.Configure<DistributedEventBusOptions>(o =>
eventBusBuilder.Services.Configure<EventBusOptions>(o =>
{
configure(o.JsonSerializerOptions);
});
@ -19,11 +19,11 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
return eventBusBuilder;
}
public static IDistributedEventBusBuilder AddSubscription<TEvent, TEventHandler>(this IDistributedEventBusBuilder eventBusBuilder) where TEvent : DistributedEvent where TEventHandler : class, IDistributedEventHandler<TEvent>
public static IEventBusBuilder AddSubscription<TEvent, TEventHandler>(this IEventBusBuilder eventBusBuilder) where TEvent : DistributedEvent where TEventHandler : class, IDistributedEventHandler<TEvent>
{
eventBusBuilder.Services.AddKeyedTransient<IDistributedEventHandler, TEventHandler>(typeof(TEvent));
eventBusBuilder.Services.Configure<DistributedEventBusOptions>(o =>
eventBusBuilder.Services.Configure<EventBusOptions>(o =>
{
o.EventTypes[typeof(TEvent).Name] = typeof(TEvent);
});
@ -31,11 +31,11 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
return eventBusBuilder;
}
public static IDistributedEventBusBuilder AddSubscription(this IDistributedEventBusBuilder eventBusBuilder, Type eventType, Type eventHandlerType)
public static IEventBusBuilder AddSubscription(this IEventBusBuilder eventBusBuilder, Type eventType, Type eventHandlerType)
{
eventBusBuilder.Services.AddKeyedTransient(typeof(IDistributedEventHandler), eventType, eventHandlerType);
eventBusBuilder.Services.Configure<DistributedEventBusOptions>(o =>
eventBusBuilder.Services.Configure<EventBusOptions>(o =>
{
o.EventTypes[eventType.Name] = eventType;
});
@ -43,7 +43,7 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
return eventBusBuilder;
}
public static IDistributedEventBusBuilder AddSubscriptionFromAssembly(this IDistributedEventBusBuilder eventBusBuilder, Assembly? assembly = null)
public static IEventBusBuilder AddSubscriptionFromAssembly(this IEventBusBuilder eventBusBuilder, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();

View File

@ -4,9 +4,9 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
namespace HelloShop.EventBus.Abstractions
{
public class DistributedEventBusOptions
public class EventBusOptions
{
public Dictionary<string, Type> EventTypes { get; } = [];
@ -17,4 +17,4 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault ? new DefaultJsonTypeInfoResolver() : JsonTypeInfoResolver.Combine()
};
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
namespace HelloShop.EventBus.Abstractions
{
public interface IDistributedEventHandler
{

View File

@ -1,9 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
namespace HelloShop.EventBus.Abstractions
{
public interface IDistributedEventBus
public interface IEventBus
{
Task PublishAsync(DistributedEvent @event, CancellationToken cancellationToken = default);
}

View File

@ -3,9 +3,9 @@
using Microsoft.Extensions.DependencyInjection;
namespace HelloShop.ServiceDefaults.DistributedEvents.Abstractions
namespace HelloShop.EventBus.Abstractions
{
public interface IDistributedEventBusBuilder
public interface IEventBusBuilder
{
public IServiceCollection Services { get; }
}

View File

@ -4,7 +4,7 @@
using Dapr;
using System.Text.Json.Serialization;
namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
namespace HelloShop.EventBus.Dapr
{
public class DaprCloudEvent<TData>(TData data) : CloudEvent<TData>(data)
{

View File

@ -2,13 +2,13 @@
// See the license file in the project root for more information.
using Dapr.Client;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
namespace HelloShop.EventBus.Dapr
{
public class DaprDistributedEventBus(DaprClient daprClient, IOptions<DaprDistributedEventBusOptions> options, ILogger<DaprDistributedEventBus> logger) : IDistributedEventBus
public class DaprEventBus(DaprClient daprClient, IOptions<DaprEventBusOptions> options, ILogger<DaprEventBus> logger) : IEventBus
{
public async Task PublishAsync(DistributedEvent @event, CancellationToken cancellationToken = default)
{
@ -22,4 +22,4 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
await daprClient.PublishEventAsync(pubSubName, topicName, data, cancellationToken);
}
}
}
}

View File

@ -1,9 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using FluentValidation;
using FluentValidation.Results;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
@ -14,25 +12,25 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Text.Json;
namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
namespace HelloShop.EventBus.Dapr
{
public static class DaprDistributedEventBusExtensions
public static class DaprEventBusExtensions
{
private const string DefaultSectionName = "DaprDistributedEventBus";
private const string DefaultSectionName = "DaprEventBus";
public static IDistributedEventBusBuilder AddDaprDistributedEventBus(this IHostApplicationBuilder builder, string sectionName = DefaultSectionName)
public static IEventBusBuilder AddDaprEventBus(this IHostApplicationBuilder builder, string sectionName = DefaultSectionName)
{
ArgumentNullException.ThrowIfNull(builder);
builder.Services.AddDaprClient();
DaprDistributedEventBusOptions daprOptions = new();
DaprEventBusOptions daprOptions = new();
builder.Configuration.GetSection(sectionName).Bind(daprOptions);
builder.Services.AddSingleton(Options.Create(daprOptions));
builder.Services.AddSingleton<IDistributedEventBus, DaprDistributedEventBus>();
builder.Services.AddSingleton<IEventBus, DaprEventBus>();
if (daprOptions.RequireAuthenticatedDaprApiToken)
{
@ -43,18 +41,18 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
return new DistributedEventBusBuilder(builder.Services);
}
private class DistributedEventBusBuilder(IServiceCollection services) : IDistributedEventBusBuilder
private class DistributedEventBusBuilder(IServiceCollection services) : IEventBusBuilder
{
public IServiceCollection Services => services;
}
public static WebApplication MapDaprDistributedEventBus(this WebApplication app)
public static WebApplication MapDaprEventBus(this WebApplication app)
{
ArgumentNullException.ThrowIfNull(app);
var eventBusOptions = app.Services.GetRequiredService<IOptions<DistributedEventBusOptions>>().Value;
var eventBusOptions = app.Services.GetRequiredService<IOptions<EventBusOptions>>().Value;
var daprEventBusOptions = app.Services.GetRequiredService<IOptions<DaprDistributedEventBusOptions>>().Value;
var daprEventBusOptions = app.Services.GetRequiredService<IOptions<DaprEventBusOptions>>().Value;
RouteHandlerBuilder routeHandler = app.MapPost($"/api/DistributedEvents", async (DaprCloudEvent<JsonElement> cloudEvent, IHttpContextAccessor contextAccessor) =>
{
@ -72,16 +70,6 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
return Results.BadRequest();
}
if (httpContext.RequestServices.GetService(typeof(IValidator<>).MakeGenericType(eventType)) is IValidator validator)
{
ValidationResult validationResult = await validator.ValidateAsync(new ValidationContext<DistributedEvent>(@event));
if (!validationResult.IsValid)
{
return Results.ValidationProblem(validationResult.ToDictionary());
}
}
foreach (var handler in httpContext.RequestServices.GetKeyedServices<IDistributedEventHandler>(eventType))
{
await handler.HandleAsync(@event);
@ -91,13 +79,13 @@ namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
}).WithTags(nameof(DistributedEvent));
app.MapSubscribeHandler();
foreach (var subscription in eventBusOptions.EventTypes)
{
routeHandler.WithTopic(daprEventBusOptions.PubSubName, subscription.Key, enableRawPayload: false);
}
app.MapSubscribeHandler();
return app;
}
}

View File

@ -1,12 +1,10 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks
namespace HelloShop.EventBus.Dapr
{
public class DaprDistributedEventBusOptions
public class DaprEventBusOptions
{
public const string SectionName = "DaprDistributedEventBus";
public string PubSubName { get; set; } = "event-bus-pubsub";
public bool RequireAuthenticatedDaprApiToken { get; set; } = false;

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapr.AspNetCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HelloShop.EventBus.Abstractions\HelloShop.EventBus.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.RabbitMQ.Client" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HelloShop.EventBus.Abstractions\HelloShop.EventBus.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,121 @@
// 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;
using Microsoft.Extensions.Options;
using Polly;
using Polly.Retry;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HelloShop.EventBus.RabbitMQ
{
public sealed class RabbitMQEventBus(ILogger<RabbitMQEventBus> logger, IServiceProvider serviceProvider, IOptions<RabbitMQEventBusOptions> rabbitMQEventBusOptions, IOptions<EventBusOptions> eventBusOptions) : IEventBus, IDisposable, IHostedService
{
private readonly ResiliencePipeline _pipeline = CreateResiliencePipeline(rabbitMQEventBusOptions.Value.RetryCount);
private readonly string _queueName = rabbitMQEventBusOptions.Value.QueueName;
private readonly string _exchangeName = rabbitMQEventBusOptions.Value.ExchangeName;
private readonly EventBusOptions _eventBusOptions = eventBusOptions.Value;
private IConnection? _rabbitMQConnection;
private IModel? _consumerChannel;
public Task StartAsync(CancellationToken cancellationToken)
{
Task.Factory.StartNew(() =>
{
try
{
_rabbitMQConnection = serviceProvider.GetRequiredService<IConnection>();
_consumerChannel = _rabbitMQConnection.CreateModel();
_consumerChannel.ExchangeDeclare(exchange: _exchangeName, type: ExchangeType.Direct);
_consumerChannel.QueueDeclare(queue: _queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
var consumer = new AsyncEventingBasicConsumer(_consumerChannel);
consumer.Received += OnMessageReceivedAsync;
_consumerChannel.BasicConsume(queue: _queueName, autoAck: false, consumer: consumer);
foreach (var (eventName, _) in _eventBusOptions.EventTypes)
{
_consumerChannel.QueueBind(queue: _queueName, exchange: _exchangeName, routingKey: eventName);
}
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while starting the RabbitMQ event bus.");
}
}, TaskCreationOptions.LongRunning);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public void Dispose() => _consumerChannel?.Dispose();
public Task PublishAsync(DistributedEvent @event, CancellationToken cancellationToken = default)
{
string routingKey = @event.GetType().Name;
using var channel = _rabbitMQConnection?.CreateModel() ?? throw new InvalidOperationException("RabbitMQ connection is not available.");
channel.ExchangeDeclare(exchange: _exchangeName, type: ExchangeType.Direct);
var body = JsonSerializer.SerializeToUtf8Bytes(@event, @event.GetType(), _eventBusOptions.JsonSerializerOptions);
return _pipeline.Execute(() =>
{
IBasicProperties properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2;
channel.BasicPublish(exchange: _exchangeName, routingKey: routingKey, mandatory: true, basicProperties: properties, body: body);
return Task.CompletedTask;
});
}
private async Task OnMessageReceivedAsync(object sender, BasicDeliverEventArgs eventArgs)
{
string eventName = eventArgs.RoutingKey;
string message = Encoding.UTF8.GetString(eventArgs.Body.Span);
if (!_eventBusOptions.EventTypes.TryGetValue(eventName, out var eventType))
{
return;
}
await using var scope = serviceProvider.CreateAsyncScope();
var distributedEvent = JsonSerializer.Deserialize(message, eventType, _eventBusOptions.JsonSerializerOptions) as DistributedEvent;
foreach (var handler in scope.ServiceProvider.GetKeyedServices<IDistributedEventHandler>(eventType))
{
if (distributedEvent is not null)
{
await handler.HandleAsync(distributedEvent);
}
}
_consumerChannel?.BasicAck(eventArgs.DeliveryTag, multiple: false);
}
private static ResiliencePipeline CreateResiliencePipeline(int retryCount)
{
var retryOptions = new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<BrokerUnreachableException>().Handle<SocketException>(),
MaxRetryAttempts = retryCount,
DelayGenerator = (context) => ValueTask.FromResult(GenerateDelay(context.AttemptNumber))
};
return new ResiliencePipelineBuilder().AddRetry(retryOptions).Build();
static TimeSpan? GenerateDelay(int attempt) => TimeSpan.FromSeconds(Math.Pow(2, attempt));
}
}
}

View File

@ -0,0 +1,36 @@
// 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;
namespace HelloShop.EventBus.RabbitMQ
{
public static class RabbitMQEventBusExtensions
{
private const string DefaultSectionName = "RabbitMQEventBus";
public static IEventBusBuilder AddRabbitMqEventBus(this IHostApplicationBuilder builder, string connectionName, string sectionName = DefaultSectionName)
{
ArgumentNullException.ThrowIfNull(builder);
builder.AddRabbitMQClient(connectionName, configureConnectionFactory: factory =>
{
factory.DispatchConsumersAsync = true;
});
builder.Services.Configure<EventBusOptions>(builder.Configuration.GetSection(sectionName));
builder.Services.AddSingleton<IEventBus, RabbitMQEventBus>();
builder.Services.AddSingleton<IHostedService>(sp => (RabbitMQEventBus)sp.GetRequiredService<IEventBus>());
return new EventBusBuilder(builder.Services);
}
private class EventBusBuilder(IServiceCollection services) : IEventBusBuilder
{
public IServiceCollection Services => services;
}
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.EventBus.RabbitMQ
{
public class RabbitMQEventBusOptions
{
public string ExchangeName { get; set; } = "event-bus-exchange";
public required string QueueName { get; set; }
public int RetryCount { get; set; } = 10;
}
}

View File

@ -3,7 +3,7 @@
using HelloShop.BasketService.DistributedEvents.Events;
using HelloShop.BasketService.Repositories;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.BasketService.DistributedEvents.EventHandling
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.BasketService.DistributedEvents.Events
{

View File

@ -20,6 +20,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libraries\HelloShop.EventBus.Dapr\HelloShop.EventBus.Dapr.csproj" />
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
</ItemGroup>
</Project>

View File

@ -5,8 +5,8 @@ using HelloShop.BasketService.DistributedEvents.EventHandling;
using HelloShop.BasketService.DistributedEvents.Events;
using HelloShop.BasketService.Repositories;
using HelloShop.BasketService.Services;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
using HelloShop.EventBus.Abstractions;
using HelloShop.EventBus.Dapr;
using HelloShop.ServiceDefaults.Extensions;
using Microsoft.IdentityModel.Tokens;
using System.Text;
@ -41,7 +41,7 @@ builder.Services.AddLocalization().AddPermissionDefinitions();
builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
builder.Services.AddCors();
builder.AddDaprDistributedEventBus().AddSubscription<OrderStartedDistributedEvent, OrderStartedDistributedEventHandler>();
builder.AddDaprEventBus().AddSubscription<OrderStartedDistributedEvent, OrderStartedDistributedEventHandler>();
// End addd extensions services to the container.
@ -55,7 +55,7 @@ app.MapDefaultEndpoints();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.").WithTags("Welcome");
// Configure extensions request pipeline.
app.MapDaprDistributedEventBus();
app.MapDaprEventBus();
app.UseAuthentication().UseAuthorization();
app.MapGrpcService<GreeterService>();
app.MapGrpcService<CustomerBasketService>();

View File

@ -1,11 +1,11 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.OrderingService.Services;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.DistributedEvents.EventHandling

View File

@ -1,10 +1,10 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.EventHandling
{

View File

@ -1,10 +1,10 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.EventHandling
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
using System.Diagnostics.CodeAnalysis;
namespace HelloShop.OrderingService.Entities.EventLogs

View File

@ -1,14 +1,14 @@
// 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.Dapr;
using HelloShop.OrderingService.Behaviors;
using HelloShop.OrderingService.Constants;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.OrderingService.Queries;
using HelloShop.OrderingService.Services;
using HelloShop.OrderingService.Workers;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
using HelloShop.ServiceDefaults.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
@ -57,7 +57,7 @@ namespace HelloShop.OrderingService.Extensions
builder.Services.AddTransient<ISmsSender, MessageService>().AddTransient<IEmailSender, MessageService>();
builder.AddDaprDistributedEventBus().AddSubscriptionFromAssembly();
builder.AddDaprEventBus().AddSubscriptionFromAssembly();
builder.Services.Configure<HostOptions>(hostOptions =>
{
@ -79,7 +79,7 @@ namespace HelloShop.OrderingService.Extensions
app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseOpenApi();
app.UseDataSeedingProviders();
app.MapDaprDistributedEventBus();
app.MapDaprEventBus();
return app;
}

View File

@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\libraries\HelloShop.EventBus.Dapr\HelloShop.EventBus.Dapr.csproj" />
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,8 +1,8 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.Entities.EventLogs;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System.Text.Json;

View File

@ -1,13 +1,13 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.Entities.EventLogs;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.Services
{
public class DistributedEventService<TContext>(TContext dbContext, IDistributedEventBus distributedEventBus, ILogger<DistributedEventService<TContext>> logger) : IDistributedEventService, IDisposable where TContext : DbContext
public class DistributedEventService<TContext>(TContext dbContext, IEventBus distributedEventBus, ILogger<DistributedEventService<TContext>> logger) : IDistributedEventService, IDisposable where TContext : DbContext
{
private volatile bool _disposedValue;

View File

@ -1,8 +1,8 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.Entities.EventLogs;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.Services
{

View File

@ -2,10 +2,10 @@
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.Workers
@ -23,7 +23,7 @@ namespace HelloShop.OrderingService.Workers
using var scope = serviceScopeFactory.CreateAsyncScope();
var dbContext = scope.ServiceProvider.GetRequiredService<OrderingServiceDbContext>();
var distributedEventBus = scope.ServiceProvider.GetRequiredService<IDistributedEventBus>();
var distributedEventBus = scope.ServiceProvider.GetRequiredService<IEventBus>();
DateTimeOffset dateTimeOffset = timeProvider.GetUtcNow().AddMinutes(-1);

View File

@ -1,10 +1,10 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.EventBus.Abstractions;
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.Workers
@ -23,7 +23,7 @@ namespace HelloShop.OrderingService.Workers
using var scope = serviceScopeFactory.CreateAsyncScope();
var dbContext = scope.ServiceProvider.GetRequiredService<OrderingServiceDbContext>();
var distributedEventBus = scope.ServiceProvider.GetRequiredService<IDistributedEventBus>();
var distributedEventBus = scope.ServiceProvider.GetRequiredService<IEventBus>();
bool paymentSucceeded = Random.Shared.NextDouble() > 0.3;

View File

@ -1,15 +1,15 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.DistributedLock;
using HelloShop.EventBus.Abstractions;
using HelloShop.ProductService.DistributedEvents.Events;
using HelloShop.ProductService.Entities.Products;
using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedLocks;
namespace HelloShop.ProductService.DistributedEvents.EventHandling
{
public class OrderAwaitingValidationDistributedEventHandler(ProductServiceDbContext dbContext, IDistributedEventBus distributedEventBus, IDistributedLock distributedLock, ILogger<OrderAwaitingValidationDistributedEventHandler> logger) : IDistributedEventHandler<OrderAwaitingValidationDistributedEvent>
public class OrderAwaitingValidationDistributedEventHandler(ProductServiceDbContext dbContext, IEventBus distributedEventBus, IDistributedLock distributedLock, ILogger<OrderAwaitingValidationDistributedEventHandler> logger) : IDistributedEventHandler<OrderAwaitingValidationDistributedEvent>
{
public async Task HandleAsync(OrderAwaitingValidationDistributedEvent @event)
{

View File

@ -1,11 +1,11 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.DistributedLock;
using HelloShop.EventBus.Abstractions;
using HelloShop.ProductService.DistributedEvents.Events;
using HelloShop.ProductService.Entities.Products;
using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedLocks;
namespace HelloShop.ProductService.DistributedEvents.EventHandling
{

View File

@ -1,7 +1,8 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{

View File

@ -1,7 +1,7 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.EventBus.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{

View File

@ -5,6 +5,8 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\libraries\HelloShop.DistributedLock.Dapr\HelloShop.DistributedLock.Dapr.csproj" />
<ProjectReference Include="..\..\libraries\HelloShop.EventBus.Dapr\HelloShop.EventBus.Dapr.csproj" />
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,11 +1,11 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.DistributedLock.Dapr;
using HelloShop.EventBus.Abstractions;
using HelloShop.EventBus.Dapr;
using HelloShop.ProductService.Constants;
using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
using HelloShop.ServiceDefaults.DistributedLocks;
using HelloShop.ServiceDefaults.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
@ -42,8 +42,8 @@ builder.Services.AddOpenApi();
builder.Services.AddModelMapper().AddModelValidator();
builder.Services.AddLocalization().AddPermissionDefinitions();
builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
builder.AddDaprDistributedEventBus().AddSubscriptionFromAssembly();
builder.Services.AddSingleton<IDistributedLock, DaprDistributedLock>();
builder.AddDaprEventBus().AddSubscriptionFromAssembly();
builder.Services.AddDaprDistributedLock();
builder.Services.AddSingleton(TimeProvider.System);
// End addd extensions services to the container.
@ -62,7 +62,7 @@ app.UseDataSeedingProviders();
app.UseCustomLocalization();
app.UseOpenApi();
app.MapGroup("api/Permissions").MapPermissionDefinitions("Permissions");
app.MapDaprDistributedEventBus();
app.MapDaprEventBus();
// End configure extensions request pipeline.
app.Run();