Как лучше всего указать Protobuf для использования с Netty (желательно с использованием встроенной поддержки protobuf) - PullRequest
6 голосов
/ 09 ноября 2011

Я указываю протокол в протокольных буферах . Транспортный уровень использует поддержку Netty протокольных буферов - значение заключается в том, что Netty ProtobufDecoder принимает один и только один тип MessageLite .

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

Мой подход состоял в том, чтобы классифицировать мои различные события с помощью enum и инкапсулировать их различия, используя дополнительные члены. См. Мой .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;
}

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

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

Спасибо

Ответы [ 3 ]

6 голосов
/ 09 ноября 2011

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

Суть в том, что у вас есть выделенное поле 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

}
3 голосов
/ 19 декабря 2011

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

Но я обнаружил, что код на Java, необходимый для работы с расширениями, был ужасно уродлив и многословен, поэтому япереключился на метод объединения, как описано.Код намного чище, поскольку сгенерированный код 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());
}
0 голосов
/ 15 декабря 2011

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

...