title: How to do type safe, thread safe boundaries with Akka?
body: <p>What is the accepted practice to implement module boundaries between groups of actors within akka typed?</p>
  <h1>TL/DR</h1>
  <p>Here's a <a href="https://github.com/joostpapendorp/akka-decoupling-example" rel="nofollow noreferrer">working repo</a> of the example below.<br />
  How do I implement a single actor recieving messages (pre-)defined in two different protocols, analogous to implementing two different interfaces in OO.</p>
  <h1>Example</h1>
  <p>With boundary, I mean the classical OO-interface boundary: exposing only the operations relevant to the other module.</p>
  <p>For example: consider Alice, Bob and Charlie. Alice likes to talk to Bob and Charlie often wonders how Bob is doing. Charlie does not know about Alice (and shouldn't) and vice versa. Between each pair exists an protocol of which messages they can recieve of one another:</p>
  <pre><code>trait Protocol[ From, To ]
  
  object Alice
  {
      sealed trait BobToAlice extends Protocol[ Bob, Alice ]
      case object ApologizeToAlice extends BobToAlice
      case object LaughAtAlice extends BobToAlice
  }
  
  object Bob
  {
      sealed trait AliceToBob extends Protocol[ Alice, Bob ]
      case object SingToBob extends AliceToBob
      case object ScoldBob extends AliceToBob
  
      sealed trait CharlieToBob extends Protocol[ Charlie, Bob ]
      case object HowYouDoinBob extends CharlieToBob
  }
  
  object Charlie
  {
      sealed trait BobToCharlie extends Protocol[ Bob, Charlie ]
      case object CryToCharlie extends BobToCharlie
      case object LaughToCharlie extends BobToCharlie
  }
  
  </code></pre>
  <p>The boundary here being the two faces of Bob: talking to Alice and talking to Charlie are two different protocols. Each can now talk to Bob without them needing to know about the other. Alice, for example, likes to sing, but not to be laughed at while she does:</p>
  <pre><code>import akka.actor.typed.scaladsl.Behaviors
  import akka.actor.typed.scaladsl.Behaviors.same
  import akka.actor.typed.{ ActorRef, Behavior }
  
  class Alice( bob: ActorRef[ Protocol[ Alice, Bob ] ] )
  {
      import Alice._
      import nl.papendorp.solipsism.protocol.Bob.{ ScoldBob, SingToBob }
  
      val talkToBob: Behavior[ BobToAlice ] = Behaviors.receiveMessage
      {
          case LaughAtAlice =&gt;
              bob ! ScoldBob
              same
  
          case ApologizeToAlice =&gt;
              bob ! SingToBob
              same
      }
  }
  </code></pre>
  <p>Charlie, on the other hand, is only concerned with how Bob is feeling at the moment:</p>
  <pre><code>
  import akka.actor.typed.scaladsl.Behaviors.{ receiveMessage, same }
  import akka.actor.typed.{ ActorRef, Behavior }
  
  class Charlie(bob: ActorRef[Protocol[Charlie,Bob]])
  {
      import Charlie._
      import nl.papendorp.solipsism.protocol.Bob.HowYouDoinBob
      
      val concerned: Behavior[BobToCharlie] = receiveMessage
      {
          case CryToCharlie =&gt;
              bob ! HowYouDoinBob
              same
  
          case LaughToCharlie =&gt;
              bob ! HowYouDoinBob
              same
      }
  }
  
  </code></pre>
  <p>However, Alices effect on Bobs mood influences how Bob talks to Charlie. For that, we need to unify the two protocols via <code>BobsPersonalLife</code> to be able to represent them within a single actor:</p>
  <pre><code>import akka.actor.typed.scaladsl.Behaviors._
  import akka.actor.typed.{ ActorRef, Behavior }
  import Alice.BobToAlice
  import Charlie.BobToCharlie
  
  object Bob
  {
      private[ Bob ] sealed trait BobsPersonalLife
  
      sealed trait AliceToBob extends Protocol[Alice, Bob] with BobsPersonalLife
      case object SingToBob extends AliceToBob
      case object ScoldBob extends AliceToBob
  
      sealed trait CharlieToBob extends Protocol[Charlie, Bob] with BobsPersonalLife
      case object HowYouDoinBob extends CharlieToBob
  }
  
  class Bob( alice: ActorRef[BobToAlice], charlie: ActorRef[BobToCharlie] )
  {
      import Alice._
      import Bob._
      import Charlie._
      
      private val happy: Behavior[ BobsPersonalLife ] = receiveMessage
      {
          case HowYouDoinBob =&gt;
              charlie ! LaughToCharlie
              same
  
          case ScoldBob =&gt;
              alice ! ApologizeToAlice
              sad
  
          case SingToBob =&gt;
              alice ! LaughAtAlice
              same
      }
  
      val sad: Behavior[ BobsPersonalLife ] = receiveMessage
      {
          case HowYouDoinBob =&gt;
              charlie ! CryToCharlie
              same
  
          case ScoldBob =&gt;
              alice ! ApologizeToAlice
              same
  
          case SingToBob  =&gt;
              alice ! LaughAtAlice
              happy
      }
  }
  </code></pre>
  <p>So far, so good. We can instantiate Alice and Charlie using <code>ActorRef.narrow[ _X_ToBob ]</code>. But what about Bob? Or rather, Bobs alter-egos? If we want to replace Bob with Boris who doesn't complain to Charlie but to Doris, using a <code>DorisToBob extends Protocol[ Doris, Bob ]</code>, we can no longer recieve messages from Alice, since there's no shared supertrait of <code>AliceToBob</code> and <code>DorisToBob</code>. Suddenly, <code>BobsPersonalLife</code> is a lock-in for every Bob Alice can talk to.</p>
  <p>What would be the way to be able to replace Bob with a Boris? If we would use <code>ActorRef.unsafeUpcast</code> we lose type safety. If we use two actors on a shared state, we lose thread safety. Wrapping _X_ToBob (e.g. <code>Either[ AliceToBob, CharlieToBob ]</code> or Dotty's shorthand union type) doesn't work either, since the wrapper just takes over the role of <code>BobsPersonalLife</code>. when we just let <code>DorisToBob</code> inherit from <code>BobsPersonalLife</code>, we end up with a union of all possible partners of all Bobs alter-ego's unable to remove any of them, ever.</p>
  <h1>Question</h1>
  <p>How can we achieve true type-safe decoupling between Alice and Charlie within Bob?</p>