Reactive Services with Servicestack and F#
Learn how to build reactive services using Servicestack and F#. Explore real-time communication, WebSockets, and server-to-client messaging patterns.
Reactive word is not new nowadays. If you want your library to get limelight, use the reactive word in the name any way possible and it will definitely get initial attention. The reactive word is appropriate for things that follow reactive manifesto. That is true for now. And there are many variants of libraries on server and client to fulfill this.
Let's start from the start of the web.
We were having the old web where we used to do post events of a page. At the time of posting, the whole page goes to the server. And then loading loading loading of the page. But after the rise of JQuery, page refresh became a thing of the past. AJAX was there even before JQuery but became widely used after that only. Now, that spinner which was there in the browser tab bar / address bar came in the center of the page.
Side Note: If you want to make ajax requests faster, just use a spinner gif which spins faster. Tried and tested thing. With no code change, the performance issue will be resolved.
With hardware getting better and, if I be more specific, more connected, it is easy to store lots of data. And this makes things slower eventually. And users have to wait even for ajax requests. So, what is next? With HTML5 allowing Web Sockets and Server Events, things are becoming real-time, as now pushing data from Server to client is possible.
Server and Client are no more a one-sided love story. Now, both can send messages to each other. This new change allows building more reactive systems.
Let's take an example. I request my friend to come with me to visit X place. I got an answer: yes, will go. So, my request is accepted but we haven't visited that place yet. The visit is still pending. After some time, we visited. So, the result happened. I don't have to ask again and again. But that event was pushed by that end.
Same goes for the server. In a post event, the client requests some data. But that data may or may not be available at that specific moment. So, the Server can send 201 - Accepted status to the client. So, the data is accepted and waiting for the processed result. And whenever the result is ready, the server pushes data to the client. This way, neither the server is blocked nor the client.
There will be no need for a spinner. Neither slow nor fast.
SignalR is a very well-known example of real-time processing. But I wanted to achieve this by an API framework. And nothing can be better than Servicestack. It has Server Sent Event / Server Event support in all 4+ versions. So, why not?
Let's jump into code. The code is in F#. It is in F# for a reason, and you will know that as you keep reading.
I am running a standalone Servicestack host but the same code will work with Asp.net host also.
Programe.fs
module reactiveServicestack.main
open ServiceStack
open System
open ServiceStack.Logging
type AppHost() =
inherit AppHostHttpListenerBase ("Hello F# Service", typeof<HelloService>.Assembly)
override this.Configure container =
this.Plugins.Add(new PostmanFeature()) |> ignore
this.Plugins.Add(new CorsFeature()) |> ignore
this.Plugins.Add(new ServerEventsFeature()) |> ignore
let serverEventsFeature = this.GetPlugin<ServerEventsFeature>()
printfn "%s" serverEventsFeature.StreamPath
ignore()
[<EntryPoint>]
let main args =
LogManager.LogFactory <- new ConsoleLogFactory()
let env_port = Environment.GetEnvironmentVariable("PORT")
let port = if env_port = null then "1234" else env_port
let host = "http://localhost:8080/"
printfn "listening on %s ..." host
let appHost = new AppHost()
appHost.Init() |> ignore
appHost.Start host |> ignore
while true do Console.ReadLine() |> ignore
0 // return an integer exit code
Above code is very much classic Servicestack. Nothing fancy here.
HelloDto.fs
namespace reactiveServicestack
open System
open ServiceStack
//I can't but CLI can mutate this one
[<CLIMutable>]
type HelloResponse = { Result:string }
//There will always be hello world, at least something should be running
[<Route("/hello")>]
[<Route("/hello/{name}")>]
type Hello() =
interface IReturn<HelloResponse>
member val Name = "" with get, set
Again, POCO members. Hello and HelloResponse. No magic here also.
AsyncProcessor.fs
#nowarn "40"
namespace reactiveServicestack
module SSE =
open ServiceStack
let private serverEvent = ServiceStackHost.Instance.Container.TryResolve<IServerEvents>()
let NotifyAll (msg:'T) = serverEvent.NotifyAll(msg)
module AsyncProcess =
open System
let rnd = new Random()
let agent =
MailboxProcessor.Start(fun inbox ->
let rec messageLoop =
async {
let! (msg:Hello) = inbox.Receive()
do! Async.Sleep(3000)
Console.WriteLine("Original " + msg.Name)
let reversed = msg.Name.ToCharArray() |> Array.rev |> fun x -> new String (x)
Console.WriteLine("Reversed " + reversed)
SSE.NotifyAll({HelloResponse.Result = reversed})
return! messageLoop
}
messageLoop)
Here fun starts. I am creating an Actor which takes a Hello typed message and NotifyAll with HelloResponse after processing the name string.
Let's understand the complicated parts.
I have created an SSE module because if I open Servicestack I was getting an asyncbuilder compile error at the async keyword. And I needed to open it to expose all the extension methods. So, I wrapped things up in another module.
Don't create a separate instance of any kind of ServerEvent implementation; instead, resolve it as above. Else things will surely not work. I was stuck at that problem for a couple of days.
The Actor is very much traditional; I am reversing a string and, as it is very complex process, my actor will take precisely 3 seconds to do it. And then I am notifying all from the actor itself.
In an ideal case, it should be Subscriber ID/s or Channel/s.
As the actor is async by nature, it may complicate stuff to return things from the agent loop. If you have used a framework like AKKA, you must know that ASK is performance heavy compared to TELL. This way, you can fire the result from the Actor itself.
Now, you can easily guess what the service will look like.
Hello.fs
namespace reactiveServicestack
open ServiceStack
open ServiceStack.Logging
open System
open System.Net
type HelloService() =
inherit Service()
member val serverEvents:IServerEvents = null with get, set
member this.Get (request:Hello) =
{Result = "Hello " + request.Name}
member this.Post (request: Hello) =
AsyncProcess.agent.Post(request)
HttpStatusCode.Accepted
And the final piece of the puzzle: HTML
default.html
<html>
<head>
<title>Reactive Servicestack</title>
<link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.css" rel='stylesheet' type='text/css'>
</head>
<body>
<div>Hello this is default page</div>
<div>
<label for="name">Enter Your name</label>
<input type="text" id="name" value="" />
<button id="reverse">Reverse</button>
<ul>
</ul>
</div>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="/js/ss-utils.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.js"></script>
<script type="text/javascript">
$('#name').keypress(function (e) {
var key = e.which;
if(key == 13) // the enter key code
{
$('#reverse').click();
$('#name').val('');
return false;
}
});
$('#reverse').click(function(e) {
e.preventDefault();
var name = $('#name').val();
if (name != '' || name != undefined) {
$.post('/hello', {
name: name
})
.done(function() {
toastr.success(name + ' is very much Accepted!')
});
}
});
var addName = function(reversedName) {
$('ul').append('<li>' + reversedName + '</li>');
};
var channel = 'home';
var eventSource = new EventSource('/event-stream?channel=home&t=' + new Date().getTime());
$(eventSource).handleServerEvents({
handlers: {
HelloResponse: function(msg) {
console.log(msg);
addName(msg.Result);
}
//... Register custom handlers
}
});
</script>
</body>
</html>
I am taking a post request and returning 201-accepted instead of 200-ok from the server. On the client, it will go in the success callback only.
This way, we can easily decouple the server and client. And this can be used for games, stock market, betting, or other reactive systems.
I am not going into detail about Actor or AKKA, but as it is natively available in F#, I used it. One of the reasons, besides being more fun while writing code.
Please provide your input on this. I don't know if this is right or wrong, but it is very much possible that using current technology, even without enabling Web Sockets (Most cloud providers support web sockets), one can create reactive web services.
P.S. - With this, there will be no need for a spinner at all.
Frequently Asked Questions
Traditional AJAX requires the client to continuously request data from the server, while reactive communication allows the server to push data to the client whenever it's ready. With reactive systems, the client doesn't need to wait with a spinner or poll repeatedly—the server notifies the client when results are available, making the system more efficient and responsive.
ServiceStack provides built-in Server Sent Event and Server Event support in version 4 and later, making it an excellent framework for building reactive systems without needing additional libraries like SignalR. It allows both server and client to communicate bidirectionally, enabling true real-time processing with minimal complexity.
The 201 - Accepted status indicates that the server has received and accepted the client's request but hasn't completed processing yet. This allows the server to respond immediately without blocking, while the result is processed asynchronously and pushed to the client later, improving overall system responsiveness.
Web Sockets and Server Events enable true bidirectional, real-time communication between server and client, eliminating the need for constant client polling. This reduces server load, improves user experience by removing spinners and wait times, and allows applications to push data instantly when events occur.
F# is specifically chosen for this implementation, as mentioned in the article, because it provides functional programming features that make building reactive and asynchronous services more elegant and maintainable than imperative approaches.