Неявная передача контекста запроса в системе акторов

Я хотел бы неявно распространять контекст запроса в системе взаимодействующих субъектов.

Чтобы упростить и представить ситуацию, моя система имеет несколько субъектов, и сообщения, передаваемые этим субъектам, должны включать этот объект RequestContext.

ActorA получает сообщения типа MessageA ActorB получает сообщения типа MessageB

когда ActorA необходимо отправить сообщение ActorB, как часть обработки MessageA, он выполняет бизнес-логику, а затем создает MessageB из результатов логики, а также RequestContext, доступного в MessageA, и затем отправляет его ActorB.

def handle(ma:MessageA) {
 val intermediateResult = businessLogic(ma)
 actorB ! MessageB(intermediateResult, ma.requestContext)
}

У нас есть множество сообщений, которые нужно обработать, и явная передача requestContext обременительна.

Я пытаюсь найти творческие способы использования неявной функции Scala, чтобы избежать явного внедрения RequestContext, встроенного во входящее сообщение, в исходящее сообщение.

Сообщения являются классами case (и они должны быть). Я читал о неявных правилах, но перенос атрибута объекта в текущую неявную область видимости кажется надуманным.

Это, я уверен, должно быть общим требованием. Какие-либо предложения ?

Спасибо.


person vishr    schedule 06.04.2013    source источник


Ответы (3)


На мой взгляд, самый простой способ - сделать ваш val неявным в классе case.

case class MessageA(req: RequestA)(implicit val ctx: RequestContext)

case class MessageB(req: RequestB)(implicit val ctx: RequestContext)

def businessLogic(req:RequestA):RequestB


def handle(ma: MessageA): Unit = {
  // import all the members of ma so that there is a legal implicit RequestContext in scope
  import ma._
  val intermediateResult = businessLogic(req)
  actorB ! MessageB(intermediateResult)
}
person Edmondo1984    schedule 06.04.2013
comment
Спасибо за ответ. Я серьезно рассматривал этот подход, но я отказался от мысли, что явная передача параметра гораздо более удобочитаема и менее многословна, чем добавление всех импортов и имплицитов для использования этой функции - вам не кажется? - person vishr; 07.04.2013
comment
Если это проблема, я бы выделил процессор сообщений, который представляет собой MessageA => MessageB, и просто вызвал бы его в теле актора. Это также улучшит тестируемость - person Edmondo1984; 07.04.2013

В вашем примере ваша обработка рассматриваемого сообщения уже учтена в методе, что делает это прямолинейным:

trait RequestContext

case class MessageA(req: RequestA, ctx: RequestContext)
object MessageA {
  def apply(req: RequestA)(implicit ctx: RequestContext) = MessageA(req, ctx)
}

case class MessageB(req: RequestB, ctx: RequestContext)
object MessageB {
  def apply(req: RequestB)(implicit ctx: RequestContext) = MessageB(req, ctx)
}

class Example extends Actor {

  def receive = {
    case MessageA(req, ctx) => handle(req)(ctx)
  }

  def handle(req: RequestA)(implicit ctx: RequestContext): Unit = {
    val intermediateResult = businessLogic(req) // could take implicit ctx as well
    actorB ! MessageB(intermediateResult)
  }
}

Но, как вы можете видеть, при объявлении типов сообщений все еще возникают некоторые накладные расходы, и сигнатуру метода handle также необходимо изменить. Стоит ли эта схема того, зависит от соотношения между потребителями и производителями этих неявных значений (т. е. если несколько вещей в handle используют контекст, это имеет больше смысла).

Вариантом вышеизложенного может быть:

case class MessageA(req: RequestA, ctx: RequestContext)
object MessageA {
  def apply(req: RequestA)(implicit ctx: RequestContext) = MessageA(req, ctx)
  implicit def toContext(implicit msg: MessageA) = msg.ctx
}

case class MessageB(req: RequestB, ctx: RequestContext)
object MessageB {
  def apply(req: RequestB)(implicit ctx: RequestContext) = MessageB(req, ctx)
  implicit def toContext(implicit msg: MessageB) = msg.ctx
}

...
def handle(implicit ma: MessageA): Unit = {
  val intermediateResult = businessLogic(req)
  actorB ! MessageB(intermediateResult)
}
person Roland Kuhn    schedule 06.04.2013
comment
почему скрытое сообщение? это не запрос, который должен быть неявным? посмотри мой ответ - person Edmondo1984; 06.04.2013
comment
@ Роланд, твоя идея вдохновила на более аккуратный подход. Если MessageA и MessageB наследуют от AbstractMessage, и мы обеспечиваем неявное преобразование из AbstractMessage в RequestContext в сопутствующем объекте AbstractMessage, то проблема решена! Все, что нужно сделать каждому сообщению, — это объявить неявный параметр контекста запроса, и пока он доступен, он будет передан! - person vishr; 07.04.2013
comment
Что ж... Я заволновался... оказалось, что пометка каждого метода дескриптора неявным параметром MessageA необходима для того, чтобы это работало, но не делает его читабельным. Неявные объявления слишком сильно распространяются по коду бизнес-логики. - person vishr; 07.04.2013

Создайте универсальный класс конверта, содержащий как исходное сообщение, так и контекст. Создайте трейт, расширяющий Actor (вы должны поместить его в пакет akka._). Переопределить метод aroundReceive(), который распаковывает ваш конверт, инициализирует защищенную переменную актера для хранения текущего контекста, вызывает исходный метод получения с распакованным сообщением, деинициализирует переменную контекста. Довольно сомнительный подход, но именно так ведет себя Actor.sender().

Чтобы отправить сообщение такому контекстно-зависимому действующему лицу, вы должны вручную завернуть свое сообщение в упомянутый выше конверт, или вы также можете автоматизировать эту задачу, введя расширение над ActorRef, которое реализует операции Tell/Ask, поднимая сообщение до контекстно-зависимый.

Это не просто концепция, а краткое изложение подхода, который я недавно успешно опробовал. Более того, я еще больше обобщил его, введя еще одно абстрактное понятие — среды неявного контекста, такие как на основе акторов, на основе ThreadLocal, на основе Future и т. д., что позволило мне легко передавать данные контекста по всей цепочке синхронных/асинхронных операций неявным образом.

person Jiří Vypědřík    schedule 27.04.2015