Wie geht man in JSON Nachrichten mit Jackson und MongoDB durch?

8

Wir haben einen Microservice, der einige JSON-Daten aus der Queue bekommt, ein wenig verarbeitet und das Ergebnis der Verarbeitung weiter - wieder über die Queue - sendet. Im Microservice arbeiten wir nicht direkt mit JSONObject an likes, wir ordnen JSON Java-Klassen mit Jackson zu.

Bei der Bearbeitung interessiert sich der Microservice nur für einige Eigenschaften der eingehenden Nachricht, nicht für alle. Stellen Sie sich vor, dass es nur

empfängt %Vor%

Und sendet:

%Vor%

Wie kann ich andere Eigenschaften der Nachricht tunnellieren oder weiterleiten, die für diesen Dienst nicht von Interesse sind, ohne sie explizit in meinen Klassen zuzuordnen?

Für die Zwecke dieses Microservice würde es genügen, eine Struktur wie folgt zu haben:

%Vor%

Wenn ich jedoch nicht alle anderen Eigenschaften abbilde, gehen sie verloren.

Wenn ich sie abbilde, muss ich das Modell dieses Microservice jedes Mal aktualisieren, wenn sich die Struktur der Nachricht ändert, was Aufwand erfordert und fehleranfällig ist.

    
lexicore 08.12.2016, 09:58
quelle

7 Antworten

5

Der einfachste Weg ist die Verwendung von Map anstelle von benutzerdefinierten POJO im Falle einer agilen Struktur:

Es ist leicht, von JSON zu lesen, z.B. mit JsonParser parser (java docs hier ) ):

%Vor%

Es ist einfach, mit BasicDBObject (java docs hier ):

%Vor%

Sie können es sogar erreichen, indem Sie Map mit Task wie folgt umschließen:

%Vor%

Es ist auch möglich, das benutzerdefinierte JsonDeserializer zu verwenden, wenn es erforderlich ist ( Task muss in diesem Fall mit @JsonDeserialize(using = TaskDeserializer.class) kommentiert werden):

%Vor%     
Gregory.K 12.12.2016 22:06
quelle
3

Wie bereits erwähnt, können @JsonAnyGetter und @JsonAnySetter die beste Wahl für Sie sein. Ich denke, dass Sie es so flexibel wie typsicher machen können.

Das erste, was mir in den Sinn kommt, ist die genaue Trennung der Eigenschaften, die Sie brauchen, und des ganzen Rests.

Der Kern

Kalkulation.java

Ein einfaches unveränderbares "Berechnungs" -Objekt. Natürlich kann es auf andere Weise gestaltet werden, aber die Unveränderlichkeit macht es einfacher und zuverlässiger, glaube ich.

%Vor%

Operation.java

Eine einfache Berechnungsstrategie, die in einer Aufzählung definiert wird, da Jackson mit Aufzählungen wirklich gut arbeitet.

%Vor%

Jackson Mappings

AbstractTask.java

Beachten Sie, dass diese Klasse einen Wert liefern soll, aber sammeln Sie den Rest in einer Satellitenkarte, die von den mit @JsonAnySetter und @JsonAnyGetter annotierten Methoden verwaltet wird. Die Karte und die Methoden können sicher deklariert werden private , da Jackson das Schutzniveau nicht wirklich interessiert (und das ist großartig). Außerdem ist es in unveränderlicher Weise entworfen, mit Ausnahme der zugrunde liegenden Karte, die einfach auf einen neuen Wert kopiert werden kann.

%Vor%

CalculationTask.java

Hier ist eine Klasse, die eine konkrete Berechnungsaufgabe definiert. Auch hier arbeitet Jackson perfekt mit privaten Feldern und Methoden zusammen, so dass die gesamte Komplexität gekapselt werden kann. Ein Nachteil, den ich sehen kann, ist, dass JSON-Eigenschaften sowohl für das Serialisieren als auch für das Deserialisieren deklariert sind, aber auch als Vorteil betrachtet werden können. Beachten Sie, dass @JsonGetter Argumente hier nicht notwendig sind, aber ich habe die Eigenschaftsnamen sowohl für Ein- als auch für Aus-Operationen verdoppelt. Keine Aufgaben sollen manuell instanziiert werden - lass es nur Jackson tun.

%Vor%

Client-Server-Interaktion

CalculationController.java

Hier ist ein einfacher GET / PUT / DELETE-Controller für Integrationstests oder einfach nur manuell mit curl getestet werden.

%Vor%

ControllerExceptionHandler.java

Da die get und delete -Methoden, die unten in der DAO-Klasse deklariert werden, NoSuchElementException werfen, kann die Ausnahme leicht auf HTTP 404 abgebildet werden.

%Vor%

Die Anwendung selbst

CalculationService.java

Nur ein einfacher Dienst, der eine gewisse "Geschäftslogik" enthält.

%Vor%

Die Datenschicht

CalculationMapping.java

Nur eine Inhaberklasse, um mit MongoDB-Repositorys in Spring Data zu arbeiten, die den Ziel-MongoDB-Dokumentauflistungsnamen angeben.

%Vor%

ICalculationRepository.java

Ein Spring Data MongoDB CRUD-Repository für die Klasse CalculationMapping . Dieses Repository wird unten verwendet.

%Vor%

CalculationDao.java

Die DAO-Komponente macht in der Demo selbst nicht viel Arbeit, und es geht eher darum, den Persistenzjob an seine Superklasse zu delegieren und von Spring Framework leicht zu finden.

%Vor%

AbstractDao.java

Dies ist das Herz, das ganze ursprüngliche Objekt zu erhalten. Die ObjectMapper -Instanz wird verwendet, um Aufgaben in ihre jeweiligen Aufgabenzuordnungen (siehe convertValue -Methode) gemäß den mit den Jackson-Anmerkungen angegebenen Serialisierungsregeln zu konvertieren. Da die Demo Spring Data MongoDB verwendet, sind die Mapping-Klassen effektiv Map<String, Object> und erben die Klasse Document . Leider scheinen Map -orientierte Zuordnungen nicht mit Spring Data MongoDB-Annotationen wie @Id , @Field usw. zu funktionieren (mehr dazu unter Wie kombiniere ich java.util.Map-basierte Mappings mit den Spring Data MongoDB Annotationen (@ Id, @Field, ...)? ). Es kann jedoch gerechtfertigt sein, solange Sie keine beliebigen Dokumente zuordnen möchten.

%Vor%

Die Spring Boot-Anwendung

EntryPoint.class

Und eine Spring Boot-Demo, die alles als eine einzige HTTP-Anwendung ausführt, die Port 9000 hört.

%Vor%

Testen der Anwendung mit curl

%Vor%
  

wurde zu db local

gewechselt
%Vor%
  

& gt; GET / foo HTTP / 1.1
  & gt; Benutzer-Agent: curl / 7.35.0
  & gt; Host: localhost: 9000
  & gt; Akzeptieren: /
  & gt;
  & lt; HTTP / 1.1 404
  & lt; Inhaltslänge: 0
  & lt; Datum: Do, 15 Dec 2016 10:07:40 GMT
  & lt;

%Vor%

(leer)

%Vor%
  

& gt; PUT / foo HTTP / 1.1
  & gt; Benutzer-Agent: curl / 7.35.0
  & gt; Host: localhost: 9000
  & gt; Akzeptieren: /
  & gt; Inhaltstyp: application / json
  & gt; Inhaltslänge: 72
  & gt;
  & lt; HTTP / 1.1 204
  & lt; Datum: Do, 15 Dec 2016 10:11:13 GMT
  & lt;

%Vor%
  

{"_id": "foo", "_class": "q41036545.CalculationTaskMapping", "a": 3, "b": 4, "Vorgang": "MULTIPLY", "Ergebnis": 12, "foo ":" FOO "," bar ":" BAR "}

%Vor%
  

& gt; GET / foo HTTP / 1.1
  & gt; Benutzeragent: curl / 7.35.0
  & gt; Host: localhost: 9000
  & gt; Akzeptieren: /
  & gt;
  & lt; HTTP / 1.1 200
  & lt; Content-Type: application / json; charset = UTF-8
  & lt; Transfer-Encoding: Chunked
  & lt; Datum: Do, 15 Dec 2016 10:16:33 GMT
  & lt;
  {"a": 3.0, "b": 4.0, "Operation": "MULTIPLY", "Ergebnis": 12.0, "foo": "FOO", "bar": "BAR"}

