Despite a lack of posts, I have been working on the app server quite a bit recently.
The “bus” is an async, loose-coupled messaging system that allows every node to talk to every other node, and every application talk to every other application. The idea is for it to be simple. I want it to be simple to use, and simple to code for extensibility. Therein has been the angst.
The bus has been working for at least 2 years. It is an essential part of this thing, and was the first thing built. But, it was built minimally. The only requirement at the time was that it worked so that I could build everything else on top of it.
But, it wasn’t pretty. Applications communicate to the node via remoting. Nodes communicate to the hub via SingnalR (by default), or RabbitMQ (provided), or anything else you want to provide.
This was accomplished by having a really ugly IBus interface that allowed for registering child buses, bus names, etc. The SignalR bus inherited from LocalBus and managed everything within. Separation of concerns: 0.
I have spent the last few weeks thinking about that and reworking it. I spent many hours just prototyping in notepad, trying to figure out how all of these things can work together and be pretty. I tried (and failed) to enlist a fresh perspective on it.
Once I thought I had it working the way I wanted it to in notepad, I started prototyping the real thing. And, of course, it didn’t work as planned. Pretty is good. Functional is better.
Another goal is for the bus to remain stand alone. It doesn’t have to be used by the app server. Once I got it working and pretty for stand alone, I then had to integrate it into the appserver, and that didn’t go very well either. If I applied a hammer to it, it would have worked, but that’s not what I wanted. So I kept refactoring until I got it to where it is now.
At last, it’s good. I’m not going to say it’s done. Functionally, there are still some things it needs. Architecturally, it has one more test to go through. Currently, applications are isolated per app domain. I’m working towards isolating by process. When I get to that, it will require to swap out a remoting (marshalbyref) component with a pipe component. I think that will go in properly from the bus perspective, but the app server may need more work. I have one “temporary” interface in there that is adapting the bus for how the app server registers app domains. When it comes to a pipe, it won’t register an appdomain, so that’s where it will break.
The bus is improved in the following ways:
- It’s all JSON serialized now. Before, I had serialization/deserialization methods that would be called only when a message had to cross app domains. That got sloppy.
- A long known issue is that a message type may not exist in a remote location, so the message couldn’t be deserialized. That was never a problem because I have all of the contracts everywhere, but it wouldn’t fly for a custom user type that isn’t deployed everywhere. That’s all handled now, but with room for improvement. If something is unable to deserialize a particular type, it will keep trying every time it receives that type. I’m going to improve it to maintain a small list of types that it knows it can’t handle so that it doesn’t keep throwing exceptions.
- The bus now handles object, as it always has, and the underlying message type which is BusMessage. You can subscribe to either.
The bus was built on objects. You subscribe to objects of a particular type, and you will receive all of those objects and all of it’s subclasses. Getting that to work side-by-side with generic bus messages was fun.
One key difference between generic and object messages: you can specify conditions on objects, which is a widely used feature. You can’t yet do that for generic messages. For objects, it uses FUNC<MESSAGE, BOOL>. For generic, all it could do right now is FUNC<STRING, BOOL>. Of course, that can improve, but that’s where it is for the moment.
It creates a signalr bus server, and a node. The node server has an AddAppDomain method. You pass it an appdomain, and it will instantiate a bus in that appdomain and hold on to a remote reference to it.
The test goes on to:
- Create 2 appdomains
- Add them to NServer
- Send a message to each bus: hub, server, app1, app2
- Confirm that every message is received by every bus: hub, server, app1, app2
This is a similar test. The difference is this uses the application server’s version.
ApplicationBusServer is application server specific. It keeps track of signalr connections, and publishes online/offline messages as nodes come and go.
ApplicationBusServer does not use the SignalRBusServer shown in the previous test. The intent was to resuse it, but it didn’t fit. It uses some of the same dependencies, but composes them differently.
At long last, the bus is reintegrated to the appserver, and the messages are flying. Furhtermore, the online/offline messages are now working properly, which is new. That never worked before.