Какова общепринятая практика реализации границ модуля между группами актеров в типе akka?
TL / DR
Вот рабочее репо примера ниже. Как мне реализовать один субъект, получающий сообщения (предварительно), определенный в двух разных протоколах, аналогично реализации двух разных интерфейсов в OO.
Пример
С границей я имею в виду классический OO- граница интерфейса: отображение только операций, относящихся к другому модулю.
Например: рассмотрим Алису, Боба и Чарл ie. Алисе нравится разговаривать с Бобом, а Чарл ie часто задается вопросом, как поживает Боб. Чарл ie не знает об Алисе (и не должен), и наоборот. Между каждой парой существует протокол, сообщения которого они могут получать друг от друга:
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
}
Граница здесь - это два лица Боба: разговор с Алисой и разговор с Чарлом ie - это два разных протокола. Теперь каждый может поговорить с Бобом, не зная друг друга. Алиса, например, любит петь, но не смеется над ней, пока она это делает:
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 =>
bob ! ScoldBob
same
case ApologizeToAlice =>
bob ! SingToBob
same
}
}
Чарл ie, с другой стороны, озабочен только тем, как Боб чувствует себя в данный момент:
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 =>
bob ! HowYouDoinBob
same
case LaughToCharlie =>
bob ! HowYouDoinBob
same
}
}
Однако эффект Алисы на настроение Боба влияет на то, как Боб разговаривает с Чарлом ie. Для этого нам нужно объединить два протокола через BobsPersonalLife
, чтобы иметь возможность представлять их в рамках одного актера:
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 =>
charlie ! LaughToCharlie
same
case ScoldBob =>
alice ! ApologizeToAlice
sad
case SingToBob =>
alice ! LaughAtAlice
same
}
val sad: Behavior[ BobsPersonalLife ] = receiveMessage
{
case HowYouDoinBob =>
charlie ! CryToCharlie
same
case ScoldBob =>
alice ! ApologizeToAlice
same
case SingToBob =>
alice ! LaughAtAlice
happy
}
}
Пока все хорошо. Мы можем создать экземпляр Алисы и Чарла ie, используя ActorRef.narrow[ _X_ToBob ]
. Но как насчет Боба? Или, скорее, альтер-эго Бобов? Если мы хотим заменить Боба на Бориса, который жалуется не Чарлу ie, а Дорис, используя DorisToBob extends Protocol[ Doris, Bob ]
, мы больше не сможем получать сообщения от Алисы, так как нет общих супертрент AliceToBob
и DorisToBob
. . Внезапно BobsPersonalLife
становится замком для каждого Боба, с которым может разговаривать Алиса.
Каким будет способ заменить Боба Борисом? Если бы мы использовали ActorRef.unsafeUpcast
, мы потеряем безопасность типов. Если мы используем двух участников в общем состоянии, мы теряем безопасность потоков. Обертка _X_ToBob (например, Either[ AliceToBob, CharlieToBob ]
или сокращенный тип объединения Дотти) также не работает, поскольку оболочка просто берет на себя роль BobsPersonalLife
. когда мы просто позволяем DorisToBob
унаследовать от BobsPersonalLife
, мы получаем объединение всех возможных партнеров всех бобов alter-e go не может удалить ни одного из них, никогда.
Вопрос
Как мы можем добиться истинно безопасного разделения типов между Алисой и Чарлом ie внутри Боба?