After some frustration with our project’s choice for a planning poker solution, I did what any reasonable developer would do. No, I didn’t search for a more suitable solution, I decided to write my own and do a blog post about it.

By: Pedro Ferreira | Lunatech

The main idea is to build a simple application that uses some features of the following core Akka libraries:

Initially, I deliberately chose to use a classic actor implementation so that I could actually also talk about something that might be more useful: migrating an Akka Classic application to an Akka Typed version.

“Why would you want to do that?”, you may ask…​ Well, for me it is the type safety of Akka Typed which means that an Actor’s behaviour isn’t just a function of Any ⇒ Unit and strong support to avoid certain anti-patterns (more eloquently explained here), but feel free to search more if this migration is something for your project.

Overview

Obviously, the observable behaviour should stay the same after migration, which consists of:

  • a stream for each websocket connection with its own wire data domain forwarding all incoming data to the main application controller (Room Manager) as well as providing an interface to forward outgoing data.
  • the Room manager is responsible for translating from the wire domain to the application domain and route translated messages to appropriate rooms and manage each room lifecycle.
  • rooms are representations of each planning poker session, holding all necessary information and making sure that each participant is up to date with it.

Step by step

First things first, we need to replace our old classical actor system with its typed counterpart:

implicit val system: ActorSystem[SpawnProtocol.Command] =
    ActorSystem(Behaviors.setup[SpawnProtocol.Command](_ => SpawnProtocol()), "pointing-poker")

Take-aways

  • There is no default guardian actor, you need to provide one.

Tips

Now that we have our actor system, we need to create our top level actors:

system ! SpawnProtocol.Spawn(RoomManager(), "room-manager", Props.empty, system.ignoreRef)

Take-aways

  • There is no actorOf method on a typed ActorSystem.
  • If you don’t care/need the response, you can use system.ignoreRef

I could have created the RoomManager during the Behaviors.setup when creating the actor system, but I also need the RoomManager reference in my http api, so I created it from “outside” using the SpawnProtocol.Spawn which will reply with the ActorRef once created. However as you can see, I’m ignoring the ref for now, because to actually use it, we need to talk how to ask information out of the actor system:

implicit val timeout: Timeout = 3.seconds

  val roomManagerFuture: Future[ActorRef[RoomManager.Command]] = system.ask { ref =>
    SpawnProtocol.Spawn(RoomManager(), "room-manager", Props.empty, ref)
  }
  implicit val ec: ExecutionContextExecutor = system.executionContext

  roomManagerFuture.onComplete {
    case Success(roomManager) =>
      val api = API(roomManager, apiConfig)
      api.run()
    case Failure(exception) =>
      log.error("Error creating room manager {}", exception)
  }