%Vor%
  

& gt; LÖSCHEN / foo HTTP / 1.1
  & gt; Benutzer-Agent: curl / 7.35.0
  & gt; Host: localhost: 9000
  & gt; Akzeptieren: /
  & gt;
  & lt; HTTP / 1.1 204
  & lt; Datum: Do, 15 Dec 2016 11:51:26 GMT
  & lt;

%Vor%

(leer)

Der Quellcode kann unter Ссылка

gefunden werden     
Lyubomyr Shaydariv 15.12.2016 10:53
quelle
2

Keine von beiden ist sehr schön, aber was Sie tun könnten, ist die Struktur in zwei Teile aufzuteilen - ein strukturiertes Teil, das von Ihrem Microservice deserialisiert wird, und ein separates Feld 'additionalFields', das Ihren anderen JSON enthält, dann können Sie Ändern Sie den JSON in diesem Feld, ohne Task zu ändern. Sie könnten verschachtelte JSON-Dateien entweder als String hinzufügen.

%Vor%

Oder Sie könnten ein Map<String, Object> hinzufügen, mit dem Sie Schlüssel-Wert-Paare hinzufügen können, aber Sie würden wiederum den Typ Sicherheit verlieren:

%Vor%     
Daniel Scott 10.12.2016 10:44
quelle
2

Eine weitere Möglichkeit: Verwenden Sie @JsonAnySetter und / oder @JsonAnyGetter , um eingebettete "dynamische" Eigenschaften zu erstellen. Ein Beispiel hier:

Ссылка

aber die Grundidee ist, dass es möglich ist, "irgendetwas anderes" von JSON über @JsonAnySetter annotierte Methode (oder Feld in neueren Versionen) zu binden; sowie "zusätzliche Eigenschaften" für die Serialisierung durch ähnliche @JsonAnyGetter Methode / Feld mit Map Wert.

    
StaxMan 14.12.2016 21:23
quelle
0

Fügen Sie @JsonView zu Ihrem POJO hinzu, so:

%Vor%

und senden Sie es dann mit com.fasterxml.jackson.databind.ObjectMapper an Ihren Dienst wie folgt:

%Vor%     
dubious cyborg 13.12.2016 11:51
quelle
0

Konfigurieren Sie eine ObjectMapper-Bean in Spring / Javaconfig und legen Sie die Eigenschaft so fest, dass fehlende Eigenschaften ignoriert werden. Injizieren Sie dieselbe Bohne wo nötig.

%Vor%     
Ayub Malik 13.12.2016 14:51
quelle
0

Wenn ich also verstehe, was Sie wollen:

Service A - & gt; sende json string - & gt; Service B - & gt; Sende die gleiche JSON-Zeichenfolge - & gt; Service C

  • Service A erstellt die JSON-Zeichenfolge
  • Dienst B kümmert sich nur um einige Parameter der JSON-Zeichenfolge
  • Dienst C kümmert sich nur um einige Parameter der JSON-Zeichenfolge

Wenn dies das ist, was Sie wollen, würde ich ein Design vorschlagen, bei dem Sie die gesamte json-Zeichenkette speichern und diese einfach übergeben. Mit IgnoreUnknownProperties in Service B und C können Sie Ihre Objekte nach Belieben analysieren lassen. Dies erfordert jedoch, dass Sie es selbst analysieren, aber das ist leicht mit ObjectMapper, Sie können es erstellen und in Ihren Frühling Kontext und verwenden Sie es überall.

Beispiel:

%Vor%

Sie können die Zeichenfolge + MyClass einfach in ein Objekt einfügen oder eine Variable in MyClass erstellen, die die Zeichenfolge für die zukünftige Verwendung enthält (wie in meinem Beispiel gezeigt).

Dies ist der einfachste Weg, den ich sehe, dass Sie die Daten behalten können und Ihre Objekte nicht ändern müssen, um sie zu enthalten.

Noch eine Anmerkung, mache nicht jedes Mal einen neuen ObjectMapper (), erstelle ihn einmal, setze ihn in den Spring-Kontext und hol ihn dann durch Autowired, wo du ihn benutzen willst.

    
nesohc 16.12.2016 11:51
quelle