Heim >Java >javaLernprogramm >Verwenden von Annotationen in Java zum Erstellen einer Strategie

Verwenden von Annotationen in Java zum Erstellen einer Strategie

Susan Sarandon
Susan SarandonOriginal
2025-01-10 12:13:43273Durchsuche

Usando annotations em Java para fazer um strategy

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.

Wie umsetzen?

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 hinzu, die damit umgehen kann.“

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:

  • Validieren Sie mögliche Abweichungen bei der Aktualisierung der Anwendung
  • Überprüfen Sie, ob die geänderten Regeln das gleiche Verhalten beibehalten
    • zum Beispiel die Optimierung der Regelausführungszeit
  • Überprüfen Sie, ob die Änderung der Regeln zu der erwarteten Änderung der Entscheidungen geführt hat
  • Bestätigen Sie, dass die Anwendung durch die Änderung tatsächlich effizienter geworden ist
    • Wenn ich beispielsweise die neue Version von GraalVM mit aktiviertem JVMCI verwende, erhöht sich die Anzahl der Anfragen, die ich stellen kann?

Dafür brauche ich also einige Strategien für die Entstehung von Ereignissen:

  • Holen Sie sich die echten Daten von S3
  • Nehmen Sie die Daten, die sich als Beispiel im Repository befinden, und kopieren Sie sie mehrmals
  • rufen Sie die Daten von einem bestimmten Ort auf meinem lokalen Computer ab

Und ich brauche auch Strategien, die von meinen Regeln abweichen:

  • per Eingabe übergeben
  • verwendet den schnell laufenden Stub
  • verwendet eine Stichprobe basierend auf der Produktionsregel
  • Verwenden Sie diesen Pfad hier auf meinem Computer

Wie gehe ich damit um? Nun, lassen Sie den Benutzer die Daten bereitstellen!

Die API für Strategie

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.

Codeüberprüfung und die Fassade

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:

  • „Warum instanziieren Sie alles und lassen den Frühling nicht für sich arbeiten?“
  • Er hat eine DataLoaderFacade im Code erstellt und sie halbfertig aufgegeben

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.

Erstellen des Objekts Fassade

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:

  • Klassenanmerkungen aus übergebenen Objekten extrahieren
  • Erstellen Sie eine String-Map Rechtspfeil Datenlader (oder string Rechtspfeil T)

Extrahieren der Anmerkung und Zusammenstellen der Karte

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

Unter der Fassade

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn