Ich habe eine sehr interessante Situation bei der Arbeit erlebt und möchte hier die Lösung mitteilen.
Stellen Sie sich vor, Sie müssen eine Reihe von Daten verarbeiten. Und um mit diesem Datensatz umzugehen, gibt es verschiedene Strategien. Ich musste beispielsweise Strategien erstellen, wie eine Datensammlung aus S3 oder Beispiele aus dem lokalen Repository abgerufen oder als Eingabe übergeben werden.
Und wer auch immer diese Strategie diktiert, ist derjenige, der die Anfrage stellt:
Ich möchte die Daten in S3 erhalten. Nehmen Sie die am Tag X zwischen den Stunden H1 und H2 generierten Daten, die vom Abóbora-Client stammen. Holen Sie sich die letzten 3000 Daten, die dies erfüllen.
Oder:
Nehmen Sie die Beispieldaten, die Sie dort haben, und kopieren Sie sie 10.000 Mal, um den Stresstest durchzuführen.
Oder sogar:
Ich habe dieses Verzeichnis, Sie haben auch Zugriff darauf. Holen Sie sich alles in diesem Verzeichnis und rekursiv in die Unterverzeichnisse.
Und schließlich auch:
Nehmen Sie diese Dateneinheit, die sich in der Eingabe befindet, und verwenden Sie sie.
Mein erster Gedanke war: „Wie kann ich die Form meiner Eingabe in Java definieren?“
Und ich kam zu der ersten Schlussfolgerung, die für das Projekt super wichtig ist: „Weißt du was? Ich werde keine Form definieren. Füge eine Map
Da ich außerdem keine Formen in das DTO eingefügt habe, hatte ich völlige Freiheit, mit der Eingabe zu experimentieren.
Nachdem wir einen Proof of Concept erstellt haben, kommen wir zu der Situation: Wir müssen aus dem Stress-POC herauskommen und zu etwas übergehen, das einem echten Nutzen nahekommt.
Der Dienst, den ich leistete, bestand darin, Regeln zu validieren. Grundsätzlich musste ich beim Ändern einer Regel diese Regel mit den Ereignissen vergleichen, die in der Produktionsanwendung aufgetreten sind. Oder wenn die Anwendung geändert wurde und keine Fehler aufgetreten sind, wird erwartet, dass die Entscheidung für dieselbe Regel für dieselben Daten dieselbe bleibt; Wenn nun die Entscheidung für dieselbe Regel, die denselben Datensatz verwendet, geändert wird ... nun, das kann zu Problemen führen.
Also brauchte ich diese Anwendung, um das Backtesting der Regeln durchzuführen. Ich muss die reale Anwendung aufrufen, die die Daten zur Auswertung und die betreffende Regel sendet. Die Verwendung hierfür ist sehr vielfältig:
Dafür brauche ich also einige Strategien für die Entstehung von Ereignissen:
Und ich brauche auch Strategien, die von meinen Regeln abweichen:
Wie gehe ich damit um? Nun, lassen Sie den Benutzer die Daten bereitstellen!
Wissen Sie etwas, das meine Aufmerksamkeit immer auf JSON-Schema gelenkt hat? Das hier:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
Diese Felder beginnen mit $. Meiner Meinung nach werden sie zur Angabe von Metadaten verwendet. Warum also nicht dies in der Dateneingabe verwenden, um die Metadaten der verwendeten Strategie anzugeben?
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
Zum Beispiel kann ich 15000 Exemplare meiner Daten als Muster bestellen. Oder fordern Sie einige Dinge von S3 an, indem Sie eine Abfrage in Athena durchführen:
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
Oder im lokalen Pfad?
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
Und so kann ich die Auswahl der bevorstehenden Strategie delegieren.
Mein erster Ansatz im Umgang mit Strategien war dieser:
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
Also stellte mein Architekt während der Codeüberprüfung zwei Fragen:
Was habe ich daraus verstanden? Dass es eine gute Idee wäre, die Fassade zu nutzen, um die Verarbeitung an die richtige Ecke zu delegieren und... die manuelle Kontrolle aufzugeben?
Nun, durch den Frühling passiert viel Magie. Da wir in einem Java-Haus mit Java-Expertise sind, warum nicht das idiomatische Java/Spring verwenden, oder? Nur weil ich als Einzelperson manche Dinge schwer zu verstehen finde, heißt das nicht zwangsläufig, dass sie kompliziert sind. Lassen Sie uns also in die Welt der Java-Abhängigkeitsinjektionsmagie eintauchen.
Was früher war:
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
Wurde:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
Meine Controller-Ebene muss dies also nicht verwalten. Überlassen Sie es der Fassade.
Also, wie machen wir die Fassade? Nun, um zu beginnen, muss ich alle Objekte hinein injizieren:
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
Ok, für den Haupt-DataLoader schreibe ich ihn zusätzlich zu @Service als @Primary. Den Rest schreibe ich einfach mit @Service auf.
Testen Sie dies hier, indem Sie getDataLoader so einstellen, dass es null zurückgibt, nur um auszuprobieren, wie Spring den Konstruktor aufruft, und ... es hat funktioniert. Jetzt muss ich notieren mit Metadaten für jeden Dienst, welche Strategie er verwendet...
Wie geht das...
Nun, schauen Sie! In Java haben wir Annotationen! Ich kann eine Laufzeit-Annotation erstellen, die enthält, welche Strategien von dieser Komponente verwendet werden!
Damit ich so etwas in AthenaQueryDataLoader haben kann:
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
Und ich kann auch Aliase haben, warum nicht?
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
Und zeigen!
Aber wie erstellt man diese Anmerkung? Nun, ich brauche ein Attribut, das ein Vektor aus Zeichenfolgen ist (der Java-Compiler kümmert sich bereits um die Bereitstellung einer einzelnen Zeichenfolge und deren Umwandlung in einen Vektor mit einer Position). Der Standardwert ist value. Es sieht so aus:
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
Wenn das Anmerkungsfeld keinen Wert hätte, müsste ich es explizit machen, und das würde hässlich aussehen, wie in der EstrategiaFeia-Anmerkung:
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
Meiner Meinung nach klingt es nicht so natürlich.
Okay, in Anbetracht dessen brauchen wir noch:
Um die Anmerkung zu extrahieren, muss ich Zugriff auf die Objektklasse haben:
dataLoaderFacade.loadData(inputDados, workingPath);
Darf ich außerdem fragen, ob dieser Kurs mit einer Anmerkung wie „Strategie:“ versehen wurde?
@Service // para o Spring gerenciar esse componente como um serviço public class DataLoaderFacade implements DataLoader { public DataLoaderFacade(DataLoader primaryDataLoader, List<DataLoader> dataLoaderWithStrategies) { // armazena de algum modo } @Override public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) { return getDataLoader(input).loadData(input, workingPath); } private DataLoader getDataLoader(Map<String, Object> input) { final var strategy = input.get("$strategy"); // magia... } }
Erinnern Sie sich, dass es das Wertefeld gibt? Nun, dieses Feld gibt einen Vektor von Zeichenfolgen zurück:
@Service @Primary @Estrategia("athena-query") public class AthenaQueryDataLoader implements DataLoader { // ... }
Zeigen! Aber ich stehe vor einer Herausforderung, denn vorher hatte ich ein Objekt vom Typ T und jetzt möchte ich dasselbe Objekt in (T, String)[] abbilden. In Streams ist die klassische Operation, die dies tut, flatMap. Und Java erlaubt mir auch nicht, so ein Tupel aus dem Nichts zurückzugeben, aber ich kann damit einen Datensatz erstellen.
Es würde ungefähr so aussehen:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
Was ist, wenn es ein Objekt gibt, das nicht mit einer Strategie versehen wurde? Wird es NPE geben? Besser nicht, lass es uns vor der NPE herausfiltern:
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
Vor diesem Hintergrund muss ich noch eine Karte zusammenstellen. Und siehe da: Java stellt hierfür bereits einen Collector zur Verfügung! Collector.toMap(keyMapper, valueMapper)
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
Bis jetzt ok. Aber flatMap hat mich besonders gestört. Es gibt eine neue Java-API namens mapMulti, die dieses Potenzial zur Vervielfältigung bietet:
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
Schönheit. Ich habe es für DataLoader bekommen, aber ich muss das Gleiche auch für RuleLoader tun. Oder vielleicht auch nicht? Wie Sie bemerken, enthält dieser Code nichts, was spezifisch für DataLoader ist. Wir können diesen Code abstrahieren!!
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
Aus rein praktischen Gründen habe ich diesen Algorithmus in die Anmerkung eingefügt:
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
Und für die Fassade? Nun, es ist gut, das Gleiche zu sagen. Ich habe beschlossen, Folgendes zu abstrahieren:
dataLoaderFacade.loadData(inputDados, workingPath);
Und die Fassade sieht so aus:
@Service // para o Spring gerenciar esse componente como um serviço public class DataLoaderFacade implements DataLoader { public DataLoaderFacade(DataLoader primaryDataLoader, List<DataLoader> dataLoaderWithStrategies) { // armazena de algum modo } @Override public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) { return getDataLoader(input).loadData(input, workingPath); } private DataLoader getDataLoader(Map<String, Object> input) { final var strategy = input.get("$strategy"); // magia... } }
Das obige ist der detaillierte Inhalt vonVerwenden von Annotationen in Java zum Erstellen einer Strategie. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!