2017-05-16 2 views
0

Angenommen, ich habe zwei Proto Puffertypen:Kopieren Felder über Objekte verschiedener Art in gRPC

message MessageType1 { 
    SomeType1 field1 = 1; 
    SomeType2 field2 = 2; 
    SomeType3 field3 = 3; 
} 

message MessageType2 { 
    SomeType1 field1 = 1; 
    SomeType2 field2 = 2; 
    SomeType4 field4 = 3; 
} 

Dann in Java Ich möchte in der Lage sein, ein zu verwenden Objekt als Vorlage zu einem anderen:

MessageType1 message1 = ...; 
MessageType2 message2 = MessageType2.newBuilder() 
    .usingTemplate(message1) // sets field1 & field2 only 
    .setField4(someValue) 
    .build() 

statt

MessageType1 message1 = ...; 
MessageType2 message2 = MessageType2.newBuilder() 
    .setField1(message1.getField1()) 
    .setField2(message1.getField2()) 
    .setField4(someValue) 
    .build() 

Warum ich das brauchen? Mein gRPC-Dienst ist so konzipiert, dass er eingehende Daten eines Typs (message1) entgegennimmt, der fast identisch mit einer anderen Nachricht eines anderen Typs (message2) ist - die gesendet werden muss. Die Anzahl der identischen Felder ist riesig und der Code ist banal. Die manuelle Lösung hat auch den Nachteil eines Fehlschlags, wenn ein neues Feld hinzugefügt wird.

Es gibt eine Template-Methode (object.newBuilder(template)), die das Templating-Objekt des gleichen Typs erlaubt, aber wie wäre es mit Templating zwischen verschiedenen Typen?

Ich könnte natürlich ein kleines Reflexionsprogramm schreiben, das alle Mitglieder (Methoden?) Inspiziert und manuell Daten kopiert, aber generierter Code sieht entmutigend und hässlich für diese Art von Quest aus.

Gibt es einen guten Ansatz, um dies anzugehen?

+0

Das klingt wie Sie sollten eine gemeinsame Unternachricht aus beiden Nachrichtentypen für ihre Kreuzung gezogen haben. (Oder, machen Sie es nur einen Nachrichtentyp und die eingehenden und ausgehenden Versionen haben unterschiedliche Teilmengen ihrer Felder in Gebrauch.) –

+0

Danke für Ihren Vorschlag @LouisWasserman! Das Extrahieren eines allgemeinen Typs ist eine Option, aber gebräuchliche Typen fügen boilerplate hinzu und wären in meiner Situation nicht geeignet, da src/dest aus verschiedenen Paketen stammt und verschiedene, nicht gemeinsam nutzbare Kontexte hat. Ich schrieb schließlich mein eigenes Dienstprogramm; Ich hoffe, ich habe keine Eckfälle verpasst (z. B. was passiert, wenn das Zielfeld innerhalb von "oneof" liegt und das Quellfeld nicht?). Ich denke, ich muss nur warten und sehen, wie ich vorankomme. – mindas

Antwort

0

Es stellte sich heraus, dass es nicht so kompliziert war. Ich schrieb ein kleines Dienstprogramm, das FieldDescriptors (etwas, das gRPC erzeugt) auswerten und zusammenbringen würde. In meiner Welt ist es genug, um sie nach Namen und Typ zu vergleichen.Volle Lösung hier:

/** 
* Copies fields from source to dest. Only copies fields if they are set, have matching name and type as their counterparts in dest. 
*/ 
public static void copyCommonFields(@Nonnull GeneratedMessageV3 source, @Nonnull com.google.protobuf.GeneratedMessageV3.Builder<?> destBuilder) { 
    Map<FieldDescriptorKeyElements, Descriptors.FieldDescriptor> elementsInSource = Maps.uniqueIndex(source.getDescriptorForType().getFields(), FieldDescriptorKeyElements::new); 
    Map<FieldDescriptorKeyElements, Descriptors.FieldDescriptor> elementsInDest = Maps.uniqueIndex(destBuilder.getDescriptorForType().getFields(), FieldDescriptorKeyElements::new); 
    // those two above could even be cached if necessary as this is static info 

    Set<FieldDescriptorKeyElements> elementsInBoth = Sets.intersection(elementsInSource.keySet(), elementsInDest.keySet()); 

    for (Map.Entry<Descriptors.FieldDescriptor, Object> entry : source.getAllFields().entrySet()) { 
     Descriptors.FieldDescriptor descriptor = entry.getKey(); 
     FieldDescriptorKeyElements keyElements = new FieldDescriptorKeyElements(descriptor); 
     if (entry.getValue() != null && elementsInBoth.contains(keyElements)) { 
      destBuilder.setField(elementsInDest.get(keyElements), entry.getValue()); 
     } 
    } 
} 

// used for convenient/quick lookups in a Set 
private static final class FieldDescriptorKeyElements { 
    final String fieldName; 
    final Descriptors.FieldDescriptor.JavaType javaType; 
    final boolean isRepeated; 

    private FieldDescriptorKeyElements(Descriptors.FieldDescriptor fieldDescriptor) { 
     this.fieldName = fieldDescriptor.getName(); 
     this.javaType = fieldDescriptor.getJavaType(); 
     this.isRepeated = fieldDescriptor.isRepeated(); 
    } 

    @Override 
    public int hashCode() { 
     return Objects.hash(fieldName, javaType, isRepeated); 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null || !(obj instanceof FieldDescriptorKeyElements)) { 
      return false; 
     } 
     FieldDescriptorKeyElements other = (FieldDescriptorKeyElements) obj; 
     return Objects.equals(this.fieldName, other.fieldName) && 
       Objects.equals(this.javaType, other.javaType) && 
       Objects.equals(this.isRepeated, other.isRepeated); 
    } 
} 
1

Beantworten Sie Ihre spezifische Frage: Nein, es gibt keinen vorlagenbasierten Weg, dies zu tun. Allerdings gibt es einige andere Möglichkeiten, um den gleichen Effekt zu erzielen:

  • Wenn Sie kümmern sich nicht um die Leistung und die Feldnummern sind die gleichen zwischen den Nachrichten, können Sie die erste Nachricht an Bytes serialisiert und deserialisiert sie zurück als die neue Nachricht. Dies erfordert, dass alle Felder in der ersten Nachricht mit dem Typ und der ID-Nummer derjenigen in der zweiten Nachricht übereinstimmen müssen (obwohl die zweite Nachricht andere Felder haben kann). Dies ist wahrscheinlich keine gute Idee.

  • Extrahieren Sie die allgemeinen Felder in eine andere Nachricht, und teilen Sie diese Nachricht. Zum Beispiel:

Proto:

message Common { 
    SomeType1 field1 = 1; 
    SomeType2 field2 = 2; 
    SomeType3 field3 = 3; 
} 

message MessageType1 { 
    Common common = 1; 
    // ... 
} 

message MessageType2 { 
    Common common = 1; 
    // ... 
} 

Dann können Sie die Nachrichten in Code teilen:

MessageType1 message1 = ...; 
MessageType2 message2 = MessageType2.newBuilder() 
    .setCommon(message1.getCommon()) 
    .build(); 

Dies ist wahrscheinlich die bessere Lösung.

  • Schließlich, wie Sie erwähnt haben, könnten Sie auf Reflexion zurückgreifen. Dies ist wahrscheinlich der ausführlichste und langsamste Weg, aber es würde Ihnen die meiste Kontrolle erlauben (abgesehen davon, dass Sie die Felder manuell kopieren). Nicht empfohlen.