2011-11-08 7 views
6

Sto specificando un protocollo in protocol buffers. Il livello di trasporto sta sfruttando il supporto del protocollo di buffer del protocollo Netty, il cui significato è che il numero ProtobufDecoder di Netty accetta uno e solo uno tipo di MessageLite.Il modo migliore per specificare un Protobuf da usare con Netty (preferibilmente usando il supporto protobuf incorporato)

Ora, voglio inviare una varietà di tipi di messaggi diversi su questo canale, ciascun sottotipo con informazioni strutturate ad esso associate. I buffer di protocollo non hanno un meccanismo di ereditarietà, quindi sto usando un tipo di composizione. Non sono sicuro se sto andando su di esso nel modo corretto.

Il mio approccio è stato quello di classificare i miei diversi eventi con un enum e incapsulare le loro differenze utilizzando membri opzionali. Vedere il mio .proto qui sotto, l'ho semplificato per motivi di chiarezza.

Il mio problema qui è che il codice ricevente deve creare l'associazione tra EventType.ERROR e ErrorEventDetail. Questo mi sembra un po 'goffo.

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

È questo ottimale? Sarebbe meglio usare extends in qualche modo, o forse qualche altro uso di enum?

O anche, dovrei creare un intero nuovo OneToOneDecoder che può identificare un tipo di messaggio da qualche tipo di intestazione? Potrei farlo, ma Preferirei di no ...

Grazie

risposta

6

sembra che tu sei abbastanza vicino/già utilizzando uno dei protobufs tecniche del Google che ha invitato Union Types

Il succo è che devi una dedicata type campo, che si sarebbe "switch" per sapere quale messaggio di ottenere:

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

dove Foo, Bar e Baz sono/potrebbero essere definiti in altri file come messaggi separati. E si può accendere il tipo per ottenere il carico utile effettivo (è Scala, ma si può fare la stessa cosa con switch di 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 

} 
+0

grazie mille, ho perso il documento sui Tipi Unione. Buono a sapersi, sono sulla strada giusta. Cheers – laher

+0

In realtà ho cambiato la mia definizione ora, alla luce della lettura di Union Types. Il mio tipo 'Unione' non contiene più nulla tranne il campo Tipo più i 'sottotipi' opzionali. I campi comuni ('id' e 'when' nel mio esempio) sono ora mantenuti in un messaggio 'EventCommon', che è composto in ogni 'sottotipo'. Quindi, ora ogni 'sottotipo' contiene tutti i dati necessari. Questo sembra funzionare meglio. – laher

0

un altro approccio è quello di utilizzare il meccanismo di estensione che protobuf sta sostenendo. Sto usando questo approccio nelle situazioni in cui il tipo di unione è troppo grande.

3

Originariamente ho risolto lo stesso problema con il meccanismo di estensione, che mi documento here

ma ho trovato il codice in Java necessario a che fare con le estensioni era orribilmente brutto e prolisso, così sono passato al metodo dell'Unione, come descritto . Il codice è molto più pulito in quanto il codice Java generato fornisce un modo per ottenere e costruire ogni messaggio in un colpo solo.

Io uso due meccanismi per decidere quale messaggio opzionale estrarre. Uso il metodo switch descritto anche in un'altra risposta quando sono necessarie prestazioni e utilizzo un metodo reflection quando le prestazioni non sono un problema e non voglio dover mantenere un'istruzione switch, ma semplicemente creare un handle (Message) per ogni Messaggio. Di seguito è riportato un esempio del metodo di riflessione, nel mio caso il wrapper java è una classe denominata Commands, ed è decodificato da Netty per me.Cerca prima di trovare un gestore che abbia il messaggio specifico come parametro, quindi se fallisce chiama un metodo usando il nome del caso cammello. Perché ciò funzioni, l'Enum deve essere il nome di sottolineatura del messaggio del caso cammello.

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

bel uso del riflesso! : D –

Problemi correlati