Search code examples
c#orleans

Orleans - Custom TCP Socket Connection


What is the proper way to connect to Orleans server?

Lets say i have a mobile app from which i want to connect to Orleans server.

Should i connect to Orleans directly or use some frontend server (and why?)

If directly should i use Orleans Streams or custom Socket connection. And if i can't use Orleans client (and streams), how should be a grain accepting socket connections implemented. Is it even possible as grains can get evicted from memory when not used?

If throught fronted, does that frontend need to be a grain itself?


Solution

  • You have multiple options to connect your mobile app to an Orleans cluster using a custom TCP socket. There are many ways to build a system using Orleans but for simplicity I will provide three models here:

    The central part of an Orleans system is the Orleans cluster. The cluster contains a number of Orleans silos which are processes and in production you will have at least three silos each running on separate hosts. The silos in the cluster will communicate with each other to call grain methods and maintain the cluster, and since they are on different hosts the communication will be over the network.

    Separate front-end and cluster

    The first option for connecting your mobile application is to create a separate group of front-end hosts where your custom TCP socket service executes. When a request is received by the socket service it uses an Orleans grain client to communicate with the Orleans cluster. The code of a grain method call looks like a simple method call but in reality the call is sent over the network from the front-end to the Orleans cluster.

    If you want "push" from the socket server to the mobile app you also need to be able to push messages from the Orleans cluster to the front-end. There are two ways to do that:

    I believe that streams at some points were being favored over observers but I don't think that is the case any more. In general observers are slightly easier to use but errors on the observer side are not propagated back to the cluster so it's essentially a fire-and-forget mechanism as opposed to streams that are able to propagate errors.

    Front-end and cluster in same process

    When the front-end is hosted separately from the Orleans cluster you gain some security because the Orleans silo hosts are not exposed directly to the internet. However, each request or push message requires an extra network hop. To avoid this you can combine a silo with a socket service into a single process and instead of having separate front-end and silo hosts you now have just one cluster which operates both the socket service and Orleans.

    However, there is one problem you have to overcome. You cannot call into a grain unless you use a grain client even if the call is from the same process as the grain. Luckily, there now exists a hosted client that allows you to make grain calls from the same process but outside the silo without having to use a full grain client that sends data over the network. It's a fairly new feature and I haven't seen any documentation about how to use this yet.

    Silo acting as socket service

    Instead of using a grain client to call into the Orleans cluster and observers/streams to send messages out of the cluster you can host the entire socket service in a grain in the cluster. You need to implement a grain that is able to act as a custom TCP socket server. To start this server you need to use a startup task that will call a RunAsync() method (or whatever) to start the socket server.

    You probably want an instance of this grain to be active in each silo. There are various ways to achieve that. You can use a stateless worker or you can use a placement strategy that ensures that the socket server grain is placed in the silo that executes the startup task. The basic idea is that when a new silo starts a startup task executes and this task ensures that the required grain is activated in the silo. Orleans will remove inactive grains so you also have to ensure that the socket server grains are never deactivated. You can do this in configuration or by delaying deactivation inside the grain.

    Since the socket server is inside a grain you are free to call other grain methods and you don't have to go through a separate grain client and instead you can use the GrainFactory property of the socket server grain. If your socket server require you to start independent tasks that will use GrainFactory you have to be mindful of the rules about grains and tasks.


    If your "custom TCP socket" is a web socket and you can build your system using .NET Core I suggest that you use ASP.NET Core with web sockets middleware (not SignalR) and use either separate front-end hosts or share the hosts between the front-end and the Orleans cluster by using the Orleans hosted client.

    If you are building a complete custom TCP socket solution you probably have to deal with encryption and authentication. You get this out of the box when using a web framework and HTTPS. Orleans can help you with state management of encryption keys etc. and it's doable but requires some effort. I know this from practical experience.