Как лучше указать Protobuf для использования с Netty (предпочтительно с использованием встроенной поддержки protobuf)

Я указываю протокол в буферах протокола. Транспортный уровень использует поддержку буферов протоколов Netty. netty.io/docs/3.2.6.Final/xref/org/jboss/netty/handler/codec/protobuf/ProtobufDecoder.html" rel="noreferrer" title="ProtobufDecoder">ProtobufDecoder принимает один, а только один тип MessageLite.

Теперь я хочу отправить по этому каналу сообщения различных типов, причем каждый подтип имеет связанную с ним структурированную информацию. Протокол-буферы не имеют механизма наследования, поэтому я использую своего рода композицию. Я не уверен, правильно ли я поступаю.

Мой подход состоял в том, чтобы классифицировать мои различные события с помощью перечисления и инкапсулировать их различия с помощью необязательных членов. См. мой .proto ниже, я упростил его для ясности.

Моя проблема заключается в том, что принимающий код должен установить связь между EventType.ERROR и ErrorEventDetail. Это выглядит немного неуклюже.

Упрощенный Events.proto:

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

Это оптимально? Будет ли мне лучше как-то использовать расширения или, возможно, какое-то другое использование enum?

Или даже я должен создать совершенно новый OneToOneDecoder, который может идентифицировать тип сообщения по какому-либо заголовку? Я мог бы сделать это, но я бы не хотел...

Спасибо


person laher    schedule 08.11.2011    source источник


Ответы (3)


Похоже, вы довольно близко / уже используете один из методов protobufs Google, который называется Типы союзов

Суть в том, что у вас есть выделенное поле 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, но вы можете сделать то же самое с switch из Java):

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

}
person tolitius    schedule 09.11.2011
comment
большое спасибо, я пропустил документ о Union Types. Приятно знать, что я на правильном пути. Ваше здоровье - person laher; 09.11.2011
comment
На самом деле я изменил свое определение сейчас, в свете чтения Union Types. Мой тип «Союз» больше не содержит ничего, кроме поля «Тип» и необязательных «подтипов». Общие поля («идентификатор» и «когда» в моем примере) теперь хранятся в сообщении «EventCommon», которое состоит из каждого «подтипа». Итак, теперь каждый «подтип» содержит все необходимые данные. Кажется, это работает лучше. - person laher; 09.11.2011

Первоначально я решил ту же проблему, используя механизм расширения, который я задокументировал здесь

Но я обнаружил, что код на Java, необходимый для работы с расширениями, был ужасно уродливым и многословным, поэтому я переключился на метод Union, как описано. Код намного чище, так как сгенерированный код Java позволяет получать и создавать каждое сообщение за один раз.

Я использую два механизма для принятия решения о том, какое необязательное сообщение следует извлечь. Я использую метод переключения, также описанный в другом ответе, когда требуется производительность, и я использую метод отражения, когда производительность не является проблемой, и я не хочу поддерживать оператор переключения, я просто создаю дескриптор (сообщение) для каждого сообщение. Пример метода отражения приведен ниже, в моем случае java-оболочка представляет собой класс с именем Commands и декодируется Netty для меня. Сначала он пытается найти обработчик, который имеет конкретное сообщение в качестве параметра, а затем, если это не удается, он вызывает метод, используя имя случая верблюда. Чтобы это работало, Enum должен быть символом подчеркивания сообщения в регистре верблюдов.

// 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());
}
person Jim Morris    schedule 19.12.2011

другой подход заключается в использовании механизма расширения, поддерживаемого protobuf. Я использую этот подход в ситуациях, когда тип объединения слишком велик.

person devsprint    schedule 15.12.2011