Fluid Lab FluidLab
Lab

Open par défaut.

Ce qui sort du Lab est public. Frameworks, outils système, RAG, expérimentations — tout est sur GitHub.

Code lu, code testé, code partagé.

FluidBus

C# · active

# FluidBus A lightweight, modular event bus for .NET 10 built around two dispatch models: **Router** (protocol-based routing with sync/async support) and **React** (channel-based reactive subscriptions), plus a **CallBack** registry and a built-in **Benchmark** toolkit. Zero external dependencies. ## Architecture ``` FluidBus ├── FluidBus.Core # Interfaces, protocols, tasks, errors ├── FluidBus.Router # Protocol-based event routing (sync/async) ├── FluidBus.React # Reactive channel-based event dispatch ├── FluidBus.CallBack # Named callback registry ├── FluidBus.Benchmark # Benchmarking utilities └── FBus # Unified facade ``` ### Router vs React | | Router | React | |---|---|---| | Dispatch | Protocol-based (sync/async) | Channel-based (always async) | | Handler count | One handler per event type | Multiple subscribers per channel | | Registration | Manual via `FRouter.Register()` | Auto-subscribe on instantiation | | Matching | Exact event type | All subscribers on the channel | | Use case | Command / request patterns | Broadcast / observer patterns | ## Getting started Reference `FluidBus` in your project. The `FBus` facade exposes all modules: ```csharp using FluidBus; FBus.Route(routeEvent); // Router dispatch FBus.React(reactEvent); // React dispatch FBus.CallBack("on_complete", someData); // Execute a named callback FBus.Bench("my scenario", 1000, 100, () => { }); // Benchmark a scenario ``` --- ## FluidBus.Core Shared foundation used by all modules. ### Instructions Instructions carry the data and logic that handlers execute. They use the `FluidCallBack` delegate: ```csharp public delegate object? FluidCallBack(object? data); ``` Inherit from `FluidInstruction<T>`: ```csharp using FluidBus.Core.Abstracts; public class PrintInstruction : FluidInstruction<string> { public PrintInstruction(string? data, params FluidCallBack[] methods) : base(data, methods) { } } ``` Each instruction can hold multiple callbacks (deduplicated by ID), executed sequentially. An `OnResult` event fires after execution. ### Protocols Protocols define the execution strategy for the Router: ```csharp public enum ExecutionStrategy { Sync = 0, Async = 1 } ``` A built-in `BusProtocol.System` (sync) is always available. Create custom protocols: ```csharp using FluidBus.Core.Protocols; public class AsyncProtocol : BusProtocol { public override ExecutionStrategy Strategy => ExecutionStrategy.Async; public AsyncProtocol() : base("ASYNC") { } } ``` ### Tasks `FluidTask` wraps `Task.Run()` with state tracking (`Running`, `Completed`, `Failed`, `Cancelled`) and a fluent `OnComplete()` continuation API. ### Error hierarchy All exceptions inherit from `FluidBusError` and expose `.DisplayMessage()`. | Exception | Thrown when | |---|---| | `DispatchException` | Async dispatch fails or unknown `ExecutionStrategy` | | `ProtocolNotFoundException` | Event protocol has no registered port | | `HandlerNotFoundException` | No handler registered for a given event type | | `DuplicateHandlerException` | Handler already registered for that event type | | `InstructionException` | `Execute()` called with no callbacks or null data | | `ChannelException` | Channel write with no subscribers, or subscriber failure | | `HandlerLinqException` | Handler registry error | --- ## FluidBus.Router The Router dispatches events through **protocols** to registered **handlers**, matched by event type. One handler per event type. ### 1. Create a custom event ```csharp using FluidBus.Core.Interfaces; using FluidBus.Core.Protocols; using FluidBus.Router.Abstracts; public class UserCreatedEvent : RouteEvent { public UserCreatedEvent(string id, BusProtocol protocol, params IFluidInstruction[] instrs) : base($"{nameof(UserCreatedEvent)}::{id}", protocol, instrs) { } } ``` ### 2. Create a custom handler ```csharp using FluidBus.Core.Interfaces; using FluidBus.Router.Abstracts; public class UserCreatedHandler : RouteHandler<UserCreatedEvent> { public UserCreatedHandler(string id) : base($"{nameof(UserCreatedEvent)}::{id}") { } public override bool Handle(IFluidEvent evt) { Console.WriteLine($"[{Id}] Handling event {evt.Id}"); return base.Handle(evt); } } ``` ### 3. Register and publish ```csharp using FluidBus; using FluidBus.Core.Abstracts; using FluidBus.Core.Protocols; using FluidBus.Router.Core; // Register the handler FRouter.Register(new UserCreatedHandler("user_handler")); // Create an instruction with a callback var instruction = new PrintInstruction("Hello from FluidBus!", msg => { Console.WriteLine(msg); return null; }); // Publish on the System protocol (sync) FBus.Route(new UserCreatedEvent("evt_1", BusProtocol.System, instruction)); ``` ### Custom protocols ```csharp // Register a port for your protocol FRouter.AddPort(new AsyncProtocol()); // Events using this protocol will dispatch asynchronously FBus.Route(new UserCreatedEvent("evt_2", new AsyncProtocol(), instruction)); ``` ### Dispatch flow ``` FBus.Route(event) -> FRouter.Publish(event) -> Lookup port by event.Protocol -> Lookup handler by event type (HandlerLinq) -> RouterPort.Dispatch(event, handler) ├─ Sync: event.Dispatch(handler) — blocking └─ Async: FluidTask wrapping event.Dispatch(handler) -> handler.Handle(event) -> Execute each instruction's callbacks sequentially ``` --- ## FluidBus.React React uses **channels** (`System.Threading.Channels`) instead of protocols. Handlers auto-subscribe to their event type's channel on creation. Events are dispatched asynchronously to all subscribers. ### 1. Create a custom event ```csharp using FluidBus.Core.Interfaces; using FluidBus.React.Abstracts; public class ChatMessageEvent : ReactEvent { public ChatMessageEvent(string id, params IFluidInstruction[] instrs) : base($"{nameof(ChatMessageEvent)}::{id}", instrs) { } } ``` ### 2. Create a custom handler Handlers subscribe automatically to their channel on instantiation: ```csharp using FluidBus.Core.Interfaces; using FluidBus.React.Abstracts; public class ChatMessageHandler : ReactHandler<ChatMessageEvent> { public ChatMessageHandler(string id) : base($"{nameof(ChatMessageEvent)}::{id}") { } public override bool Handle(IFluidEvent evt) { Console.WriteLine($"[{Id}] Received message"); return base.Handle(evt); } } ``` ### 3. Register and publish ```csharp using FluidBus; using FluidBus.Core.Abstracts; using FluidBus.React.Core; // Instantiating the handler auto-subscribes it to the ChatMessageEvent channel var handler = new ChatMessageHandler("chat_listener"); // Optionally register it for lifecycle management FReact.RegisterHandler(handler); // Publish — all subscribed handlers are notified asynchronously var instruction = new PrintInstruction("New message!", msg => { Console.WriteLine(msg); return null; }); FBus.React(new ChatMessageEvent("msg_1", instruction)); // Drop a handler FReact.DropHandler(handler); ``` ### Dispatch flow ``` FBus.React(event) -> FReact.Publish(event) -> GetOrCreateChannel(event type) -> channel.Write(event) — enqueued in unbounded Channel<T> -> Background reader loop (per channel) -> Broadcast to all ReactReceive subscribers -> handler.Handle(event) -> Execute each instruction's callbacks ``` --- ## FluidBus.CallBack A simple named callback registry using the `FluidCallBack` delegate. ```csharp using FluidBus.Core.Abstracts; using FluidBus.CallBack.Core; // Register a callback FCallBack.RegisterCallBack("on_complete", data => { Console.WriteLine($"Completed with: {data}"); return data; }); // Execute by name (through the facade) FBus.CallBack("on_complete", "some result"); // Remove a callback FCallBack.DropCallBack("on_complete"); ``` Returns `null` silently if the callback doesn't exist. --- ## FluidBus.Benchmark Built-in benchmarking with warmup support and nanosecond precision (`Stopwatch.GetTimestamp()`). ```csharp using FluidBus; using FluidBus.Benchmark.Core; BenchResult result = FBus.Bench("route 1000 events", iterations: 1000, warmup: 100, () => { FBus.Route(myEvent); }); result.Print(); // Prints iterations, duration (ms), avg ns/iteration ``` `BenchResult` exposes: `Iteration`, `Warmup`, `Case`, `Start`, `End`, `Duration` (ms). --- ## License [Business Source License 1.1](LICENSE) - See LICENSE file for details.

C# .NET Core 10
GitHub ↗
Curieux ?

Un projet à creuser ensemble ?
Ou juste envie de jeter un œil au code ?

Tout est lisible, forkable, contestable. Issue, PR ou message — tous les retours sont bienvenus.