I’m getting familiar with RX and as my experimental project I’m trying to create a simple command bus conceptually similar to this:
class Bus
{
Subject<Command> commands;
IObservable<Invocation> invocations;
public Bus()
{
this.commands = new Subject<Command>();
this.invocations = commands.Select(x => new Invocation { Command = x }).Publish();
}
public IObserver<Command> Commands
{
get { return this.commands; }
}
public IObservable<Invocation> Invocations
{
get { return this.invocations; }
}
}
class Invocation
{
public Command Command { get; set; }
public bool Handled { get; set; }
}
The idea is that modules can install command handlers at startup using the Invocations property and can apply any filtering they wish to their subscription. Clients on the other hand can trigger command execution by calling Commands.OnNext(command).
However, I would like the Bus to provide a guarantee that each command submitted will be handled by exactly one handler. That is, OnNext processing should ideally terminate as soon as the first handler sets Invocation.Handled to true AND should throw an exception if, at the conclusion of OnNext(), Invocation.Handled is still false.
I played around creating my own ISubject, IObservable and IObserver implementations but this feels “dirty and cheap” 😉
I’m endeavouring to get my head around the compositional power that RX provides. In a compositional way, how can I provide the “exactly once” guarantee?
Thanks for any insights you can provide.
You’ve generally got the right idea here, actually. You just need to do the actual dispatch. For this, SelectMany will help:
But, to be honest, this doesn’t really demonstrate the power of Rx, because it’s executing the handler list synchronously. Let’s make this more compelling by making this completely non-blocking.
First, we’ll change our Func prototype to be
Func<Command, IObservable<Invocation>>. This means, a method that takes a command and produces a Future Invocation result (a-laTask<T>). Then, we can get identical behavior yet have our handlers be async via this selector (coding via TextArea ahead):This is a pretty graduate-level use of Rx, but the idea is, for each Command, we’re going to initially create a Stream of handlers and run them in order (that’s what Defer + Concat does), until we find one whose Handled is true, then take the last one.
The outer SelectMany selects a stream of Commands into a Stream of Future Results (i.e. the type is
IO<IO<Invocation>>then flattens it, so it becomes a stream of results.No blocking ever, very concise, 100% testable, type-safe code that just expressed a pretty complicated idea that would’ve been really ugly to write imperatively. This is why Rx is cool.