Out of the box, the app server uses signalr for 2 things:
- The bus – it is how all nodes communicate with each other
- The website – it is how the website communicates with the bus
Since the beginning, I have always said that “anything in the app server can be swapped out… theoretically”. I always added the “theoretically” disclaimer because I hadn’t actually tried it.
Finally, I tried it with RabbitMQ, and the theory was incorrect. I had to make some changes, and chose to make some others.
The primarily required change: the two things done by SignalR were handled by a single class (with a TODO to break it up.) If you switch the bus to RabbitMQ, then you still need SignalR for the website. Managing those 2 things as one doesn’t work. So, per the TODO, I broke it up.
Secondary required change: the battle for flexibility and simplicity continued. There are server level features and application level features, and they all need to be easily managed via code. I want everything to work out of the box, and then also be modifiable either via configuration on the hub, or via code overrides. The real trick from the code perspective is that the applications are, essentially, remote. Any changes you make need to be made to the server then propagated to the app. I spent a lot of time working on that, and it’s now in a good state. (Ironically, for the purposes of RabbitMQ, I don’t even need that yet. But, it’s done. You can manage application features and server features from the server api.)
Chosen change: On the hub, the SignalRBusHub being hosted as a feature in an application. It was quick and dirty. The feature simply subscribed to the bus then forwarded messages to SignalR. On the application, though, the SignalR connectivity was built into the bus object itself as a subclass. So, that was inconsistent. I changed the hub level bus to also be a subclass hosted by the server. That cleaned it up.
Chosen change: The SignalR code was all within it’s feature. It wasn’t reusable. I changed that so that SignalR hub hosting is a core feature that other features can opt into. That was already implemented for WebAPI and LocalScheduler, but the ability did not exist when I wrote the SignalR code. The implementation of it was very similar to WebAPI. That’s not because they are both ASP.NET technologies (that’s just coincidence), but because they both involve features registering with other features. The WebApi Feature keeps track of how many other features require it, and manages itself based on that. Anything that uses the OPT-IN approach will need this. I took most of the webapi feature code and moved it up to base classes, and implementation of the signalr feature became pretty insignificant. To register another feature with the signalr feature is just an attribute.
The SignaRHubHost attribute says that “when this feature is started, start the BusHub SignalR hub.”
Finally, I had to write the RabbitMQ code. There’s a lot of work to be done on that, but it’s working. (IE: a bus message can expire after a few seconds. Currently, it doesn’t.) At the moment, it’s the bare minimum.
Now, you can simply say UseRabbitMqBus(), and signalr is swapped out for RabbitMQ. (You are reminded that this is a test console app, so don’t pay attention to the static fields, etc.)
And here is the implementation of it.
The funny thing about that is that it’s specifically disabling SIGNALR. What if you also have a third implementation? To solve that I will introduce a FeatureRole, and then allow for disabling of the entire role. Then you’d be able to do: server.DisableFeaturesOfRole(“BusHost”), or similar.
For SignalR, I wrote the code to receive messages and delegate to clients. For RabbitMQ, I don’t need to do that because RabbitMQ does all of that automatically. So, there isn’t a server component running in the app server for RabbitMQ. It’s just the client bus.
The following screenshot is the admin portal as one of the demo features starts. The feature fires a bunch of useless child activities just to show how features can send messages that are associated to parent activities. In this case, during the start activity it creates a child activity that fires a bunch of messages for no good reason. I was pleased to see that, as expected, the message numbers are all in order. You do not get guaranteed message order, or guaranteed delivery, with SignalR. They are mostly in order, but there are always a few that are out of sorts. With RabbitMQ, they are in order every time.
I’ve put a lot of time into this over the last few days. I’m really happy to have RabbitMQ working via a single method call.
This is a good update. Now that RabbitMQ is in the mix, I can start working on the service layer. The service layer will need an out-of-the-box default for when you’re not using RabbitMQ. Usually I do out-of-the box first, but for this I will use RabbitMQ first because I’m looking forward to diving into it. But first I need some sleep.