Upgrade to Pro — share decks privately, control downloads, hide ads and more …

First experiences with Akka Typed and Dotty

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

First experiences with Akka Typed and Dotty

The release of Akka 2.6 was received with a lot of excitement in the community. A major milestone in this release was the marking of the Akka Typed APIs as stable. In the process of migrating an existing application from Akka Classic to Akka Typed, not only did I learn a lot but I also started to appreciate the great design of the APIs.
In an unrelated thread, there was the announcement by the Dotty team that, as of now, Dotty is feature-complete for Scala 3. With a scheduled availability of the first release candidate for Scala 3 around the end of this year, the time has come to start looking at what Scala 3 can bring to the Scala ecosystem.
With the above in mind, we will look at the following topics during this talk:
- Experiences during the migration of an application from classic to typed
- What can Scala 3 bring to Akka Typed?

Avatar for Eric Loots

Eric Loots

March 17, 2020
Tweet

Other Decks in Programming

Transcript

  1. LunaConf > First Experiences with Akka Typed and Dotty Akka

    Typed • Akka 2.6 • typed Actors API changed to Stable from Experimental • Serialisation • Java serialisation disabled • Jackson based serialisation with CBOR binary format • Important Akka APIs available in Typed flavour • Akka Cluster Singleton/Sharding/ClusterEvents/Routing/… • First experience of porting an Akka Classic application. • More to come…
  2. LunaConf > First Experiences with Akka Typed and Dotty Dotty

    • Dotty 0.21.0-RC1 • Feature complete for Scala 3 (December) • Currently at 0.23.0-RC1 - 0.24.0 in progress • Scala 3 • Projected Scala 3.0.0-RC1: end of year 2020 • Let’s use Scala 3 instead of Dotty for the remainder of this talk • Application Developer versus Library Developer • Focus on the former
  3. LunaConf > First Experiences with Akka Typed and Dotty First

    experience with Akka Typed • Existing code (pi-akka-cluster): • ±15 exercises written Akka Classic/Scala • Rewrite to Akka Typed using the functional style exclusively • Non-trivial code • Straight port first, then look for way to improve implementation • Learn from the experience • Check out this talk to see what this project is about!
  4. LunaConf > First Experiences with Akka Typed and Dotty First

    experience with Akka Typed - take aways • Complete protocol definitions are a big win! • Static typing is a major help for refactoring Akka Typed code • Moving to Akka Typed from classic will expose inconsistencies • Domain re-enforcement • Message adapters • add boilerplate… • makes interpreting code more difficult • once you get the hang off it, not a huge deal… • The Akka Typed version has added functionality and is modular
  5. LunaConf > First Experiences with Akka Typed and Dotty First

    experience with Akka Typed • Coding in Akka Classic requires great discipline: “anything goes” (almost) • Akka Typed allows one us to specify an Actor’s protocol • Let’s look at a simple example
  6. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol (1) How can we make Pinger understand the Pong message?
  7. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol (2) Current (Scala 2 approach): Wrap response in Ping Protocol
  8. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol object PingPong { sealed trait Command final case class Ping(replyTo: ActorRef[Response]) extends Command sealed trait Response case object Pong extends Response def apply(): Behavior[Command] = Behaviors.setup { context => (new PingPong).run() } } class PingPong { import PingPong._ def run(): Behavior[Command] = Behaviors.receiveMessage { case Ping(replyTo) => replyTo ! Pong Behaviors.same } } object Pinger { sealed trait Command case object SendPing extends Command case object StopPingPong extends Command final case class WrappedPongResponse(pong: PingPong.Response) extends Command def apply(pingPong: ActorRef[PingPong.Ping]): Behavior[Command] = Behaviors.setup { context => val pongResponseMapper: ActorRef[PingPong.Response] = context.messageAdapter(response => WrappedPongResponse(response)) Behaviors.receiveMessage { case StopPingPong => context.log.info(s"End of ping-pong game") context.system.terminate() Behaviors.stopped case SendPing => pingPong ! PingPong.Ping(replyTo = pongResponseMapper) Behaviors.same case WrappedPongResponse(response) => context.log.info(s"Hey: I just received a $response !!!") Behaviors.same } } }
  9. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol object PingPong { sealed trait Command final case class Ping(replyTo: ActorRef[Response]) extends Command sealed trait Response case object Pong extends Response def apply(): Behavior[Command] = Behaviors.setup { context => (new PingPong).run() } } class PingPong { import PingPong._ def run(): Behavior[Command] = Behaviors.receiveMessage { case Ping(replyTo) => replyTo ! Pong Behaviors.same } } object Pinger { sealed trait Command case object SendPing extends Command case object StopPingPong extends Command final case class WrappedPongResponse(pong: PingPong.Response) extends Command def apply(pingPong: ActorRef[PingPong.Ping]): Behavior[Command] = Behaviors.setup { context => val pongResponseMapper: ActorRef[PingPong.Response] = context.messageAdapter(response => WrappedPongResponse(response)) Behaviors.receiveMessage { case StopPingPong => context.log.info(s"End of ping-pong game") context.system.terminate() Behaviors.stopped case SendPing => pingPong ! PingPong.Ping(replyTo = pongResponseMapper) Behaviors.same case WrappedPongResponse(response) => context.log.info(s"Hey: I just received a $response !!!") Behaviors.same } } }
  10. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol (4) • Message adapter solves the issue, but • boilerplate • requires extra effort from code-reader • Is there a better solution? • We should be able to extend an actor’s command set with the expected reponse types… • Not solution yet, but there will be, thanks to Scala 3 • Let’s have a look later…
  11. LunaConf > First Experiences with Akka Typed and Dotty •

    Visual Cluster Status Tracker • Originally Implemented in Akka Classic in a single actor • Reimplemented in Akka Typed - modular & subscription model • No need to utilise message adapters Moving an Application to Akka Typed
  12. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  13. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  14. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  15. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  16. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  17. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  18. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  19. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  20. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Clustered Sudoku Solver • Actor based - mimics how one manually solves a Sudoku
  21. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Deploy 1 Sudoku Solver per cluster node • Use buffering using a Stash Buffer • Use a group router to distribute load between nodes
  22. LunaConf > First Experiences with Akka Typed and Dotty Moving

    an Application to Akka Typed • Akka Typed can help in discovering mixing Domains: • Inconsistent utilisation of SudokuDetailUnchanged message • Use NewUpdatesInFlight(-1) instead.
  23. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Why? • Motivation for Scala 3 • Remove some warts/inconsistencies in Scala 2 • Reduce complexity • Improve user experience • Lower learning curve
  24. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Compatibility • Source versus binary • Ability to utilise Scala 2 artefacts with Scala 3 (and vice versa) • Source rewrite tools • Gradual migration by • deprecating features in Scala 3.x • Removing deprecated features in Scala 3.(x+1)
  25. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Tooling/Getting started • Scastie supports Dotty • Start from an sbt sample project • Visual Code Studio • Follow instructions to install/configure here • Limited functionality • No Metals support [yet] but coming soon • IntelliJ • First support for Dotty in IntelliJ 2020.1-RC1
  26. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - a few features • Many new features • Top level definitions • Main functions • Extension methods - new simplified syntax • Contextual abstractions - complete overhaul of Scala 2 implicits • Optional braces syntax support • Union and Intersection types • … • Complete list at Dotty reference
  27. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Top-level definitions/Main functions • Package objects: • will still be around initially • will be marked as deprecated • will be removed soon afterwards • Use top-level definitions instead: • apart from classes, object, and traits, you can now also define: • implicit values • type aliases • definitions • values • Main functions • @main annotation turns a method into an executable program • method can take arbitrary number of arguments
  28. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Extension methods • The Scala 2, convoluted syntax for defining extension methods is gone! • New, concise syntax: package org.lunaconf.extensionmethods case class Member(id: String) case class Cluster(members: Set[Member]) { override def toString: String = s"Cluster(${members.mkString(", ")})" } def (cluster: Cluster) merge (otherCluster: Cluster): Cluster = Cluster(cluster.members ++ otherCluster.members) def (cluster: Cluster) addMember(newMember: Member): Cluster = Cluster(cluster.members + newMember) package org.lunaconf.extensionmethods object ExtensionMethods { def main(args: Array[String]): Unit = { val member0 = Member("node-0") val member1 = Member("node-1") val initialMembers0 = Set(member0, member1) val cluster0: Cluster = Cluster(initialMembers0) val member3 = Member("node-3") val member4 = Member("node-4") val initialMembers1 = Set(member3, member4) val cluster1: Cluster = Cluster(initialMembers1) val member2 = Member("node-2") val cluster = cluster0.addMember(member2).merge(cluster1) println(s"${cluster}") } }
  29. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Extension methods package org.lunaconf.extensionmethods case class Member(id: String) case class Cluster(members: Set[Member]) { override def toString: String = s"Cluster(${members.mkString(", ")})" } def (cluster: Cluster) merge (otherCluster: Cluster): Cluster = Cluster(cluster.members ++ otherCluster.members) def (cluster: Cluster) addMember(newMember: Member): Cluster = Cluster(cluster.members + newMember) package org.lunaconf.extensionmethods object ExtensionMethods { def main(args: Array[String]): Unit = { val member0 = Member("node-0") val member1 = Member("node-1") val initialMembers0 = Set(member0, member1) val cluster0: Cluster = Cluster(initialMembers0) val member3 = Member("node-3") val member4 = Member("node-4") val initialMembers1 = Set(member3, member4) val cluster1: Cluster = Cluster(initialMembers1) val member2 = Member("node-2") val cluster = cluster0.addMember(member2).merge(cluster1) println(s"${cluster}") } }
  30. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Optional braces syntax support • Compare to, for example, python • Compiler can rewrite code in both directions - details here • Works only with new control-syntax
  31. LunaConf > First Experiences with Akka Typed and Dotty Scala

    3 - Union types • Example: sealed trait Tools case class Hammer(size: Int) extends Tools case class Screwdriver(size: Int) extends Tools sealed trait ToolSupplies case class Nail(size: Int) extends ToolSupplies case class Screw(size: Int) extends ToolSupplies def printIt(t: Tools | ToolSupplies): Unit = { t match case tool: Tools => println(s"Got a tool: $tool") case supply: ToolSupplies => println(s"Got a supply: $supply") }
  32. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol revisited object PingPong { sealed trait Command final case class Ping(replyTo: ActorRef[Response]) extends Command sealed trait Response case object Pong extends Response def apply(): Behavior[Command] = Behaviors.setup { context => (new PingPong).run() } } class PingPong { import PingPong._ def run(): Behavior[Command] = Behaviors.receiveMessage { case Ping(replyTo) => replyTo ! Pong Behaviors.same } } object Pinger { // My protocol sealed trait Command case object SendPing extends Command case object StopPingPong extends Command // My protocol + the responses I need to understand... type CommandsAndResponses = Command | PingPong.Response def apply(pingPong: ActorRef[PingPong.Ping]): Behavior[CommandsAndResponses] = Behaviors.setup { context => Behaviors.receiveMessage { case StopPingPong => context.log.info(s"End of ping-pong game”) context.system.terminate() Behaviors.stopped case SendPing => pingPong ! PingPong.Ping(replyTo = context.self) Behaviors.same case response : PingPong.Response => context.log.info(s"Hey: I just received a $response !!!") Behaviors.same } } }
  33. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol revisited object Pinger { // My protocol sealed trait Command case object SendPing extends Command case object StopPingPong extends Command // My protocol + the responses I need to understand... type CommandsAndResponses = Command | PingPong.Response def apply(pingPong: ActorRef[PingPong.Ping]): Behavior[CommandsAndResponses] = Behaviors.setup { context => Behaviors.receiveMessage { case StopPingPong => context.log.info(s"End of ping-pong game”) context.system.terminate() Behaviors.stopped case SendPing => pingPong ! PingPong.Ping(replyTo = context.self) Behaviors.same case response : PingPong.Response => context.log.info(s"Hey: I just received a $response !!!") Behaviors.same } } } Scala 3 Union types save the day! object PingPong { sealed trait Command final case class Ping(replyTo: ActorRef[Response]) extends Command sealed trait Response case object Pong extends Response def apply(): Behavior[Command] = Behaviors.setup { context => (new PingPong).run() } } class PingPong { import PingPong._ def run(): Behavior[Command] = Behaviors.receiveMessage { case Ping(replyTo) => replyTo ! Pong Behaviors.same } }
  34. LunaConf > First Experiences with Akka Typed and Dotty Have

    we reached our goal? Not quite… We’ve polluted the Pinger actor’s protocol…
  35. LunaConf > First Experiences with Akka Typed and Dotty Have

    we reached our goal? Not quite… Let’s fix that! We’ve polluted the Pinger actor’s protocol…
  36. LunaConf > First Experiences with Akka Typed and Dotty Message

    Protocol revisited object Pinger { // My protocol sealed trait Command case object SendPing extends Command case object StopPingPong extends Command // My protocol + the responses I need to understand... type CommandsAndResponses = Command | PingPong.Response def apply(pingPong: ActorRef[PingPong.Ping]): Behavior[Commands] = Behaviors.setup[CommandsAndResponses] { context => Behaviors.receiveMessage { case StopPingPong => context.log.info(s"End of ping-pong game”) context.system.terminate() Behaviors.stopped case SendPing => pingPong ! PingPong.Ping(replyTo = context.self) Behaviors.same case response : PingPong.Response => context.log.info(s"Hey: I just received a $response !!!") Behaviors.same } }.narrow } Scala 3 Union types save the day! object PingPong { sealed trait Command final case class Ping(replyTo: ActorRef[Response]) extends Command sealed trait Response case object Pong extends Response def apply(): Behavior[Command] = Behaviors.setup { context => (new PingPong).run() } } class PingPong { import PingPong._ def run(): Behavior[Command] = Behaviors.receiveMessage { case Ping(replyTo) => replyTo ! Pong Behaviors.same } }
  37. LunaConf > First Experiences with Akka Typed and Dotty Conclusions

    • Akka Typed is a huge improvement on Akka Classic • Helps re-enforcing service domain • Huge help in case of refactoring • Major re-implementation of major Akka APIs: • Huge simplification • Less error prone • Message adapters add overhead. Scala 3 will eliminate this • Scala 3 • Removes some warts/inconsistencies • Simplified syntax • Great compatibility story • Tooling situation improving soon