如何最好地指定一个用于Netty的Protobuf(最好使用内置的protobuf支持)

6
我正在在协议缓冲区中指定一个协议。传输层正在利用Netty的协议缓冲区支持,这意味着Netty的ProtobufDecoder只接受一种类型的MessageLite

现在,我想通过此通道发送各种不同的消息类型,每个子类型都与其关联有结构化信息。协议缓冲区并没有继承机制,因此我使用了一种组合方式。我不确定我的做法是否正确。

我的做法是使用枚举对不同的事件进行分类,并使用可选成员封装它们之间的差异。如下是我的.proto文件,出于清晰起见已经简化:

我的问题在于接收代码需要将EventType.ERROR和ErrorEventDetail进行关联,这感觉有点笨拙。

package events;

option java_package = "com.example";
option java_outer_classname = "EventProtocol";

message Event {
  enum EventType {
    START = 0;
    DELEGATE = 1;
    ERROR = 2;
    STOP = 3;
  }
  required events.Event.EventType event_type = 1 [default = START];
  required int32 id = 2;
  required int64 when = 3;
  optional StartEventDetail start_event_detail = 4;
  optional DelegateEventDetail delegate_event_detail = 5;
  optional ErrorEventDetail error_event_detail = 6;
  optional StopEventDetail stop_event_detail = 7;
}

message StartEventDetail {
    required string object_name = 1;
}

message DelegateEventDetail {
    required int32 object_id = 2;
    required string task = 3;
}

message ErrorEventDetail {
  required string text = 1;
  required int32 error_code = 2;
  optional Event cause = 3;
}

message StopEventDetail {
    required int32 object_id = 2;
}

这是最优解吗?我是否可以更好地使用继承,或者其他一些枚举的用法?
或者,我应该创建一个全新的OneToOneDecoder,通过某种头部识别消息类型?我可以这样做,但我宁愿不这样做...
谢谢
3个回答

7

看起来你已经使用了谷歌的protobuf技术之一,称为联合类型

要点是你有一个专门的type字段,你可以在上面“切换”以知道要获取哪个消息:

message OneMessage {
  enum Type { FOO = 1; BAR = 2; BAZ = 3; }

  // Identifies which field is filled in.
  required Type type = 1;

  // One of the following will be filled in.
  optional Foo foo = 2;
  optional Bar bar = 3;
  optional Baz baz = 4;
}

其中Foo、Bar和Baz可能在其他文件中作为单独的消息定义。您可以根据类型切换以获取实际有效载荷(这是Scala,但您可以使用Java的switch执行相同的操作):

OneMessage.getType match { 

  case OneMessage.Type.FOO => 

    val foo = OneMessage.getFoo
    // do the processing
    true

  case OneMessage.Type.BAR => 

    val bar = OneMessage.getBar
    // do the processing
    true

  case OneMessage.Type.BAZ => 

    val baz = OneMessage.getBaz
    // do the processing
    true

}

非常感谢,我错过了有关联合类型的文档。知道我走在正确的轨道上很好。干杯! - laher
我已经根据阅读联合类型的相关资料修改了我的定义。我的“Union”类型不再包含除Type字段和可选的“子类型”之外的任何内容。公共字段(例如我的示例中的“id”和“when”)现在保存在一个“EventCommon”消息中,该消息被组合到每个“子类型”中。因此,现在每个“子类型”都包含所有必要的数据。这似乎更有效。 - laher

3

我最初使用扩展机制解决了相同的问题,这个我在这里记录了下来。

但是我发现用Java编写处理扩展的代码非常丑陋和冗长,所以我转而使用了上述的Union方法。生成的Java代码提供了一种获取和构建每个消息的方式,因此代码更加简洁。

我使用两种机制来决定提取哪个可选消息。当需要性能时,我使用另一个答案中描述的switch方法;当不需要考虑性能并且不想维护switch语句时,我使用反射方法,只需为每个消息创建一个handle(Message)即可。下面给出了反射方法的示例,在我的情况下,Java包装器是一个名为Commands的类,并由Netty为我解码。它首先尝试查找具有特定消息作为参数的处理程序,如果失败,则使用驼峰命名法调用方法。为使其工作,枚举必须是驼峰消息的下划线名称。

// Helper that stops me having to create a switch statement for every command
// Relies on the Cmd enum naming being uppercase version of the sub message field names
// Will call the appropriate handle(Message) method by reflection
// If it is a command with no arguments, therefore no sub message it
// constructs the method name from the camelcase of the command enum
private MessageLite invokeHandler(Commands.Command cmd) throws Exception {
    Commands.Command.Cmd com= cmd.getCmd();
    //String name= CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_UNDERSCORE, com.name());
    String name= com.name().toLowerCase();
    jlog.debug("invokeHandler() - Looking up {} from {}", name, com.name());
    FieldDescriptor field= Commands.Command.getDescriptor().findFieldByName(name);
    if(field != null) {
        // if we have a matching field then extract it and call the handle method with that as a parameter
        Object c = cmd.getField(field);
        jlog.debug("invokeHandler() - {}\n{}", c.getClass().getCanonicalName(), c);
        Method m = getClass().getDeclaredMethod("handle", String.class, c.getClass());
        return (MessageLite) m.invoke(this, cmd.getUser(), c);
    }
    // else we call a method with the camelcase name of the Cmd, this is for commands that take no arguments other than the user
    String methodName= "handle"+CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, com.name());
    jlog.debug("invokeHandler() - using method: {}", methodName);
    Method m = getClass().getDeclaredMethod(methodName, String.class);
    return (MessageLite) m.invoke(this, cmd.getUser());
}

0

另一种方法是使用protobuf支持的扩展机制。我在联合类型过于庞大的情况下使用这种方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接