From 1442830ce863582ae30fb3a88bb7cf33de955bc2 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 19 Nov 2025 22:44:21 -0300 Subject: [PATCH 01/18] feat: initialize project structure with Mediator pattern and basic command/event/query handling --- .vscode/launch.json | 35 +++++ .vscode/settings.json | 1 + .vscode/tasks.json | 41 ++++++ .../Message/ICommand.cs | 2 +- UltraSpeedBus.Abstractions/Message/IEvent.cs | 2 +- UltraSpeedBus.Abstractions/Message/IQuery.cs | 2 +- .../Message/IUltraSpeedBusMediator.cs | 13 ++ .../Middleware/IMessageMiddleware.cs | 0 .../Middleware/LoggingMiddleware.cs | 0 UltraSpeedBus.slnx | 3 + .../Extensions/ServiceCollectionExtensions.cs | 38 +++++ UltraSpeedBus/Message/DefaultPipeline.cs | 28 ++++ UltraSpeedBus/Message/HandlerRegistry.cs | 50 +++++++ UltraSpeedBus/Message/IMiddleware.cs | 10 ++ .../Message/MessageInvocationContext.cs | 17 +++ .../Message/UltraSpeedBusMediator.cs | 139 ++++++++++++++++++ UltraSpeedBus/UltraSpeedBus.csproj | 5 + .../Handlers/CreateOrderCommandHandler.cs | 20 +++ sample/UltraSpeedBus.WebAPI/IFoo.cs | 41 ++++++ sample/UltraSpeedBus.WebAPI/Program.cs | 40 +++++ .../Properties/launchSettings.json | 41 ++++++ .../UltraSpeedBus.WebAPI.csproj | 19 +++ .../UltraSpeedBus.WebAPI.http | 6 + .../appsettings.Development.json | 8 + sample/UltraSpeedBus.WebAPI/appsettings.json | 9 ++ 25 files changed, 567 insertions(+), 3 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 UltraSpeedBus.Abstractions/Message/IUltraSpeedBusMediator.cs create mode 100644 UltraSpeedBus.Abstractions/Middleware/IMessageMiddleware.cs create mode 100644 UltraSpeedBus.Abstractions/Middleware/LoggingMiddleware.cs create mode 100644 UltraSpeedBus/Extensions/ServiceCollectionExtensions.cs create mode 100644 UltraSpeedBus/Message/DefaultPipeline.cs create mode 100644 UltraSpeedBus/Message/HandlerRegistry.cs create mode 100644 UltraSpeedBus/Message/IMiddleware.cs create mode 100644 UltraSpeedBus/Message/MessageInvocationContext.cs create mode 100644 UltraSpeedBus/Message/UltraSpeedBusMediator.cs create mode 100644 sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs create mode 100644 sample/UltraSpeedBus.WebAPI/IFoo.cs create mode 100644 sample/UltraSpeedBus.WebAPI/Program.cs create mode 100644 sample/UltraSpeedBus.WebAPI/Properties/launchSettings.json create mode 100644 sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj create mode 100644 sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http create mode 100644 sample/UltraSpeedBus.WebAPI/appsettings.Development.json create mode 100644 sample/UltraSpeedBus.WebAPI/appsettings.json 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..c7e0f32 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "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": "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/UltraSpeedBus.Abstractions/Message/ICommand.cs b/UltraSpeedBus.Abstractions/Message/ICommand.cs index 2587b7c..20bf0b6 100644 --- a/UltraSpeedBus.Abstractions/Message/ICommand.cs +++ b/UltraSpeedBus.Abstractions/Message/ICommand.cs @@ -3,4 +3,4 @@ /// /// Command message marker interface /// -public interface ICommand : IMessage {} +public interface ICommand{}// : IMessage {} diff --git a/UltraSpeedBus.Abstractions/Message/IEvent.cs b/UltraSpeedBus.Abstractions/Message/IEvent.cs index 9f9ce4f..c31e9f8 100644 --- a/UltraSpeedBus.Abstractions/Message/IEvent.cs +++ b/UltraSpeedBus.Abstractions/Message/IEvent.cs @@ -3,4 +3,4 @@ /// /// Event message marker interface /// -public interface IEvent : IMessage {} +public interface IEvent {}// : IMessage {} diff --git a/UltraSpeedBus.Abstractions/Message/IQuery.cs b/UltraSpeedBus.Abstractions/Message/IQuery.cs index 1a2a8b0..6d13267 100644 --- a/UltraSpeedBus.Abstractions/Message/IQuery.cs +++ b/UltraSpeedBus.Abstractions/Message/IQuery.cs @@ -1,3 +1,3 @@ namespace UltraSpeedBus.Abstractions.Message; -public interface IQuery : IMessage {} \ No newline at end of file +public interface IQuery {}// : IMessage {} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Message/IUltraSpeedBusMediator.cs b/UltraSpeedBus.Abstractions/Message/IUltraSpeedBusMediator.cs new file mode 100644 index 0000000..befc95b --- /dev/null +++ b/UltraSpeedBus.Abstractions/Message/IUltraSpeedBusMediator.cs @@ -0,0 +1,13 @@ +namespace UltraSpeedBus.Abstractions.Message; + +public interface IUltraSpeedBusMediator +{ + Task SendAsync(TCommand command, CancellationToken cancellationToken = default) + where TCommand : ICommand; + + Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) + where TEvent : IEvent; + + Task QueryAsync(TQuery query, CancellationToken cancellationToken = default) + where TQuery : IQuery; +} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Middleware/IMessageMiddleware.cs b/UltraSpeedBus.Abstractions/Middleware/IMessageMiddleware.cs new file mode 100644 index 0000000..e69de29 diff --git a/UltraSpeedBus.Abstractions/Middleware/LoggingMiddleware.cs b/UltraSpeedBus.Abstractions/Middleware/LoggingMiddleware.cs new file mode 100644 index 0000000..e69de29 diff --git a/UltraSpeedBus.slnx b/UltraSpeedBus.slnx index a6bb6dd..8c61d76 100644 --- a/UltraSpeedBus.slnx +++ b/UltraSpeedBus.slnx @@ -1,4 +1,7 @@ + + + diff --git a/UltraSpeedBus/Extensions/ServiceCollectionExtensions.cs b/UltraSpeedBus/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..0707d5e --- /dev/null +++ b/UltraSpeedBus/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; +using UltraSpeedBus.Message; + +namespace UltraSpeedBus.Extensions; + +public static class ServiceCollectionExtensions +{ + /// + /// Registers the Mediator and supporting types. Consumers must register their handlers separately, + /// e.g. services.AddTransient, MyCommandHandler>(); + /// + public static IServiceCollection AddUltraSpeedBusMediator(this IServiceCollection services) + { + // Register pipeline middlewares (none by default), but the DefaultPipeline expects IEnumerable + services.AddTransient(); + services.AddTransient(sp => + { + var middlewares = sp.GetServices(); + return new DefaultPipeline(middlewares); + }); + + services.AddSingleton(); + // Expose mediator via interface or concrete type + services.AddSingleton(sp => sp.GetRequiredService()); + + return services; + } + + /// + /// Register a middleware implementation. + /// + public static IServiceCollection AddUltraSpeedBusMiddleware(this IServiceCollection services) + where TMiddleware : class, IMiddleware + { + services.AddTransient(); + return services; + } +} \ No newline at end of file diff --git a/UltraSpeedBus/Message/DefaultPipeline.cs b/UltraSpeedBus/Message/DefaultPipeline.cs new file mode 100644 index 0000000..f13ca2e --- /dev/null +++ b/UltraSpeedBus/Message/DefaultPipeline.cs @@ -0,0 +1,28 @@ +namespace UltraSpeedBus.Message; + +/// +/// Executes middleware in order. Accepts a list of IMiddleware resolved from DI. +/// +public class DefaultPipeline +{ + private readonly IReadOnlyList _middlewares; + public DefaultPipeline(IEnumerable middlewares) + { + _middlewares = middlewares is IReadOnlyList list ? list : new List(middlewares); + } + + public Task ExecuteAsync(MessageInvocationContext context, Func finalHandler) + { + // Build the pipeline delegate chain + Func next = finalHandler; + + for (int i = _middlewares.Count - 1; i >= 0; i--) + { + var current = _middlewares[i]; + var localNext = next; + next = () => current.InvokeAsync(context, localNext); + } + + return next(); + } +} diff --git a/UltraSpeedBus/Message/HandlerRegistry.cs b/UltraSpeedBus/Message/HandlerRegistry.cs new file mode 100644 index 0000000..ee9e802 --- /dev/null +++ b/UltraSpeedBus/Message/HandlerRegistry.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.DependencyInjection; +using UltraSpeedBus.Abstractions.Message; + +namespace UltraSpeedBus.Message; + +/// +/// Locates handlers from the configured DI container. +/// +public class HandlerRegistry +{ + private readonly IServiceProvider _serviceProvider; + + public HandlerRegistry(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + /// Gets the single command handler for a command type. + /// + /// + /// + public object? GetCommandHandler(Type commandType) + { + var handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType); + return _serviceProvider.GetService(handlerType); + } + + /// + /// Gets the single query handler for the query type (queryType includes TResult generic). + /// + public object? GetQueryHandler(Type queryHandlerInterfaceType) + { + // queryHandlerInterfaceType is expected to be IQueryHandler closed generic + return _serviceProvider.GetService(queryHandlerInterfaceType); + } + + /// + /// Gets all event handlers for an event type. + /// + public IEnumerable GetEventHandlers(Type eventType) + { + var handlerType = typeof(IEventHandler<>).MakeGenericType(eventType); + var services = _serviceProvider.GetServices(handlerType); + + if (services is not null) return services!; + + return Enumerable.Empty(); + } +} diff --git a/UltraSpeedBus/Message/IMiddleware.cs b/UltraSpeedBus/Message/IMiddleware.cs new file mode 100644 index 0000000..ef76c2f --- /dev/null +++ b/UltraSpeedBus/Message/IMiddleware.cs @@ -0,0 +1,10 @@ +namespace UltraSpeedBus.Message; + +// +/// Non-generic middleware for the message pipeline. The middleware receives a context and a `next`. +/// Keep it simple: middleware can inspect context.Message and act accordingly. +/// +public interface IMiddleware +{ + Task InvokeAsync(MessageInvocationContext context, Func next); +} diff --git a/UltraSpeedBus/Message/MessageInvocationContext.cs b/UltraSpeedBus/Message/MessageInvocationContext.cs new file mode 100644 index 0000000..80a5161 --- /dev/null +++ b/UltraSpeedBus/Message/MessageInvocationContext.cs @@ -0,0 +1,17 @@ +namespace UltraSpeedBus.Message; + +public sealed class MessageInvocationContext +{ + public object Message { get; } + public Type MessageType { get; } + public CancellationToken CancellationToken { get; } + public IServiceProvider Services { get; } + + public MessageInvocationContext(object message, Type messageType, IServiceProvider services, CancellationToken cancellationToken) + { + Message = message ?? throw new ArgumentNullException(nameof(message)); + MessageType = messageType ?? throw new ArgumentNullException(nameof(messageType)); + Services = services ?? throw new ArgumentNullException(nameof(services)); + CancellationToken = cancellationToken; + } +} diff --git a/UltraSpeedBus/Message/UltraSpeedBusMediator.cs b/UltraSpeedBus/Message/UltraSpeedBusMediator.cs new file mode 100644 index 0000000..3185d67 --- /dev/null +++ b/UltraSpeedBus/Message/UltraSpeedBusMediator.cs @@ -0,0 +1,139 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using UltraSpeedBus.Abstractions.Message; + +namespace UltraSpeedBus.Message; + +/// +/// Mediator-style dispatcher that routes Commands, Events and Queries through DI-resolved handlers +/// and a middleware pipeline. +/// +public class UltraSpeedBusMediator : IUltraSpeedBusMediator +{ + private readonly HandlerRegistry _registry; + private readonly IServiceProvider _services; + private readonly DefaultPipeline _pipeline; + + public UltraSpeedBusMediator(HandlerRegistry registry, IServiceProvider services, DefaultPipeline pipeline) + { + _registry = registry ?? throw new ArgumentNullException(nameof(registry)); + _services = services ?? throw new ArgumentNullException(nameof(services)); + _pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline)); + } + + #region Send + public async Task SendAsync(TCommand command, CancellationToken cancellationToken = default) + where TCommand : ICommand + { + if (command == null) throw new ArgumentNullException(nameof(command)); + + var handlerObj = _registry.GetCommandHandler(typeof(TCommand)); + if (handlerObj == null) + throw new InvalidOperationException($"No handler registered for command type ICommandHandler<{typeof(TCommand).FullName}>"); + + var context = new MessageInvocationContext(command, typeof(TCommand), _services, cancellationToken); + + async Task FinalHandler() + { + var handlerType = typeof(ICommandHandler<>).MakeGenericType(typeof(TCommand)); + using var scope = _services.CreateScope(); + var handler = scope.ServiceProvider.GetRequiredService(handlerType); + + var method = handlerType.GetMethod("HandleAsync", BindingFlags.Instance | BindingFlags.Public); + if (method == null) throw new InvalidOperationException("Handler has no HandleAsync method."); + var task = (Task)method.Invoke(handler, new object[] { command, cancellationToken })!; + await task.ConfigureAwait(false); + } + + await _pipeline.ExecuteAsync(context, FinalHandler).ConfigureAwait(false); + } + #endregion + + #region Publish (Event) + + public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) + where TEvent : IEvent + { + if (@event == null) throw new ArgumentNullException(nameof(@event)); + + var handlers = _registry.GetEventHandlers(typeof(TEvent)).ToArray(); + + var context = new MessageInvocationContext(@event, typeof(TEvent), _services, cancellationToken); + + if (handlers.Length == 0) + { + // Still run pipeline with no-op final. + await _pipeline.ExecuteAsync(context, () => Task.CompletedTask).ConfigureAwait(false); + return; + } + + // For events, invoke each handler inside pipeline. We will execute handlers concurrently, + // but each handler invocation goes through pipeline (so middlewares run per handler). + var tasks = handlers.Select(handlerObj => InvokeEventHandler(handlerObj, @event, cancellationToken)); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + private async Task InvokeEventHandler(object handlerObj, object @event, CancellationToken cancellationToken) + { + var handlerType = handlerObj.GetType().GetInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEventHandler<>)); + if (handlerType == null) return; + + var messageType = handlerType.GetGenericArguments()[0]; + var context = new MessageInvocationContext(@event, messageType, _services, cancellationToken); + + async Task FinalHandler() + { + using var scope = _services.CreateScope(); + var handler = scope.ServiceProvider.GetRequiredService(handlerType); + var method = handlerType.GetMethod("HandleAsync", BindingFlags.Instance | BindingFlags.Public); + if (method == null) throw new InvalidOperationException("Event handler has no HandleAsync method."); + var task = (Task)method.Invoke(handler, new object[] { @event, cancellationToken })!; + await task.ConfigureAwait(false); + } + + await _pipeline.ExecuteAsync(context, FinalHandler).ConfigureAwait(false); + } + #endregion + + #region Query + public async Task QueryAsync(TQuery query, CancellationToken cancellationToken = default) + where TQuery : IQuery + { + if (query == null) throw new ArgumentNullException(nameof(query)); + + var queryHandlerInterface = typeof(IQueryHandler<,>).MakeGenericType(typeof(TQuery), typeof(TResult)); + var handlerObj = _registry.GetQueryHandler(queryHandlerInterface); + if (handlerObj == null) + throw new InvalidOperationException($"No IQueryHandler<{typeof(TQuery).Name},{typeof(TResult).Name}> registered."); + + var context = new MessageInvocationContext(query, typeof(TQuery), _services, cancellationToken); + + TResult result = default!; + + async Task FinalHandler() + { + using var scope = _services.CreateScope(); + var handler = scope.ServiceProvider.GetRequiredService(queryHandlerInterface); + var method = queryHandlerInterface.GetMethod("HandleAsync", BindingFlags.Instance | BindingFlags.Public); + if (method == null) throw new InvalidOperationException("Query handler has no HandleAsync method."); + var task = (Task)method.Invoke(handler, new object[] { query, cancellationToken })!; + await task.ConfigureAwait(false); + // If method is Task, we need to get Result property via reflection + var resultProperty = task.GetType().GetProperty("Result"); + if (resultProperty != null) + { + result = (TResult)resultProperty.GetValue(task)!; + } + else + { + // For Task with no Result (should not happen) + result = default!; + } + } + + await _pipeline.ExecuteAsync(context, FinalHandler).ConfigureAwait(false); + return result; + } + #endregion Query +} \ No newline at end of file diff --git a/UltraSpeedBus/UltraSpeedBus.csproj b/UltraSpeedBus/UltraSpeedBus.csproj index 1620378..0c6fed6 100644 --- a/UltraSpeedBus/UltraSpeedBus.csproj +++ b/UltraSpeedBus/UltraSpeedBus.csproj @@ -18,4 +18,9 @@ + + + + + diff --git a/sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs b/sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs new file mode 100644 index 0000000..44f6d37 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs @@ -0,0 +1,20 @@ +using UltraSpeedBus.Abstractions.Message; + +namespace UltraSpeedBus.WebAPI.Handlers; + +public sealed record OrderDto(Guid OrderId, string CustomerName, DateTime CreatedAt); +public sealed record CreateOrderCommand(Guid OrderId, string CustomerName) : ICommand; +public sealed record OrderCreatedEvent(Guid OrderId, DateTime CreatedAt) : IEvent; +public sealed record GetOrderQuery(Guid OrderId) : IQuery; + + +public class CreateOrderCommandHandler : ICommandHandler +{ + public async Task HandleAsync(CreateOrderCommand command, CancellationToken cancellationToken) + { + // Simulate some processing time + await Task.Delay(500, cancellationToken); + + Console.WriteLine($"Order created: {command.OrderId} for {command.CustomerName}"); + } +} diff --git a/sample/UltraSpeedBus.WebAPI/IFoo.cs b/sample/UltraSpeedBus.WebAPI/IFoo.cs new file mode 100644 index 0000000..6705bae --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/IFoo.cs @@ -0,0 +1,41 @@ +namespace UltraSpeedBus.WebAPI; + +public interface IFoo +{ + string Does(); +} + +public class Foo : IFoo +{ + public string Does() + { + return "Quente"; + } +} + +public interface IFeatureManager +{ + bool IsEnabled(string featureName); +} + +public class FeatureManager : IFeatureManager +{ + public FeatureManager(IExternalScopeProvider scopeProvider) + { + + } + public bool IsEnabled(string featureName) + { + return true; + } +} + + +public static class FooExtensions +{ + public static IServiceCollection AddFooService(this IServiceCollection service) + { + service.AddScoped(); + return service; + } +} \ 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..8fc61f9 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/Program.cs @@ -0,0 +1,40 @@ +using UltraSpeedBus.WebAPI; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddFooService(); +builder.Services.AddSingleton(); + + +var spo = builder.Services.BuildServiceProvider(); +var featureManager = spo.GetRequiredService(); + +if (featureManager.IsEnabled("teste")) +{ + builder.Services.AddFooService(); +} + + +var app = builder.Build(); + + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + + +app.MapGet("/foo", (IFoo foo) => +{ + return foo.Does(); +}) +.WithName("GetFoo") +.WithOpenApi(); + +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..df7aef4 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj @@ -0,0 +1,19 @@ + + + + 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..68331f3 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http @@ -0,0 +1,6 @@ +@UltraSpeedBus.WebAPI_HostAddress = http://localhost:5185 + +GET {{UltraSpeedBus.WebAPI_HostAddress}}/weatherforecast/ +Accept: application/json + +### 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": "*" +} From f06eb09f6f4e57059159cf1113d18c41d884b076 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Mon, 24 Nov 2025 19:47:45 -0300 Subject: [PATCH 02/18] feat: add build task and define IPublish and ISend interfaces; remove obsolete message interfaces and handlers --- .vscode/tasks.json | 12 ++ .../Contracts/IPublish.cs | 6 + UltraSpeedBus.Abstractions/Contracts/ISend.cs | 6 + .../Message/ICommand.cs | 6 - .../Message/ICommandHandler.cs | 7 - UltraSpeedBus.Abstractions/Message/IEvent.cs | 6 - .../Message/IEventHandler.cs | 7 - .../Message/IMessage.cs | 14 -- UltraSpeedBus.Abstractions/Message/IQuery.cs | 3 - .../Message/IQueryHandler.cs | 7 - .../Message/IUltraSpeedBusMediator.cs | 13 -- .../Middleware/IMessageMiddleware.cs | 0 .../Middleware/LoggingMiddleware.cs | 0 .../Transport/ITransport.cs | 7 - .../Transport/ITransportConsumer.cs | 23 --- .../Transport/ITransportProducer.cs | 20 --- .../Extensions/ServiceCollectionExtensions.cs | 38 ----- UltraSpeedBus/Message/DefaultPipeline.cs | 28 ---- UltraSpeedBus/Message/HandlerRegistry.cs | 50 ------- UltraSpeedBus/Message/IMiddleware.cs | 10 -- .../Message/MessageInvocationContext.cs | 17 --- .../Message/UltraSpeedBusMediator.cs | 139 ------------------ .../Handlers/CreateOrderCommandHandler.cs | 20 --- sample/UltraSpeedBus.WebAPI/Program.cs | 21 --- 24 files changed, 24 insertions(+), 436 deletions(-) create mode 100644 UltraSpeedBus.Abstractions/Contracts/IPublish.cs create mode 100644 UltraSpeedBus.Abstractions/Contracts/ISend.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/ICommand.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/ICommandHandler.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/IEvent.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/IEventHandler.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/IMessage.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/IQuery.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/IQueryHandler.cs delete mode 100644 UltraSpeedBus.Abstractions/Message/IUltraSpeedBusMediator.cs delete mode 100644 UltraSpeedBus.Abstractions/Middleware/IMessageMiddleware.cs delete mode 100644 UltraSpeedBus.Abstractions/Middleware/LoggingMiddleware.cs delete mode 100644 UltraSpeedBus.Abstractions/Transport/ITransport.cs delete mode 100644 UltraSpeedBus.Abstractions/Transport/ITransportConsumer.cs delete mode 100644 UltraSpeedBus.Abstractions/Transport/ITransportProducer.cs delete mode 100644 UltraSpeedBus/Extensions/ServiceCollectionExtensions.cs delete mode 100644 UltraSpeedBus/Message/DefaultPipeline.cs delete mode 100644 UltraSpeedBus/Message/HandlerRegistry.cs delete mode 100644 UltraSpeedBus/Message/IMiddleware.cs delete mode 100644 UltraSpeedBus/Message/MessageInvocationContext.cs delete mode 100644 UltraSpeedBus/Message/UltraSpeedBusMediator.cs delete mode 100644 sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c7e0f32..fbf5fc4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,6 +13,18 @@ ], "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", diff --git a/UltraSpeedBus.Abstractions/Contracts/IPublish.cs b/UltraSpeedBus.Abstractions/Contracts/IPublish.cs new file mode 100644 index 0000000..fe1710d --- /dev/null +++ b/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/UltraSpeedBus.Abstractions/Contracts/ISend.cs b/UltraSpeedBus.Abstractions/Contracts/ISend.cs new file mode 100644 index 0000000..be59bd2 --- /dev/null +++ b/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/UltraSpeedBus.Abstractions/Message/ICommand.cs b/UltraSpeedBus.Abstractions/Message/ICommand.cs deleted file mode 100644 index 20bf0b6..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 c31e9f8..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 6d13267..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/Message/IUltraSpeedBusMediator.cs b/UltraSpeedBus.Abstractions/Message/IUltraSpeedBusMediator.cs deleted file mode 100644 index befc95b..0000000 --- a/UltraSpeedBus.Abstractions/Message/IUltraSpeedBusMediator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Message; - -public interface IUltraSpeedBusMediator -{ - Task SendAsync(TCommand command, CancellationToken cancellationToken = default) - where TCommand : ICommand; - - Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) - where TEvent : IEvent; - - Task QueryAsync(TQuery query, CancellationToken cancellationToken = default) - where TQuery : IQuery; -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Middleware/IMessageMiddleware.cs b/UltraSpeedBus.Abstractions/Middleware/IMessageMiddleware.cs deleted file mode 100644 index e69de29..0000000 diff --git a/UltraSpeedBus.Abstractions/Middleware/LoggingMiddleware.cs b/UltraSpeedBus.Abstractions/Middleware/LoggingMiddleware.cs deleted file mode 100644 index e69de29..0000000 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/Extensions/ServiceCollectionExtensions.cs b/UltraSpeedBus/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index 0707d5e..0000000 --- a/UltraSpeedBus/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using UltraSpeedBus.Message; - -namespace UltraSpeedBus.Extensions; - -public static class ServiceCollectionExtensions -{ - /// - /// Registers the Mediator and supporting types. Consumers must register their handlers separately, - /// e.g. services.AddTransient, MyCommandHandler>(); - /// - public static IServiceCollection AddUltraSpeedBusMediator(this IServiceCollection services) - { - // Register pipeline middlewares (none by default), but the DefaultPipeline expects IEnumerable - services.AddTransient(); - services.AddTransient(sp => - { - var middlewares = sp.GetServices(); - return new DefaultPipeline(middlewares); - }); - - services.AddSingleton(); - // Expose mediator via interface or concrete type - services.AddSingleton(sp => sp.GetRequiredService()); - - return services; - } - - /// - /// Register a middleware implementation. - /// - public static IServiceCollection AddUltraSpeedBusMiddleware(this IServiceCollection services) - where TMiddleware : class, IMiddleware - { - services.AddTransient(); - return services; - } -} \ No newline at end of file diff --git a/UltraSpeedBus/Message/DefaultPipeline.cs b/UltraSpeedBus/Message/DefaultPipeline.cs deleted file mode 100644 index f13ca2e..0000000 --- a/UltraSpeedBus/Message/DefaultPipeline.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace UltraSpeedBus.Message; - -/// -/// Executes middleware in order. Accepts a list of IMiddleware resolved from DI. -/// -public class DefaultPipeline -{ - private readonly IReadOnlyList _middlewares; - public DefaultPipeline(IEnumerable middlewares) - { - _middlewares = middlewares is IReadOnlyList list ? list : new List(middlewares); - } - - public Task ExecuteAsync(MessageInvocationContext context, Func finalHandler) - { - // Build the pipeline delegate chain - Func next = finalHandler; - - for (int i = _middlewares.Count - 1; i >= 0; i--) - { - var current = _middlewares[i]; - var localNext = next; - next = () => current.InvokeAsync(context, localNext); - } - - return next(); - } -} diff --git a/UltraSpeedBus/Message/HandlerRegistry.cs b/UltraSpeedBus/Message/HandlerRegistry.cs deleted file mode 100644 index ee9e802..0000000 --- a/UltraSpeedBus/Message/HandlerRegistry.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using UltraSpeedBus.Abstractions.Message; - -namespace UltraSpeedBus.Message; - -/// -/// Locates handlers from the configured DI container. -/// -public class HandlerRegistry -{ - private readonly IServiceProvider _serviceProvider; - - public HandlerRegistry(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - /// - /// Gets the single command handler for a command type. - /// - /// - /// - public object? GetCommandHandler(Type commandType) - { - var handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType); - return _serviceProvider.GetService(handlerType); - } - - /// - /// Gets the single query handler for the query type (queryType includes TResult generic). - /// - public object? GetQueryHandler(Type queryHandlerInterfaceType) - { - // queryHandlerInterfaceType is expected to be IQueryHandler closed generic - return _serviceProvider.GetService(queryHandlerInterfaceType); - } - - /// - /// Gets all event handlers for an event type. - /// - public IEnumerable GetEventHandlers(Type eventType) - { - var handlerType = typeof(IEventHandler<>).MakeGenericType(eventType); - var services = _serviceProvider.GetServices(handlerType); - - if (services is not null) return services!; - - return Enumerable.Empty(); - } -} diff --git a/UltraSpeedBus/Message/IMiddleware.cs b/UltraSpeedBus/Message/IMiddleware.cs deleted file mode 100644 index ef76c2f..0000000 --- a/UltraSpeedBus/Message/IMiddleware.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace UltraSpeedBus.Message; - -// -/// Non-generic middleware for the message pipeline. The middleware receives a context and a `next`. -/// Keep it simple: middleware can inspect context.Message and act accordingly. -/// -public interface IMiddleware -{ - Task InvokeAsync(MessageInvocationContext context, Func next); -} diff --git a/UltraSpeedBus/Message/MessageInvocationContext.cs b/UltraSpeedBus/Message/MessageInvocationContext.cs deleted file mode 100644 index 80a5161..0000000 --- a/UltraSpeedBus/Message/MessageInvocationContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace UltraSpeedBus.Message; - -public sealed class MessageInvocationContext -{ - public object Message { get; } - public Type MessageType { get; } - public CancellationToken CancellationToken { get; } - public IServiceProvider Services { get; } - - public MessageInvocationContext(object message, Type messageType, IServiceProvider services, CancellationToken cancellationToken) - { - Message = message ?? throw new ArgumentNullException(nameof(message)); - MessageType = messageType ?? throw new ArgumentNullException(nameof(messageType)); - Services = services ?? throw new ArgumentNullException(nameof(services)); - CancellationToken = cancellationToken; - } -} diff --git a/UltraSpeedBus/Message/UltraSpeedBusMediator.cs b/UltraSpeedBus/Message/UltraSpeedBusMediator.cs deleted file mode 100644 index 3185d67..0000000 --- a/UltraSpeedBus/Message/UltraSpeedBusMediator.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.Reflection; -using Microsoft.Extensions.DependencyInjection; -using UltraSpeedBus.Abstractions.Message; - -namespace UltraSpeedBus.Message; - -/// -/// Mediator-style dispatcher that routes Commands, Events and Queries through DI-resolved handlers -/// and a middleware pipeline. -/// -public class UltraSpeedBusMediator : IUltraSpeedBusMediator -{ - private readonly HandlerRegistry _registry; - private readonly IServiceProvider _services; - private readonly DefaultPipeline _pipeline; - - public UltraSpeedBusMediator(HandlerRegistry registry, IServiceProvider services, DefaultPipeline pipeline) - { - _registry = registry ?? throw new ArgumentNullException(nameof(registry)); - _services = services ?? throw new ArgumentNullException(nameof(services)); - _pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline)); - } - - #region Send - public async Task SendAsync(TCommand command, CancellationToken cancellationToken = default) - where TCommand : ICommand - { - if (command == null) throw new ArgumentNullException(nameof(command)); - - var handlerObj = _registry.GetCommandHandler(typeof(TCommand)); - if (handlerObj == null) - throw new InvalidOperationException($"No handler registered for command type ICommandHandler<{typeof(TCommand).FullName}>"); - - var context = new MessageInvocationContext(command, typeof(TCommand), _services, cancellationToken); - - async Task FinalHandler() - { - var handlerType = typeof(ICommandHandler<>).MakeGenericType(typeof(TCommand)); - using var scope = _services.CreateScope(); - var handler = scope.ServiceProvider.GetRequiredService(handlerType); - - var method = handlerType.GetMethod("HandleAsync", BindingFlags.Instance | BindingFlags.Public); - if (method == null) throw new InvalidOperationException("Handler has no HandleAsync method."); - var task = (Task)method.Invoke(handler, new object[] { command, cancellationToken })!; - await task.ConfigureAwait(false); - } - - await _pipeline.ExecuteAsync(context, FinalHandler).ConfigureAwait(false); - } - #endregion - - #region Publish (Event) - - public async Task PublishAsync(TEvent @event, CancellationToken cancellationToken = default) - where TEvent : IEvent - { - if (@event == null) throw new ArgumentNullException(nameof(@event)); - - var handlers = _registry.GetEventHandlers(typeof(TEvent)).ToArray(); - - var context = new MessageInvocationContext(@event, typeof(TEvent), _services, cancellationToken); - - if (handlers.Length == 0) - { - // Still run pipeline with no-op final. - await _pipeline.ExecuteAsync(context, () => Task.CompletedTask).ConfigureAwait(false); - return; - } - - // For events, invoke each handler inside pipeline. We will execute handlers concurrently, - // but each handler invocation goes through pipeline (so middlewares run per handler). - var tasks = handlers.Select(handlerObj => InvokeEventHandler(handlerObj, @event, cancellationToken)); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - private async Task InvokeEventHandler(object handlerObj, object @event, CancellationToken cancellationToken) - { - var handlerType = handlerObj.GetType().GetInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEventHandler<>)); - if (handlerType == null) return; - - var messageType = handlerType.GetGenericArguments()[0]; - var context = new MessageInvocationContext(@event, messageType, _services, cancellationToken); - - async Task FinalHandler() - { - using var scope = _services.CreateScope(); - var handler = scope.ServiceProvider.GetRequiredService(handlerType); - var method = handlerType.GetMethod("HandleAsync", BindingFlags.Instance | BindingFlags.Public); - if (method == null) throw new InvalidOperationException("Event handler has no HandleAsync method."); - var task = (Task)method.Invoke(handler, new object[] { @event, cancellationToken })!; - await task.ConfigureAwait(false); - } - - await _pipeline.ExecuteAsync(context, FinalHandler).ConfigureAwait(false); - } - #endregion - - #region Query - public async Task QueryAsync(TQuery query, CancellationToken cancellationToken = default) - where TQuery : IQuery - { - if (query == null) throw new ArgumentNullException(nameof(query)); - - var queryHandlerInterface = typeof(IQueryHandler<,>).MakeGenericType(typeof(TQuery), typeof(TResult)); - var handlerObj = _registry.GetQueryHandler(queryHandlerInterface); - if (handlerObj == null) - throw new InvalidOperationException($"No IQueryHandler<{typeof(TQuery).Name},{typeof(TResult).Name}> registered."); - - var context = new MessageInvocationContext(query, typeof(TQuery), _services, cancellationToken); - - TResult result = default!; - - async Task FinalHandler() - { - using var scope = _services.CreateScope(); - var handler = scope.ServiceProvider.GetRequiredService(queryHandlerInterface); - var method = queryHandlerInterface.GetMethod("HandleAsync", BindingFlags.Instance | BindingFlags.Public); - if (method == null) throw new InvalidOperationException("Query handler has no HandleAsync method."); - var task = (Task)method.Invoke(handler, new object[] { query, cancellationToken })!; - await task.ConfigureAwait(false); - // If method is Task, we need to get Result property via reflection - var resultProperty = task.GetType().GetProperty("Result"); - if (resultProperty != null) - { - result = (TResult)resultProperty.GetValue(task)!; - } - else - { - // For Task with no Result (should not happen) - result = default!; - } - } - - await _pipeline.ExecuteAsync(context, FinalHandler).ConfigureAwait(false); - return result; - } - #endregion Query -} \ No newline at end of file diff --git a/sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs b/sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs deleted file mode 100644 index 44f6d37..0000000 --- a/sample/UltraSpeedBus.WebAPI/Handlers/CreateOrderCommandHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using UltraSpeedBus.Abstractions.Message; - -namespace UltraSpeedBus.WebAPI.Handlers; - -public sealed record OrderDto(Guid OrderId, string CustomerName, DateTime CreatedAt); -public sealed record CreateOrderCommand(Guid OrderId, string CustomerName) : ICommand; -public sealed record OrderCreatedEvent(Guid OrderId, DateTime CreatedAt) : IEvent; -public sealed record GetOrderQuery(Guid OrderId) : IQuery; - - -public class CreateOrderCommandHandler : ICommandHandler -{ - public async Task HandleAsync(CreateOrderCommand command, CancellationToken cancellationToken) - { - // Simulate some processing time - await Task.Delay(500, cancellationToken); - - Console.WriteLine($"Order created: {command.OrderId} for {command.CustomerName}"); - } -} diff --git a/sample/UltraSpeedBus.WebAPI/Program.cs b/sample/UltraSpeedBus.WebAPI/Program.cs index 8fc61f9..24ee761 100644 --- a/sample/UltraSpeedBus.WebAPI/Program.cs +++ b/sample/UltraSpeedBus.WebAPI/Program.cs @@ -5,22 +5,8 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddFooService(); -builder.Services.AddSingleton(); - - -var spo = builder.Services.BuildServiceProvider(); -var featureManager = spo.GetRequiredService(); - -if (featureManager.IsEnabled("teste")) -{ - builder.Services.AddFooService(); -} - - var app = builder.Build(); - if (app.Environment.IsDevelopment()) { app.UseSwagger(); @@ -30,11 +16,4 @@ app.UseHttpsRedirection(); -app.MapGet("/foo", (IFoo foo) => -{ - return foo.Does(); -}) -.WithName("GetFoo") -.WithOpenApi(); - app.Run(); From e84146cd61b436b8bb3d1291cb65e7280c9ff002 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:00:13 -0300 Subject: [PATCH 03/18] feat: implement ConsumeContext class for message handling --- UltraSpeedBus.Abstractions/Context/ConsumeContext.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Context/ConsumeContext.cs diff --git a/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs b/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs new file mode 100644 index 0000000..be4822b --- /dev/null +++ b/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; +} From 66c3907a16a8e61bc9f2acf8e60ab9aea8539a07 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:00:40 -0300 Subject: [PATCH 04/18] feat: add QueryContext class for handling query data --- UltraSpeedBus.Abstractions/Context/QueryContext.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Context/QueryContext.cs diff --git a/UltraSpeedBus.Abstractions/Context/QueryContext.cs b/UltraSpeedBus.Abstractions/Context/QueryContext.cs new file mode 100644 index 0000000..6228d40 --- /dev/null +++ b/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; +} From 5c824f5681e5949be0dc19badd4b265609b98087 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:01:04 -0300 Subject: [PATCH 05/18] feat: add CommandContext class for encapsulating command data --- UltraSpeedBus.Abstractions/Context/CommandContext.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Context/CommandContext.cs diff --git a/UltraSpeedBus.Abstractions/Context/CommandContext.cs b/UltraSpeedBus.Abstractions/Context/CommandContext.cs new file mode 100644 index 0000000..202e311 --- /dev/null +++ b/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; +} From 2759ae31a6da97d7001b77ded5e409ed9ca2e8fb Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:01:35 -0300 Subject: [PATCH 06/18] feat: define IHandlerHandle and IDynamicHandler interfaces for message handling --- .../Contracts/IHandlerHandle.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs diff --git a/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs b/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs new file mode 100644 index 0000000..94982d5 --- /dev/null +++ b/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 From 417dd55a2de1f154cd5905eef658d42dcc04da7f Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:02:10 -0300 Subject: [PATCH 07/18] feat: add EventContext class for encapsulating event data --- UltraSpeedBus.Abstractions/Context/EventContext.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Context/EventContext.cs diff --git a/UltraSpeedBus.Abstractions/Context/EventContext.cs b/UltraSpeedBus.Abstractions/Context/EventContext.cs new file mode 100644 index 0000000..19bf5e4 --- /dev/null +++ b/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; +} From 06ecf68d0246e123af057dd28f2c46d7394ca128 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:02:37 -0300 Subject: [PATCH 08/18] feat: add IConsumerConnector interface for connecting message handlers --- UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs diff --git a/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs b/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs new file mode 100644 index 0000000..2c62537 --- /dev/null +++ b/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs @@ -0,0 +1,6 @@ +namespace UltraSpeedBus.Abstractions.Contracts; + +public interface IConsumerConnector +{ + IHandlerHandle ConnectHandlerAsync(Func, Task> handler); +} From 2af1de5dfff1dfb43d275698d0d5a7ec8203b11a Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:03:00 -0300 Subject: [PATCH 09/18] feat: add IConsumerRegister interface for registering command, query, and event handlers --- UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs diff --git a/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs b/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs new file mode 100644 index 0000000..70a85cb --- /dev/null +++ b/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 From e0404af7c309344ac63cb112fcd3f64e83bc5d8a Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:03:21 -0300 Subject: [PATCH 10/18] feat: add IMediator interface for handling message sending and publishing --- UltraSpeedBus.Abstractions/Mediator/IMediator.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Mediator/IMediator.cs diff --git a/UltraSpeedBus.Abstractions/Mediator/IMediator.cs b/UltraSpeedBus.Abstractions/Mediator/IMediator.cs new file mode 100644 index 0000000..4641035 --- /dev/null +++ b/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 From c091e6ee66ddd316d792bd1de4c2c11faa579db2 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 11:03:37 -0300 Subject: [PATCH 11/18] feat: implement UltraMediator class for handling commands, queries, and events --- .../Mediator/UltraMediator.cs | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs diff --git a/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs b/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs new file mode 100644 index 0000000..5bc0bc2 --- /dev/null +++ b/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs @@ -0,0 +1,156 @@ +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 From a9b09432a589d896ab5e4a3b8482ca50f687a022 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 13:46:56 -0300 Subject: [PATCH 12/18] feat: remove obsolete context and mediator classes and interfaces --- .../Context/CommandContext.cs | 7 - .../Context/ConsumeContext.cs | 7 - .../Context/EventContext.cs | 7 - .../Context/QueryContext.cs | 7 - .../Contracts/IConsumerConnector.cs | 6 - .../Contracts/IConsumerRegister.cs | 8 - .../Contracts/IHandlerHandle.cs | 15 -- .../Contracts/IPublish.cs | 6 - UltraSpeedBus.Abstractions/Contracts/ISend.cs | 6 - .../Mediator/IMediator.cs | 12 -- .../Mediator/UltraMediator.cs | 156 ------------------ .../UltraSpeedBus.Abstractions.csproj | 13 -- .../UltraSpeedBus.Tests.csproj | 29 ---- UltraSpeedBus/UltraSpeedBus.csproj | 26 --- sample/UltraSpeedBus.WebAPI/IFoo.cs | 41 ----- 15 files changed, 346 deletions(-) delete mode 100644 UltraSpeedBus.Abstractions/Context/CommandContext.cs delete mode 100644 UltraSpeedBus.Abstractions/Context/ConsumeContext.cs delete mode 100644 UltraSpeedBus.Abstractions/Context/EventContext.cs delete mode 100644 UltraSpeedBus.Abstractions/Context/QueryContext.cs delete mode 100644 UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs delete mode 100644 UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs delete mode 100644 UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs delete mode 100644 UltraSpeedBus.Abstractions/Contracts/IPublish.cs delete mode 100644 UltraSpeedBus.Abstractions/Contracts/ISend.cs delete mode 100644 UltraSpeedBus.Abstractions/Mediator/IMediator.cs delete mode 100644 UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs delete mode 100644 UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj delete mode 100644 UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj delete mode 100644 UltraSpeedBus/UltraSpeedBus.csproj delete mode 100644 sample/UltraSpeedBus.WebAPI/IFoo.cs diff --git a/UltraSpeedBus.Abstractions/Context/CommandContext.cs b/UltraSpeedBus.Abstractions/Context/CommandContext.cs deleted file mode 100644 index 202e311..0000000 --- a/UltraSpeedBus.Abstractions/Context/CommandContext.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Contracts; - -public class CommandContext -{ - public TCommand Command { get; } - public CommandContext(TCommand command) => Command = command; -} diff --git a/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs b/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs deleted file mode 100644 index be4822b..0000000 --- a/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Contracts; - -public class ConsumeContext -{ - public T Message { get; } - public ConsumeContext(T message) => Message = message; -} diff --git a/UltraSpeedBus.Abstractions/Context/EventContext.cs b/UltraSpeedBus.Abstractions/Context/EventContext.cs deleted file mode 100644 index 19bf5e4..0000000 --- a/UltraSpeedBus.Abstractions/Context/EventContext.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Contracts; - -public class EventContext -{ - public TEvent Event { get; } - public EventContext(TEvent @event) => Event = @event; -} diff --git a/UltraSpeedBus.Abstractions/Context/QueryContext.cs b/UltraSpeedBus.Abstractions/Context/QueryContext.cs deleted file mode 100644 index 6228d40..0000000 --- a/UltraSpeedBus.Abstractions/Context/QueryContext.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Contracts; - -public class QueryContext -{ - public TQuery Query { get; } - public QueryContext(TQuery query) => Query = query; -} diff --git a/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs b/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs deleted file mode 100644 index 2c62537..0000000 --- a/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Contracts; - -public interface IConsumerConnector -{ - IHandlerHandle ConnectHandlerAsync(Func, Task> handler); -} diff --git a/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs b/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs deleted file mode 100644 index 70a85cb..0000000 --- a/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs +++ /dev/null @@ -1,8 +0,0 @@ -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/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs b/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs deleted file mode 100644 index 94982d5..0000000 --- a/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs +++ /dev/null @@ -1,15 +0,0 @@ -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/UltraSpeedBus.Abstractions/Contracts/IPublish.cs b/UltraSpeedBus.Abstractions/Contracts/IPublish.cs deleted file mode 100644 index fe1710d..0000000 --- a/UltraSpeedBus.Abstractions/Contracts/IPublish.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Contracts; - -public interface IPublish -{ - Task PublishAsync(TEvent @event); -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Contracts/ISend.cs b/UltraSpeedBus.Abstractions/Contracts/ISend.cs deleted file mode 100644 index be59bd2..0000000 --- a/UltraSpeedBus.Abstractions/Contracts/ISend.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace UltraSpeedBus.Abstractions.Contracts; - -public interface ISend -{ - Task SendAsync(TRequest request); -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Mediator/IMediator.cs b/UltraSpeedBus.Abstractions/Mediator/IMediator.cs deleted file mode 100644 index 4641035..0000000 --- a/UltraSpeedBus.Abstractions/Mediator/IMediator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using UltraSpeedBus.Abstractions.Contracts; - -namespace UltraSpeedBus.Abstractions.Mediator; - -public interface IMediator : - ISend, - IPublish, - IConsumerConnector, - IConsumerRegister -{ - -} \ No newline at end of file diff --git a/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs b/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs deleted file mode 100644 index 5bc0bc2..0000000 --- a/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs +++ /dev/null @@ -1,156 +0,0 @@ -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/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj deleted file mode 100644 index d03a664..0000000 --- a/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net8.0 - enable - enable - - UltraSpeedBus.Abstractions - Core abstractions and interfaces for UltraSpeedBus - messaging;ultraspeedbus;ultraspeedbus.abstractions;distributedsystems - - - diff --git a/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj b/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj deleted file mode 100644 index ff5bde5..0000000 --- a/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - - - - - - - - - - - - - diff --git a/UltraSpeedBus/UltraSpeedBus.csproj b/UltraSpeedBus/UltraSpeedBus.csproj deleted file mode 100644 index 0c6fed6..0000000 --- a/UltraSpeedBus/UltraSpeedBus.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net8.0 - enable - enable - - UltraSpeedBus - A high-performance messaging library for building distributed systems in .NET - messaging;ultraspeedbus;distributedsystems - - - - - - - - - - - - - - - - diff --git a/sample/UltraSpeedBus.WebAPI/IFoo.cs b/sample/UltraSpeedBus.WebAPI/IFoo.cs deleted file mode 100644 index 6705bae..0000000 --- a/sample/UltraSpeedBus.WebAPI/IFoo.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace UltraSpeedBus.WebAPI; - -public interface IFoo -{ - string Does(); -} - -public class Foo : IFoo -{ - public string Does() - { - return "Quente"; - } -} - -public interface IFeatureManager -{ - bool IsEnabled(string featureName); -} - -public class FeatureManager : IFeatureManager -{ - public FeatureManager(IExternalScopeProvider scopeProvider) - { - - } - public bool IsEnabled(string featureName) - { - return true; - } -} - - -public static class FooExtensions -{ - public static IServiceCollection AddFooService(this IServiceCollection service) - { - service.AddScoped(); - return service; - } -} \ No newline at end of file From 9fd85857f6797c41e18cdcc452704b97b1f7fb68 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 13:47:34 -0300 Subject: [PATCH 13/18] feat: add command, query, and event context classes and mediator interfaces --- .../Context/CommandContext.cs | 7 +++++++ .../Context/ConsumeContext.cs | 7 +++++++ .../Context/EventContext.cs | 7 +++++++ .../Context/QueryContext.cs | 7 +++++++ .../Contracts/IConsumerConnector.cs | 6 ++++++ .../Contracts/IConsumerRegister.cs | 8 ++++++++ .../Contracts/IHandlerHandle.cs | 15 +++++++++++++++ .../Contracts/IPublish.cs | 6 ++++++ .../Contracts/ISend.cs | 6 ++++++ .../ICommandHandler.cs | 18 ++++++++++++++++++ .../Mediator/IMediator.cs | 12 ++++++++++++ 11 files changed, 99 insertions(+) create mode 100644 src/UltraSpeedBus.Abstractions/Context/CommandContext.cs create mode 100644 src/UltraSpeedBus.Abstractions/Context/ConsumeContext.cs create mode 100644 src/UltraSpeedBus.Abstractions/Context/EventContext.cs create mode 100644 src/UltraSpeedBus.Abstractions/Context/QueryContext.cs create mode 100644 src/UltraSpeedBus.Abstractions/Contracts/IConsumerConnector.cs create mode 100644 src/UltraSpeedBus.Abstractions/Contracts/IConsumerRegister.cs create mode 100644 src/UltraSpeedBus.Abstractions/Contracts/IHandlerHandle.cs create mode 100644 src/UltraSpeedBus.Abstractions/Contracts/IPublish.cs create mode 100644 src/UltraSpeedBus.Abstractions/Contracts/ISend.cs create mode 100644 src/UltraSpeedBus.Abstractions/ICommandHandler.cs create mode 100644 src/UltraSpeedBus.Abstractions/Mediator/IMediator.cs 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 From e46db99b8861eadf3783652d15571c2d91796da0 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 13:47:51 -0300 Subject: [PATCH 14/18] feat: add initial implementation of UltraSpeedBus with command, query, and event handling --- UltraSpeedBus.slnx | 7 +- .../CreateOrderHandler.cs | 52 ++++++ sample/UltraSpeedBus.WebAPI/Program.cs | 51 ++++++ .../UltraSpeedBus.WebAPI.csproj | 5 +- .../UltraSpeedBus.WebAPI.http | 6 + .../Mediator/UltraMediator.cs | 155 ++++++++++++++++++ .../UltraSpeedBus.Abstractions.csproj | 13 ++ ...edBus.Extensions.DepedencyInjection.csproj | 17 ++ .../UltraSpeedBusExtensions.cs | 21 +++ .../UltraSpeedBus.Tests.csproj | 29 ++++ src/UltraSpeedBus/UltraSpeedBus.csproj | 26 +++ 11 files changed, 377 insertions(+), 5 deletions(-) create mode 100644 sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs create mode 100644 src/UltraSpeedBus.Abstractions/Mediator/UltraMediator.cs create mode 100644 src/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj create mode 100644 src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj create mode 100644 src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBusExtensions.cs create mode 100644 src/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj create mode 100644 src/UltraSpeedBus/UltraSpeedBus.csproj diff --git a/UltraSpeedBus.slnx b/UltraSpeedBus.slnx index 8c61d76..bb6ee9d 100644 --- a/UltraSpeedBus.slnx +++ b/UltraSpeedBus.slnx @@ -3,8 +3,9 @@ - - - + + + + diff --git a/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs b/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs new file mode 100644 index 0000000..053cd15 --- /dev/null +++ b/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs @@ -0,0 +1,52 @@ +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 index 24ee761..91cf792 100644 --- a/sample/UltraSpeedBus.WebAPI/Program.cs +++ b/sample/UltraSpeedBus.WebAPI/Program.cs @@ -1,9 +1,18 @@ +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(); @@ -15,5 +24,47 @@ 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/UltraSpeedBus.WebAPI.csproj b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj index df7aef4..284e404 100644 --- a/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj +++ b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.csproj @@ -12,8 +12,9 @@ - - + + + diff --git a/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http index 68331f3..6bc9dce 100644 --- a/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http +++ b/sample/UltraSpeedBus.WebAPI/UltraSpeedBus.WebAPI.http @@ -4,3 +4,9 @@ GET {{UltraSpeedBus.WebAPI_HostAddress}}/weatherforecast/ Accept: application/json ### +POST /orders +Body: +{ + "product": "Pizza", + "quantity": 2 +} \ 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/src/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj b/src/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj new file mode 100644 index 0000000..d03a664 --- /dev/null +++ b/src/UltraSpeedBus.Abstractions/UltraSpeedBus.Abstractions.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + UltraSpeedBus.Abstractions + Core abstractions and interfaces for UltraSpeedBus + messaging;ultraspeedbus;ultraspeedbus.abstractions;distributedsystems + + + 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..ee18642 --- /dev/null +++ b/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + 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/src/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj b/src/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj new file mode 100644 index 0000000..ff5bde5 --- /dev/null +++ b/src/UltraSpeedBus.Tests/UltraSpeedBus.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/src/UltraSpeedBus/UltraSpeedBus.csproj b/src/UltraSpeedBus/UltraSpeedBus.csproj new file mode 100644 index 0000000..0c6fed6 --- /dev/null +++ b/src/UltraSpeedBus/UltraSpeedBus.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + UltraSpeedBus + A high-performance messaging library for building distributed systems in .NET + messaging;ultraspeedbus;distributedsystems + + + + + + + + + + + + + + + + From c7f0d69501a57630f0340eceadc0a669fce67787 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 13:53:39 -0300 Subject: [PATCH 15/18] feat: add documentation for UltraSpeedBus.Extensions.DependencyInjection package --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7be00a9..ec252aa 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 From f4d84e2b2c58c6289d9114da118b12618aba87a9 Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 13:55:24 -0300 Subject: [PATCH 16/18] feat: update README with command handler example and package installation instructions --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ec252aa..2cca35e 100644 --- a/README.md +++ b/README.md @@ -24,18 +24,25 @@ 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 ``` ```csharp using UltraSpeedBus; using UltraSpeedBus.Abstractions; -// Create a message -var message = new MyCommand { Name = "Test" }; -var envelope = MessageFactory.Create(message); - -// Send using your transport implementation (e.g., Azure Service Bus) -await producer.SendAsync(envelope); +// 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)); + } +} ``` ## Contributing From b41b00e7e683d777e22b168ab02975a1e1cf1a0d Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 13:56:58 -0300 Subject: [PATCH 17/18] feat: add command and query handler examples to README --- README.md | 37 +++++++++++++++++++ .../CreateOrderHandler.cs | 1 - 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cca35e..5785a4d 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ dotnet add package UltraSpeedBus.Abstractions dotnet add package UltraSpeedBus.Extensions.DependencyInjection ``` +## Command handler + ```csharp using UltraSpeedBus; using UltraSpeedBus.Abstractions; @@ -45,6 +47,41 @@ public class CreateOrderHandler : ICommandHandler +{ + 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); + +public class OrderCreatedEventHandler : IEventHandler +{ + public Task Handle(EventContext context) + { + Console.WriteLine($"[Event] Order created → Id = {context.Event.OrderId}"); + return Task.CompletedTask; + } +} +``` + ## Contributing Contributions are welcome! Please open issues or submit pull requests to help improve UltraSpeedBus. diff --git a/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs b/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs index 053cd15..796dd84 100644 --- a/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs +++ b/sample/UltraSpeedBus.WebAPI/CreateOrderHandler.cs @@ -22,7 +22,6 @@ public Task Handle(CommandContext request) } #endregion - #region Query public sealed record GetOrder(int OrderId); public sealed record OrderDto(int OrderId, string Description); From 9aafbf9fbdef59523d716b99d63f3e5dea5f6d7d Mon Sep 17 00:00:00 2001 From: Yuri Melo Date: Wed, 26 Nov 2025 13:59:07 -0300 Subject: [PATCH 18/18] feat: add package metadata for UltraSpeedBus.Extensions.DependencyInjection --- .../UltraSpeedBus.Extensions.DepedencyInjection.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj b/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj index ee18642..6c20ca5 100644 --- a/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj +++ b/src/UltraSpeedBus.Extensions.DepedencyInjection/UltraSpeedBus.Extensions.DepedencyInjection.csproj @@ -4,6 +4,10 @@ net8.0 enable enable + + UltraSpeedBus.Extensions.DependencyInjection + Dependency Injection for UltraSpeedBus + di;dependencyinjection;messaging;ultraspeedbus;distributedsystems