2013-04-06 13 views
5

我想隐式地在合作角色系统中传播请求上下文。在角色系统中隐式传递请求上下文

为了简化和展示这种情况,我的系统有多个参与者,传递给这些参与者的消息需要包含这个RequestContext对象。

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

时ActorA需要将消息发送到ActorB,作为MessageA的处理部分,其执行业务逻辑,然后构造一个MessageB从逻辑以及在MessageA可用的RequestContext的结果,然后将其发送到ActorB

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

我们有消息的回转将被处理,并且明确地绕过的RequestContext是麻烦的。

我想创造性地使用Scala的implicits功能,以避免将传入消息中嵌入的RequestContext明确注入传出消息。

消息是大小写类(它们需要是)。我已经阅读了关于implicits规则的知识,但是将一个对象的属性引入当前的隐式作用域似乎很牵强。

这,我肯定应该是一个共同的要求。 有什么建议吗?

谢谢。

回答

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

感谢您的回答。我认真考虑了这种方法,但我放弃了认为,明确传递参数比添加所有导入和隐含以便使用该功能更具可读性和冗余性 - 您不觉得吗? – vishr 2013-04-06 23:48:47

+0

如果这是一个问题,我会分解出一个消息处理器,它是一个MessageA => MessageB,并在演员的主体中调用它。这也会增强可测试性 – Edmondo1984 2013-04-07 15:43:04

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中的不止一件事物使用上下文,则更有意义)。

的一种变化上面可能是:

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

为什么一个隐含的消息?不是应该隐含的请求吗?看到我的回答 – Edmondo1984 2013-04-06 18:20:50

+0

@Roland,你的想法激发了一个整洁的方法。如果MessageA和MessageB继承自AbstractMessage,并且我们提供从AbstractMessage到AbstractMessage的伴侣对象中的RequestContext的隐式转换,那么问题就解决了!每条消息所需做的就是声明一个隐式的请求上下文参数,并且只要有一个可用,它就会被传递! – vishr 2013-04-07 00:00:03

+0

好吧......我得到了极大的兴奋......事实证明,使用隐式MessageA参数标记每个句柄方法对于此工作是必需的,但不会使其可读。隐式声明在业务逻辑代码上传播太多。 – vishr 2013-04-07 17:43:16

0

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

将消息发送到这样一个上下文绑定的演员,你要包装你的邮件进入手动上面提到的信封,也可以通过引入扩展了ActorRef实现告诉/按要求操作自动执行此任务,以及将信息提升到上下文绑定。

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