2011-11-08 16 views
6

Podaję protokół w protocol buffers. Warstwa transportowa obsługuje obsługę buforów protokołów od Netty - znaczenie tego polega na tym, że Netty's ProtobufDecoder przyjmuje jeden, i tylko jeden, typ MessageLite.Jak najlepiej określić Protobuf do użycia z Netty (najlepiej przy użyciu wbudowanej obsługi protobuf)

Teraz chcę wysłać różne rodzaje wiadomości na tym kanale, każdy podtyp ma powiązane z nim informacje strukturalne. Bufory protokołów nie mają mechanizmu dziedziczenia, więc używam pewnego rodzaju kompozycji. Nie jestem pewien, czy robię to w prawidłowy sposób.

Moje podejście polegało na kategoryzowaniu moich różnych wydarzeń za pomocą wyliczenia i ujmowania ich różnic za pomocą elementów opcjonalnych. Zobacz poniżej moją .proto, uprościłem to dla jasności.

Mój problem polega na tym, że kod odbiorczy musi utworzyć powiązanie między atrybutami EventType.ERROR i ErrorEventDetail. To po prostu wydaje się trochę niezgrabne.

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

Jest to optymalna? Czy mógłbym lepiej wykorzystać przedłużenia w jakiś sposób lub być może inne użycie z enum?

A może nawet powinienem utworzyć całkiem nowy OneToOneDecoder, który może zidentyfikować typ wiadomości według nagłówka? Mógłbym to zrobić, ale Wolałbym nie ...

Dzięki

Odpowiedz

6

Wygląda na to, że są dość blisko/korzysta już z jednym z Google protobufs technik, które zwane Union Types

Istotą to masz dedykowany type pole, które chcesz „przełącznik” on wiedzieć, które komunikaty dostać:

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

gdzie Foo, bar i Baz są/mogą być zdefiniowane w innych plikach jako oddzielne wiadomości. I można przełączyć na typ, aby uzyskać rzeczywistą ładowność (to Scala, ale można zrobić to samo z 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 

} 
+0

Dziękuję bardzo, brakowało mi dokumentu o typach Unii. Dobrze wiedzieć, że jestem na dobrej drodze. Pozdrawiam – laher

+0

Naprawdę zmieniłem teraz swoją definicję, w świetle czytania o typach unijnych. Mój typ "Union" nie zawiera już niczego oprócz pola Type plus opcjonalne "podtypy". Wspólne pola ("id" i "when" w moim przykładzie) są teraz przechowywane w wiadomości "EventCommon", która składa się na każdą "podtyp". Teraz każdy "podtyp" zawiera wszystkie niezbędne dane. Wydaje się, że działa lepiej. – laher

0

Innym podejściem jest użycie mechanizmu przedłużającego że Protobuf wspiera. Używam tego podejścia w sytuacjach, w których typ unii jest zbyt duży.

3

pierwotnie rozwiązać ten sam problem przy użyciu mechanizmu rozszerzenia, które dokumentować here

Ale znalazłem kod w Javie wymagane do czynienia z rozszerzeniami był potwornie brzydka i gadatliwy, więc przeszedłem do metody unijnej, jak opisano . Kod jest znacznie czystszy, ponieważ wygenerowany kod Java zapewnia sposób na uzyskanie i zbudowanie każdej wiadomości za jednym razem.

Używam dwóch mechanizmów do decydowania, który opcjonalny komunikat wyodrębnić. Używam metody przełącznika opisanej również w innej odpowiedzi, gdy wymagana jest wydajność i używam metody refleksyjnej, gdy wydajność nie jest problemem i nie chcę mieć instrukcji switch, po prostu utworzę uchwyt (Message) dla każdego wiadomość. Przykład metody odbicia podano poniżej, w moim przypadku opakowanie java jest klasą o nazwie Commands i jest dekodowane przez Netty dla mnie.Najpierw próbuje znaleźć program obsługi, który ma określony komunikat jako parametr, a następnie, jeśli to się nie powiedzie, wywołuje metodę wykorzystującą nazwę wielbłąda. Aby to działało, Enum musi być podkreśloną nazwą komunikatu o wielbłądzie.

// 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

miłe wykorzystanie refleksji! :RE –

Powiązane problemy