diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6f48480 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/sample/UltraSpeedBus.WebAPI/bin/Debug/net8.0/UltraSpeedBus.WebAPI.dll", + "args": [], + "cwd": "${workspaceFolder}/sample/UltraSpeedBus.WebAPI", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..fbf5fc4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,53 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build2", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "promptOnClose": false + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 7be00a9..5785a4d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ UltraSpeedBus is a free, open-source messaging framework for .NET, engineered fo | ---------------------------- | --------------------------------------------------------------------------------------------- | | `UltraSpeedBus` | The core library with message transport, context, pipelines, and integration implementations. | | `UltraSpeedBus.Abstractions` | Contains the core contracts, interfaces, and message envelope definitions for the system. | +| `UltraSpeedBus.Extensions.DependencyInjection` | Inject your dependencies | ## Features @@ -23,18 +24,62 @@ UltraSpeedBus is a free, open-source messaging framework for .NET, engineered fo # Install the packages via NuGet dotnet add package UltraSpeedBus dotnet add package UltraSpeedBus.Abstractions +dotnet add package UltraSpeedBus.Extensions.DependencyInjection ``` +## Command handler + ```csharp using UltraSpeedBus; using UltraSpeedBus.Abstractions; -// Create a message -var message = new MyCommand { Name = "Test" }; -var envelope = MessageFactory.Create(message); +// Create a command and command Handler with ICommandHandler +public sealed record CreateOrderCommand(string Product, int Quantity); +public sealed record OrderResult(int OrderId); + +public class CreateOrderHandler : ICommandHandler +{ + public Task Handle(CommandContext request) + { + int generatedId = Random.Shared.Next(1000, 9999); + return Task.FromResult(new OrderResult(generatedId)); + } +} +``` + +## Query Handler + +```cs +public sealed record GetOrderQuery(int OrderId); +public sealed record OrderDto(int OrderId, string Description); + +public class GetOrderQueryHandler : IQueryHandler +{ + public Task Handle(QueryContext context) + { + if (context.Query.OrderId == 42) + { + return Task.FromResult(new OrderDto(42, "Example Order")); + } + + return Task.FromResult(null); + } +} +``` + +## Event Handler + +```cs +public sealed record OrderCreatedEvent(int OrderId); -// Send using your transport implementation (e.g., Azure Service Bus) -await producer.SendAsync(envelope); +public class OrderCreatedEventHandler : IEventHandler +{ + public Task Handle(EventContext context) + { + Console.WriteLine($"[Event] Order created → Id = {context.Event.OrderId}"); + return Task.CompletedTask; + } +} ``` ## Contributing diff --git a/UltraSpeedBus.Abstractions/Message/ICommand.cs b/UltraSpeedBus.Abstractions/Message/ICommand.cs deleted file mode 100644 index 2587b7c..0000000 --- a/UltraSpeedBus.Abstractions/Message/ICommand.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -/// -/// Command message marker interface -/// -public interface ICommand : IMessage {} diff --git a/UltraSpeedBus.Abstractions/Message/ICommandHandler.cs b/UltraSpeedBus.Abstractions/Message/ICommandHandler.cs deleted file mode 100644 index 2022b97..0000000 --- a/UltraSpeedBus.Abstractions/Message/ICommandHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public interface ICommandHandler - where TCommand : ICommand -{ - Task HandleAsync(TCommand command, CancellationToken cancellationToken); -} diff --git a/UltraSpeedBus.Abstractions/Message/IEvent.cs b/UltraSpeedBus.Abstractions/Message/IEvent.cs deleted file mode 100644 index 9f9ce4f..0000000 --- a/UltraSpeedBus.Abstractions/Message/IEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -/// -/// Event message marker interface -/// -public interface IEvent : IMessage {} diff --git a/UltraSpeedBus.Abstractions/Message/IEventHandler.cs b/UltraSpeedBus.Abstractions/Message/IEventHandler.cs deleted file mode 100644 index 8791ab3..0000000 --- a/UltraSpeedBus.Abstractions/Message/IEventHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public interface IEventHandler - where TEvent : IEvent -{ - Task HandleAsync(TEvent @event, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Message/IMessage.cs b/UltraSpeedBus.Abstractions/Message/IMessage.cs deleted file mode 100644 index a554c61..0000000 --- a/UltraSpeedBus.Abstractions/Message/IMessage.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public interface IMessage -{ - /// - /// It will be used for Saga, Idempotency, etc. - /// - Guid MessageId { get; } - - /// - /// It will be used for logging, tracing, etc. - /// - DateTime Timestamp { get; } -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Message/IQuery.cs b/UltraSpeedBus.Abstractions/Message/IQuery.cs deleted file mode 100644 index 1a2a8b0..0000000 --- a/UltraSpeedBus.Abstractions/Message/IQuery.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public interface IQuery : IMessage {} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Message/IQueryHandler.cs b/UltraSpeedBus.Abstractions/Message/IQueryHandler.cs deleted file mode 100644 index 03740ec..0000000 --- a/UltraSpeedBus.Abstractions/Message/IQueryHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public interface IQueryHandler - where TQuery : IQuery -{ - Task HandleAsync(TQuery query, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Transport/ITransport.cs b/UltraSpeedBus.Abstractions/Transport/ITransport.cs deleted file mode 100644 index 6dca86c..0000000 --- a/UltraSpeedBus.Abstractions/Transport/ITransport.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public interface ITransport -{ - ITransportProducer CreateProducer(); - ITransportConsumer CreateConsumer(string queueOrTopic); -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Transport/ITransportConsumer.cs b/UltraSpeedBus.Abstractions/Transport/ITransportConsumer.cs deleted file mode 100644 index 6ec83e7..0000000 --- a/UltraSpeedBus.Abstractions/Transport/ITransportConsumer.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -/// -/// Represents a transport-agnostic consumer interface that supports multiple message consumption patterns: -/// - Long polling (e.g., AWS SQS) -/// - Push-based delivery (e.g., Azure Service Bus) -/// - Subscription streaming (e.g., Apache Kafka) -/// - Polling loop (e.g., Redis Streams) -/// -public interface ITransportConsumer : IAsyncDisposable -{ - Task InitializeAsync(CancellationToken cancellationToken = default); - - Task StartConsumingAsync( - Func handler, - CancellationToken cancellationToken = default); - - Task StopConsumingAsync(CancellationToken cancellationToken = default); -} - -public class ConsumerTransportContext -{ -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Transport/ITransportProducer.cs b/UltraSpeedBus.Abstractions/Transport/ITransportProducer.cs deleted file mode 100644 index a8c3911..0000000 --- a/UltraSpeedBus.Abstractions/Transport/ITransportProducer.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public class MessageEnvelope -{ - public Guid MessageId { get; init; } - public string MessageType { get; init; } = null!; - public byte[] Payload { get; init; } = null!; -} - -/// -/// Defines a transport producer capable of sending and publishing messages. -/// it will be used for Azure Service Bus, AWS, RabbitMQ fanout, SQS, Redis Streams, etc. -/// -public interface ITransportProducer : IAsyncDisposable -{ - Task InitializeAsync(CancellationToken cancellationToken = default); - - Task SendAsync(string queue, MessageEnvelope envelop, CancellationToken cancellationToken = default); - Task PublishAsync(string topic, MessageEnvelope envelop, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/UltraSpeedBus.slnx b/UltraSpeedBus.slnx index a6bb6dd..bb6ee9d 100644 --- a/UltraSpeedBus.slnx +++ b/UltraSpeedBus.slnx @@ -1,7 +1,11 @@ + + + - - - + + + + diff --git a/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs b/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs new file mode 100644 index 0000000..796dd84 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs @@ -0,0 +1,51 @@ +using UltraSpeedBus.Abstractions; +using UltraSpeedBus.Abstractions.Contracts; + + +namespace UltraSpeedBus.WebAPI; + + +#region Command +public sealed record CreateOrder(string Product, int Quantity); +public sealed record OrderResult(int OrderId); +public sealed record OrderCreated(int OrderId); + +public class CreateOrderHandler : ICommandHandler +{ + public Task Handle(CommandContext request) + { + // Simula criação de pedido + int generatedId = Random.Shared.Next(1000, 9999); + + return Task.FromResult(new OrderResult(generatedId)); + } +} +#endregion + +#region Query +public sealed record GetOrder(int OrderId); +public sealed record OrderDto(int OrderId, string Description); +public class GetOrderQueryHandler : IQueryHandler +{ + public Task Handle(QueryContext context) + { + if (context.Query.OrderId == 42) + { + return Task.FromResult(new OrderDto(42, "Example Order")); + } + + return Task.FromResult(null); + } +} +#endregion + +#region Event +public class OrderCreatedEventHandler : IEventHandler +{ + public Task Handle(EventContext context) + { + Console.WriteLine($"[Event] Order created → Id = {context.Event.OrderId}"); + return Task.CompletedTask; + } +} +#endregion \ No newline at end of file diff --git a/sample/UltraSpeedBus.WebAPI/Program.cs b/sample/UltraSpeedBus.WebAPI/Program.cs new file mode 100644 index 0000000..91cf792 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/Program.cs @@ -0,0 +1,70 @@ +using UltraSpeedBus.Abstractions; +using UltraSpeedBus.Abstractions.Contracts; +using UltraSpeedBus.Abstractions.Mediator; +using UltraSpeedBus.Extensions.DepedencyInjection; +using UltraSpeedBus.WebAPI; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddUltraSpeedBus(); + +builder.Services.AddSingleton, CreateOrderHandler>(); +builder.Services.AddSingleton, GetOrderQueryHandler>(); +builder.Services.AddSingleton, OrderCreatedEventHandler>(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +var mediator = app.Services.GetRequiredService(); + +mediator.RegisterCommandHandler( + (ctx) => app.Services.GetRequiredService>().Handle(ctx) +); + +mediator.RegisterQueryHandler( + (ctx) => app.Services.GetRequiredService>().Handle(ctx) +); + +mediator.RegisterEventHandler( + (ctx) => app.Services.GetRequiredService>().Handle(ctx) +); + +app.MapPost("/orders", async (CreateOrder command, ISend sender) => +{ + var result = await sender.SendAsync(command); + return Results.Ok(result); +}); + +// GET /orders/{id} -> Send Query +app.MapGet("/orders/{id:int}", async (int id, ISend sender) => +{ + var result = await sender.SendAsync(new GetOrder(id)); + if (result is null) + return Results.NotFound(); + + return Results.Ok(result); +}); + +// POST /simulate -> Publish Event directly +app.MapPost("/simulate", async (IPublish publisher) => +{ + await publisher.PublishAsync(new OrderCreated(999)); + return Results.Ok("Event Published"); +}); + +// Example: Dynamic event consumer (runtime registration) +mediator.ConnectHandlerAsync(async ctx => +{ + Console.WriteLine($"[Dynamic Consumer] Order created with {ctx.Message.OrderId}"); +}); + +app.Run(); diff --git a/sample/UltraSpeedBus.WebAPI/Properties/launchSettings.json b/sample/UltraSpeedBus.WebAPI/Properties/launchSettings.json new file mode 100644 index 0000000..7c6c7a3 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59279", + "sslPort": 44363 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5185", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7286;http://localhost:5185", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj new file mode 100644 index 0000000..284e404 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http new file mode 100644 index 0000000..6bc9dce --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http @@ -0,0 +1,12 @@ +@UltraSpeedBus.WebAPI_HostAddress = http://localhost:5185 + +GET {{UltraSpeedBus.WebAPI_HostAddress}}/weatherforecast/ +Accept: application/json + +### +POST /orders +Body: +{ + "product": "Pizza", + "quantity": 2 +} \ No newline at end of file diff --git a/sample/UltraSpeedBus.WebAPI/appsettings.Development.json b/sample/UltraSpeedBus.WebAPI/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/sample/UltraSpeedBus.WebAPI/appsettings.json b/sample/UltraSpeedBus.WebAPI/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/UltraSpeedBus.Abstractions/Context/CommandContext.cs b/src/UltraSpeedBus.Abstractions/Context/CommandContext.cs new file mode 100644 index 0000000..202e311 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Context/CommandContext.cs @@ -0,0 +1,7 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public class CommandContext +{ + public TCommand Command { get; } + public CommandContext(TCommand command) => Command = command; +} diff --git a/src/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs b/src/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs new file mode 100644 index 0000000..be4822b --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs @@ -0,0 +1,7 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public class ConsumeContext +{ + public T Message { get; } + public ConsumeContext(T message) => Message = message; +} diff --git a/src/UltraSpeedBus.Abstractions/Context/EventContext.cs b/src/UltraSpeedBus.Abstractions/Context/EventContext.cs new file mode 100644 index 0000000..19bf5e4 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Context/EventContext.cs @@ -0,0 +1,7 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public class EventContext +{ + public TEvent Event { get; } + public EventContext(TEvent @event) => Event = @event; +} diff --git a/src/UltraSpeedBus.Abstractions/Context/QueryContext.cs b/src/UltraSpeedBus.Abstractions/Context/QueryContext.cs new file mode 100644 index 0000000..6228d40 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Context/QueryContext.cs @@ -0,0 +1,7 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public class QueryContext +{ + public TQuery Query { get; } + public QueryContext(TQuery query) => Query = query; +} diff --git a/src/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs b/src/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs new file mode 100644 index 0000000..2c62537 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs @@ -0,0 +1,6 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public interface IConsumerConnector +{ + IHandlerHandle ConnectHandlerAsync(Func, Task> handler); +} diff --git a/src/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs b/src/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs new file mode 100644 index 0000000..70a85cb --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs @@ -0,0 +1,8 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public interface IConsumerRegister +{ + void RegisterCommandHandler(Func, Task> handler); + void RegisterQueryHandler(Func, Task> handler); + void RegisterEventHandler(Func, Task> handler); +} \ No newline at end of file diff --git a/src/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs b/src/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs new file mode 100644 index 0000000..94982d5 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs @@ -0,0 +1,15 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +// TODO implementar IDisposable +public interface IHandlerHandle +{ + void Disconnect(); +} + +public interface IDynamicHandler : IHandlerHandle +{ + Type MessageType { get; } + + // Handler is typed to generic publishing + Task Handle(object mesage); +} \ No newline at end of file diff --git a/src/UltraSpeedBus.Abstractions/Contracts/IPublish.cs b/src/UltraSpeedBus.Abstractions/Contracts/IPublish.cs new file mode 100644 index 0000000..fe1710d --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Contracts/IPublish.cs @@ -0,0 +1,6 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public interface IPublish +{ + Task PublishAsync(TEvent @event); +} \ No newline at end of file diff --git a/src/UltraSpeedBus.Abstractions/Contracts/ISend.cs b/src/UltraSpeedBus.Abstractions/Contracts/ISend.cs new file mode 100644 index 0000000..be59bd2 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Contracts/ISend.cs @@ -0,0 +1,6 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public interface ISend +{ + Task SendAsync(TRequest request); +} \ No newline at end of file diff --git a/src/UltraSpeedBus.Abstractions/ICommandHandler.cs b/src/UltraSpeedBus.Abstractions/ICommandHandler.cs new file mode 100644 index 0000000..d734479 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/ICommandHandler.cs @@ -0,0 +1,18 @@ +using UltraSpeedBus.Abstractions.Contracts; + +namespace UltraSpeedBus.Abstractions; + +public interface ICommandHandler +{ + Task Handle(CommandContext request); +} + +public interface IQueryHandler +{ + Task Handle(QueryContext request); +} + +public interface IEventHandler +{ + Task Handle(EventContext request); +} \ No newline at end of file diff --git a/src/UltraSpeedBus.Abstractions/Mediator/IMediator.cs b/src/UltraSpeedBus.Abstractions/Mediator/IMediator.cs new file mode 100644 index 0000000..4641035 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Mediator/IMediator.cs @@ -0,0 +1,12 @@ +using UltraSpeedBus.Abstractions.Contracts; + +namespace UltraSpeedBus.Abstractions.Mediator; + +public interface IMediator : + ISend, + IPublish, + IConsumerConnector, + IConsumerRegister +{ + +} \ No newline at end of file diff --git a/src/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs b/src/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs new file mode 100644 index 0000000..4735e7f --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs @@ -0,0 +1,155 @@ +using UltraSpeedBus.Abstractions.Contracts; +using UltraSpeedBus.Abstractions.Mediator; + +namespace UltraSppedBus.Abstractions.Mediator; + +public class UltraMediator : IMediator +{ + private readonly Dictionary>> _commandHandlers = new(); + private readonly Dictionary>> _queryHandlers = new(); + private readonly Dictionary>> _eventHandlers = new(); + private readonly Dictionary> _dynamicHandlers = new(); + + #region Implement ISend + // Publisher 1 command x 1 Consumer + // Publisher 1 query x 1 Consumer + public Task SendAsync(TRequest request) + { + var type = typeof(TRequest); + + if (_commandHandlers.TryGetValue(type, out var handler)) + { + return InvokeHandler(handler, request); + } + + if (_queryHandlers.TryGetValue(type, out var queryHandler)) + { + return InvokeHandler(queryHandler, request); + } + + throw new InvalidOperationException($"No handler registered for {type.Name}"); + } + #endregion + + #region Implement IPublish + // Publisher 1 x Many Consumers + public Task PublishAsync(TEvent @event) + { + var type = typeof(TEvent); + var tasks = new List(); + + if (_eventHandlers.TryGetValue(type, out var eventHandlers)) + { + foreach (var handler in eventHandlers) + tasks.Add(handler(@event)); + } + + // You can disable this one + if (_dynamicHandlers.TryGetValue(type, out var dynamicEventHandlers)) + { + foreach (var handler in dynamicEventHandlers.OfType>()) + tasks.Add(handler.Handle(@event)); + } + + return Task.WhenAll(tasks); + } + #endregion + + #region Implement IConsumerConnector + public IHandlerHandle ConnectHandlerAsync(Func, Task> handler) + { + var dynamicHandler = new DynamicHandler(this, handler); + + lock (_dynamicHandlers) + { + if (!_dynamicHandlers.TryGetValue(typeof(TMessage), out var list)) + { + list = new List(); + _dynamicHandlers.Add(typeof(TMessage), list); + } + list.Add(dynamicHandler); + } + return dynamicHandler; + } + #endregion + + #region Implement IConsumerRegister + public void RegisterCommandHandler(Func, Task> handler) + { + _commandHandlers[typeof(TCommand)] = async (object cmd) => + { + var typed = (TCommand)cmd; + var ctx = new CommandContext(typed); + var resp = await handler(ctx); + return resp!; + }; + } + + public void RegisterQueryHandler(Func, Task> handler) + { + _queryHandlers[typeof(TQuery)] = async q => + { + var typed = (TQuery)q; + var ctx = new QueryContext(typed); + var resp = await handler(ctx); + return resp!; + }; + } + + public void RegisterEventHandler(Func, Task> handler) + { + if (!_eventHandlers.TryGetValue(typeof(TEvent), out var list)) + _eventHandlers[typeof(TEvent)] = list = new List>(); + } + #endregion + + private async Task InvokeHandler( + Func> handler, + TRequest request + ) + { + var response = await handler(request); + return (TResponse)response; + } + + internal void RemoveDynamicHandler(IDynamicHandler handler) + { + lock (_dynamicHandlers) + { + if (_dynamicHandlers.TryGetValue(handler.MessageType, out var list)) + { + list.Remove(handler); + + if (list.Count == 0) + _dynamicHandlers.Remove(handler.MessageType); + } + } + } + + private class DynamicHandler : IDynamicHandler + { + private readonly UltraMediator _mediator; + private readonly Func, Task> _handler; + + public DynamicHandler( + UltraMediator mediator, + Func, Task> handler) + { + _mediator = mediator; + _handler = handler; + } + + public Type MessageType => typeof(T); + // Pensa em uma interface para este método futuramente. Melhore o design + public Task Handle(object msg) + { + var ctx = new ConsumeContext((T)msg); + return _handler(ctx); + } + + public void Disconnect() + { + _mediator.RemoveDynamicHandler(this); + } + } +} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj b/src/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj similarity index 100% rename from UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj rename to src/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj diff --git a/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj b/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj new file mode 100644 index 0000000..6c20ca5 --- /dev/null +++ b/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + + UltraSpeedBus.Extensions.DependencyInjection + Dependency Injection for UltraSpeedBus + di;dependencyinjection;messaging;ultraspeedbus;distributedsystems + + + + + + + + + + + diff --git a/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBusExtensions.cs b/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBusExtensions.cs new file mode 100644 index 0000000..956bc16 --- /dev/null +++ b/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBusExtensions.cs @@ -0,0 +1,21 @@ +namespace UltraSpeedBus.Extensions.DepedencyInjection; + +using Microsoft.Extensions.DependencyInjection; +using UltraSpeedBus.Abstractions.Contracts; +using UltraSpeedBus.Abstractions.Mediator; +using UltraSppedBus.Abstractions.Mediator; + +public static class UltraSpeedBusExtensions +{ + public static IServiceCollection AddUltraSpeedBus( this IServiceCollection services) + { + services.AddSingleton(); + + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); + + return services; + } +} diff --git a/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj b/src/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj similarity index 100% rename from UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj rename to src/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj diff --git a/UltraSpeedBus/UltraSpeedBus.csproj b/src/UltraSpeedBus/UltraSpeedBus.csproj similarity index 73% rename from UltraSpeedBus/UltraSpeedBus.csproj rename to src/UltraSpeedBus/UltraSpeedBus.csproj index 1620378..0c6fed6 100644 --- a/UltraSpeedBus/UltraSpeedBus.csproj +++ b/src/UltraSpeedBus/UltraSpeedBus.csproj @@ -18,4 +18,9 @@ + + + + +