在actor系统中隐式传递请求上下文

时间:2013-04-06 15:11:01

标签: scala akka actor implicit-conversion implicit

我想在协作参与者系统中隐式传播请求上下文。

为了简化和呈现这种情况,我的系统有多个actor,传递给这些actor的消息需要包含这个RequestContext对象。

ActorA接收MessageA类型的消息 ActorB接收MessageB

类型的消息

当ActorA需要向ActorB发送消息时,作为MessageA处理的一部分,它执行业务逻辑,然后根据逻辑结果以及MessageA中可用的RequestContext构造MessageB,然后将其发送给ActorB

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

我们要处理大量消息,并且显式传递requestContext很麻烦。

我正在尝试使用Scala的implicits功能的创造性方法,以避免将传入消息中嵌入的RequestContext显式注入传出消息。

消息是案例类(它们必须是)。我已经阅读了有关implicits规则的内容,但是将对象的属性带入当前的隐式范围似乎很牵强。

我相信这应该是一个共同的要求。 有什么建议 ?

感谢。

3 个答案:

答案 0 :(得分:6)

在你的例子中,你对有问题的信息的处理已经被考虑在一个方法中,这使得这个直截了当:

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)
}

答案 1 :(得分:6)

在我看来,最简单的方法是在案例类中隐含你的val。

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)
}

答案 2 :(得分:0)

创建包含原始邮件和上下文的通用信封类。创建一个延伸Actor的特征(您必须将其放在akka._包中)。覆盖解包信封的方法aroundReceive(),初始化actor的受保护变量以存储当前上下文,使用解压缩消息调用原始接收方法,取消初始化上下文变量。相反,这是一种可疑的方法,但这正是Actor.sender()的行为。

要向这样的上下文绑定actor发送消息,您必须手动将消息包装到上面提到的信封中,或者您可以通过引入实现tell /的ActorRef扩展来自动执行此任务通过将消息提升到上下文来询问操作。

这不仅仅是一个概念,而是我最近尝试成功的方法的简要总结。此外,我通过引入另一个抽象概念 - 一些基于actor,基于ThreadLocal,基于Future的隐式上下文环境等概括了它,这使我能够轻松地在所有同步/异步操作链中隐式传递上下文数据。