Fluid Lab FluidLab
← Tous les projets
Framework

FluidBus

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

Patchnotes

  1. 2026-01-15 v2.0.1 refactor

    FluidBus v2.0.1

    Refacto de FluidBus v1 qui devient maintenant un vrai FrameWork
    Option de dispatch procédural ou asynchrone
    Instruction dispatcher par events
Envie de creuser ?

Un cas d'usage particulier pour ce projet ?

Intégration, fork, fonctionnalité custom — on peut en parler.