【问题标题】:Passing request context implicitly in an actor system在参与者系统中隐式传递请求上下文
【发布时间】:2013-03-28 23:11:56
【问题描述】:

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

为了简化和呈现这种情况,我的系统有多个参与者,传递给这些参与者的消息需要包含这个 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 的隐式功能,以避免将嵌入在传入消息中的 RequestContext 显式注入到传出消息中。

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

这个,我相信应该是一个普遍的要求。 有什么建议 ?

谢谢。

【问题讨论】:

    标签: scala akka actor implicit-conversion implicit


    【解决方案1】:

    在我看来,最简单的方法是让你的 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)
    }
    

    【讨论】:

    • 感谢您的回答。我认真考虑过这种方法,但我放弃了认为显式传递参数比添加所有导入和隐式以使用该功能更具可读性和简洁性 - 你不觉得吗?
    • 如果这是一个问题,我会分解出一个消息处理器,它是一个 MessageA => MessageB,然后在actor的主体中调用它。这也将增强可测试性
    【解决方案2】:

    在您的示例中,您对相关消息的处理已被分解为一个方法,这使得这一点变得简单:

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

    【讨论】:

    • 为什么是隐式消息?不应该是隐含的请求吗?看我的回答
    • @Roland,您的想法启发了一种更简洁的方法。如果MessageA 和MessageB 继承自AbstractMessage,并且我们在AbstractMessage 的伴生对象中提供了从AbstractMessage 到RequestContext 的隐式转换,那么问题就解决了!每条消息需要做的就是声明一个隐式请求上下文参数,只要有一个可用,它就会被传递!
    • 嗯...我兴奋过度了...事实证明,使用隐式 MessageA 参数标记每个句柄方法对于它的工作是必要的,但不会使其可读。隐式声明在业务逻辑代码中传播过多。
    【解决方案3】:

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

    要将消息发送给这样一个上下文绑定的参与者,您必须手动将消息包装到上述信封中,或者您也可以通过在 ActorRef 上引入一个扩展来自动执行此任务,该扩展实现了告诉/询问操作通过将消息提升到上下文绑定。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-25
      • 2021-09-24
      • 1970-01-01
      • 1970-01-01
      • 2020-12-22
      • 2016-09-16
      • 2012-03-10
      • 1970-01-01
      相关资源
      最近更新 更多