A more functionally idiomatic approach to Akka.NET

Three weeks ago I wrote about my experiences trying out Akka.NET. I had assembled a small working Akka.NET example application written in F# using the information I was able to find on the subject.

My first attempt however borrowed heavily from the none-functional approach to Akka.NET, and while that worked fine, it wasn't very elegant. After publishing the blog post I got some suggestions and help from the Akka.NET team and @RogerAlsing in particular.

With the feedback it's time to revisit my code and re-implement it using a more functionally idiomatic approach.

If you haven't already, you should read my first blog post on Akka.NET for reference. If you want to learn more about Akka.NET I also suggest signing up for the free Akka.NET bootcamp at Petabridge.

The AkkaFlix example code is still available in Github, with the new functional approach in the "functional" branch

Objects vs functions and handling state

The first time around I chose to implement each of the actors in my application (User, Users, Player, Reporting) as objects. The reason for this was that I couldn't figure out how to avoid objects if I wanted my actors to have state.

Consider the User-object as an example. A user actor needs to know its own identity, and contain information about what it is currently watching.

type User(user) =
    inherit Actor()

    let user = user
    let mutable watching = null

    // Incoming message handler
    override x.OnReceive message =
        match message with
        | :? string as asset -> 
            watching <- asset
            printfn "%s is watching %s" user watching
        | _ ->  failwith "Unknown message"

It turned out however that handling state with the functional approach to Akka.NET is quite simple. Take a look at this function for spawning a new user actor.

let createUser parent username = 
  spawn parent username
      (fun mailbox -> 
          let rec loop(user) = actor {
              let! message = mailbox.Receive()
              printfn "%s is watching %s" user message
              return! loop(user)
          }                        
          loop(username))

There are two things to notice here. First I have removed the "watching" property of the actor as it wasn't really used for anything other than "reporting" what the user is watching by writing the information to the console.

The interesting part however is the recursive function ("loop"), taking the state of the actor as arguments. That's all there is to it. The state in this case is the user information, which is simply passed as an argument to the recursive function.

Spawning actors

When rewriting the example I ended up "wrapping" the Akka.NET spawn functions, creating a spawn function for each of my actor types, where the "parent"-argument is the actor spawning a child actor.

let createUser parent username = ...
let createUsers parent = ...
let createReporting parent = ...

That allowed me to build my hiearchy of actors using my wrappers to spawn new child actors in a clean way.

// Create a user actor for a single user identified by username
let createUser parent username = 
    spawn parent username
        (fun mailbox -> 
            let rec loop(user) = actor {
                let! message = mailbox.Receive()
                printfn "%s is watching %s" user message
                return! loop(user)
            }                        
            loop(username))

// Create users actor (keeps track of all users)
let createUsers parent = 
    spawn parent "users" 
        (fun mailbox -> 
            // Find or add an actor for a user identified by username
            let rec findOrSpawn (users : Dictionary<string, ActorRef>) username =
                match users.ContainsKey(username) with
                | true -> users.[username]
                | false ->
                    users.Add(username, createUser mailbox username)
                    findOrSpawn users username
            ... See repository for complete code ...

(and yes, I kept the Dictionary).

Discriminated unions for messages

I used to have a Play-event that looked something like this.

type PlayEvent = 
    { User: string
      Asset: string }

While that worked fine for the case at hand it was suggested that I should use discriminated unions instead. The Play-event now looks like this.

type Event =
    | Play of user : string * asset : string

The advantage of this approach is the ease of which I can add new types of events to my example. It also makes the pattern matching when handling incoming messages cleaner.

All in all

Changing from an object-oriented approach to a functional approach has, in my opinion, made the example code cleaner and more readable. The example is now contained within a single source file without it feeling too cluttered. It naturally seems like a better fit when using F#.

Again, if you haven't already, go and check out the updated code at GitHub.

Also don't hesitate to comment here or on Twitter. I am still learning and I appreciate the feedback!

View Comments