Akka — отличный инструмент для написания распределенных приложений. Однако меня всегда удивляло то, что, будучи базирующимся на Scala, который является языком, очень безопасным для типов, элементарная конструкция в Akka — актер — на самом деле не является безопасной для типов. Вы можете отправить любое сообщение любому актеру и получить в ответ любой объект.
Предстоящий выпуск 2.2 Akka будет содержать экспериментальную реализацию типизированных каналов , которые используют макросы для обеспечения безопасности типов при взаимодействии акторов; но, прежде чем это будет окончательно, вот более простой и менее мощный подход, чтобы добавить некоторую типизацию в шаблон запроса .
Вы можете посмотреть на шаблон запроса как на вид асинхронного вызова метода: вы
отправить сообщение (его класс соответствует имени метода) с некоторыми аргументами и ожидать ответа (результат метода). Точнее говоря, мы возвращаем Future , которое в конечном итоге будет содержать ответ (если есть). Обратите внимание, что ответ также может иметь тип Unit , соответствующий методам, не возвращающим результат. Зная, когда такие «методы» завершены, все же может быть полезно.
Очень простой пример использования базового шаблона запроса:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
import akka.pattern.askcase class LookupUser(id: Int)// the actor impl should send back a message to the senderval userFuture = actor ? LookupUser(10)// userFuture: Future[Any]userFuture onSuccess { case result => { // do something with the result // result has type Any }} |
Не очень хорошая вещь здесь, что возвращаемый тип ? (см. реализацию AskSupport ) является Future[Any] , поскольку субъект может ответить любым сообщением. Однако в идеале при отправке LookupUser мы хотели бы получить Future[Option[User]] , при отправке UserCount a Future[Int] и так далее.
Это на самом деле довольно легко реализовать. Прежде всего, мы должны как-то вставить ожидаемый тип ответа в сообщение. Для этого мы можем использовать признак, который принимает тип ожидаемого ответа в качестве параметра типа:
|
1
|
trait Replyable[T] |
Это можно использовать в сообщениях, которые мы отправляем актеру:
|
1
2
|
case class LookupUser(id: Int) extends Replyable[Option[User]]case class UserCount() extends Replyable[Int] |
Теперь нам нужен вариант ? который возвращает будущее с правильным параметром типа:
|
01
02
03
04
05
06
07
08
09
10
|
trait ReplySupport { implicit class ReplyActorRef(actorRef: ActorRef) { def ?[T](message: Replyable[T]) (implicit timeout: Timeout, tag: ClassTag[T]): Future[T] = { akka.pattern.ask(actorRef, message).mapTo[T] } }}package object reply extends ReplySupport |
Вы можете видеть, что мы просто повторно используем существующую реализацию Ask и сопоставляем получающееся будущее с правильным типом. ClassTag аут является неявным параметром ask и ClassTag mapTo , поэтому мы должны также включить их в сигнатуру.
Использование довольно простое, фактически оно почти такое же, как и раньше, за исключением импорта и того, что будущее имеет правильный тип:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
import reply._val userFuture = actor ? LookupUser(10)// userFuture: Future[Option[User]]userFuture onSuccess { case result => { // do something with the result // result has type Option[User] }} |
Это сторона актера-пользователя. А как насчет самого актера? Как гарантировать, что если актер получит сообщение типа Replyable[T] , он действительно ответит с помощью T ? Как обычно в Scala, ответом снова является черта, которую можно смешать в актера:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
trait ReplyingActor extends Actor { def receive = { case m: Replyable[_] if receiveReplyable.isDefinedAt(m) => { try { sender ! receiveReplyable(m) } catch { case e: Exception => sender ! Failure(e) } } } def receiveReplyable[T]: PartialFunction[Replyable[T], T]} |
И пример использования:
|
1
2
3
4
5
6
|
class UserActor extends ReplyingActor { def receiveReplyable[T] = { case LookupUser(id) => Some(User(...)) case UserCount() => 512 }} |
Теперь все это хорошо проверено. Если бы мы попытались вернуть String в ветке UserCount , мы получили бы ошибку времени компиляции.