Akka / Java:处理自定义actor中的多个消息类型?

时间:2014-09-18 16:13:20

标签: java akka messaging

要在Akka(Java绑定)中实现自己的自定义actor,可以扩展UntypedActor基类。这需要您定义自己的onReceive(...)方法:

@Override
public void onReceive(Object message) {
    // TODO
}

目前的问题是确定一种消息处理策略,使演员能够处理多种类型的消息。一种策略是使用反射/类型。这里的问题是:

  1. 它迫使我们创建空的“shell类”,它们只为消息提供语义(见下文);和
  2. 它占用message参数并阻止我们传递任何动态或有意义的内容
  3. 空shell类的示例:

    public class EmptyShellMessage { }
    

    然后在onReceive方法中看起来像:

    @Override
    public void onReceive(Class<?> message) {
        if(message.isAssignableFrom(EmptyShellMessage.class)) {
            // TODO
        } else {
            // TODO
        }
    }
    

    因此,我们不仅创建了一个无用的类,而且由于Object message现在被用于传达消息的类/类型,我们不能使用它来包含更多信息,尤其是另一个角色可能想要传递的动态/运行时信息。

    有时候我会看到一个变体:

    @Override
    public void onReceive(Object message) {
        if(message instanceof FizzEvent) {
            // TODO
        } else {
            // TODO
        }
    }
    

    但是在这里我们使用instanceof许多视为巨大的反模式(只是google“ instanceof antipattern < / em>的“)。

    然后我们有枚举:

    public enum ActorMessage {
        FizzEvent,
        BuzzEvent,
        FooEvent,
        BarEvent
    }
    

    现在onReceive看起来像:

    @Override
    public void onReceive(ActorMessage message) {
        if(message.equals(ActorMessage.FizzEvent)) {
            // TODO
        } else {
            // TODO
        }
    }
    

    这里的问题是我们可能有一个大型演员系统,需要处理数百甚至数千种不同的事件/消息类型。这个枚举变得很大并且难以维护。它也有与上面的反射策略相同的问题,它阻止我们在演员之间发送任何动态信息。

    我能想到的最后一件事是使用字符串:

    @Override
    public void onReceive(String message) {
        if(message.equals("FizzEvent")) {
            // TODO
        } else {
            // TODO
        }
    }
    

    但我讨厌这个。期。句末。

    所以我问:我在这里错过了一些明显的东西,也许是另一种策略? Java / Akka应用程序应该如何处理大量事件/消息类型,并指定它们在onReceive方法中处理哪一个?

4 个答案:

答案 0 :(得分:6)

不,你没有遗漏任何东西。我也不是粉丝。在Scala中它更好一点,因为onReceive方法可以被换出来模拟协议的变化状态,它使用的部分函数比if / elseif / else好一点......但它仍然很蹩​​脚。它在Erlang中更好,这是该模型的起源,但考虑到akka团队所面临的局限,他们做出了正确的设计选择并做得非常出色。

另一种策略是执行双重调度。因此,将命令传递给actor作为动作,或者在map中查找消息的处理程序。 Akka特工基本上是前者,当他们的力量使用时非常好。但总的来说,双重调度只会增加复杂性,因此对于大多数情况,人们只需要习惯标准方法。 java中的含义是instanceof还是switch语句。


双重调度(伪代码)的示例,此处包含完整性,以帮助理解。作为一种方法,它带有健康警告,所以我重申;标准方法是标准的原因,并且应该使用99%的时间。

onReceive( msg ) {
    msg.doWork()
}

这种方法的问题在于,现在消息需要知道如何处理自己,这很脏;它会引起紧密耦合并且可能很脆弱。呸。

因此我们可以使用查找处理程序(命令模式样式)

val handlers = Map( "msgid1"->Handler1, "msgid2->Handler2 )

onReceive( msg ) {
    val h = handlers(msg.id)

    h.doWork( msg )
}

这里的问题是现在还不清楚命令是什么,并且遵循代码通过涉及跳转更多。但有时候这是值得的。

正如Roland所指出的,在传递对演员本身的引用时必须小心。上面的例子并没有违背这个问题,但这很容易诱惑。

答案 1 :(得分:1)

我强烈建议不要传递Actor的this引用,因为这很容易让你将它传递到执行上下文边界,这可能以完全不显眼的方式发生。另一个问题是它需要消息类型知道actor如何处理它,这与消息传递应该如何解耦不同实体完全相反:Actor必须选择如何处理消息,而不是其他方式周围。

Actors是动态实体,它们可以以不可预见的顺序接收输入(这是模型的核心),因此我们必须使用动态语言功能来实现它们。 instanceof是促进消息类型的运行时发现的语言的一部分。无论它是否在其他环境中被滥用,并且与谁称之为反模式无关,它正是这项工作的正确工具。其他语言中的Actor实现 - 包括古老的Erlang使用模式匹配以实现同样的效果,模式匹配与instanceof测试完全相同(加上更方便的参数提取)。

答案 2 :(得分:0)

我为Java 8创建了一个匹配DSL的简单模式,这对Akka演员很有用:https://github.com/strangepleasures/matchmetender

public class MatchingActor extends UntypedActor {
  private LoggingAdapter log = Logging.getLogger(getContext().system(), this);

  public void onReceive(Object message) throws Exception {
    match(message, 
      (Foo foo) -> log.info("received a Foo: " + foo),
      (Bar bar) -> log.info("received a Bar: " + bar),
      (Baz baz) -> log.info("received a Baz: " + baz),
      (unknown) -> unhandled(unknown)
    );
  }
}

答案 3 :(得分:0)

我同意框架会促使您做一些根本不优雅的事情,并且我不会弄脏编写此类代码的手指。 所以我的方法是这样。 1)接口:

public interface IAnswerable {
    Object getAnswer();
}

2)演员:

@Override
public void onReceive(Object message) throws Throwable {
    IAnswerable iAnswerable = (IAnswerable) message;
    getSender().tell(iAnswerable.getAnswer(), getSelf());
}

3)我用作消息的类:

public class MyClass implements IAnswerable {

@Override
public Object getAnswer() {
    // Whatever you need to do here
    return null;
}

}

这是一种原始方法,但是您可以理解。它优雅且易读,可防止演员壮大。

希望它会有所帮助;)