ホームページ > Java > &#&チュートリアル > Java でアノテーションを使用して戦略を立てる

Java でアノテーションを使用して戦略を立てる

Susan Sarandon
リリース: 2025-01-10 12:13:43
オリジナル
270 人が閲覧しました

Usando annotations em Java para fazer um strategy

仕事で非常に興味深い状況に遭遇したので、ここで解決策を共有したいと思いました。

一連のデータを処理する必要があると想像してください。そして、このデータセットを扱うには、いくつかの異なる戦略があります。たとえば、S3 からデータのコレクション、ローカル リポジトリ内のサンプル、または入力として渡されるサンプルを取得する方法に関する戦略を作成する必要がありました。

そして、この戦略を決定するのは、リクエストを行う人です:

S3のデータを取得したい。 X 日目の H1 時間と H2 時間の間に生成された、Abobora クライアントからのデータを取得します。これを満たす最後の 3000 データを取得します。

または:

そこにあるサンプル データを 10,000 回コピーして、ストレス テストを実行します。

または:

私はこのディレクトリを持っています。あなたもそれにアクセスできます。そのディレクトリ内のすべてを取得し、サブディレクトリに再帰的に入れます。

そして最後に:

入力にあるこのデータユニットを取得して使用します。

実装方法は?

最初に考えたのは、「Java で入力の形状を定義するにはどうすればよいでしょうか?」

そして私は、プロジェクトにとって非常に重要な最初の結論に達しました: 「ご存知ですか? 形状を定義するつもりはありません。それを処理できる Map を追加します。」

その上、DTO にシェイプを入れていないので、入力を完全に自由に試すことができました。

したがって、概念実証を確立した後、ストレスの多い POC から抜け出し、実際の使用に近いものに進む必要があるという状況に到達します。

私が行っていたサービスはルールを検証することでした。基本的に、ルールを変更するときは、そのルールを取得し、本番アプリケーションで発生したイベントと照合する必要がありました。または、アプリケーションが変更され、バグがなかった場合、同じデータに対する同じルールの決定が同じままであることが期待されます。ここで、同じデータセットを使用する同じルールの決定が変更された場合...そうですね、それは潜在的な問題です。

そこで、ルールのバックテストを実行するにはこのアプリケーションが必要でした。評価用のデータと問題のルールを送信する実際のアプリケーションを実行する必要があります。これの使用法は非常に多様です:

  • アプリケーションの更新時に潜在的な逸脱を検証します
  • 変更されたルールが同じ動作を維持するかどうかを検証する
    • ルールの実行時間の最適化など
  • ルールの変更により、予想される意思決定の変更が生じたかどうかを確認します
  • アプリケーションの変更により実際に効率が向上したことを検証する
    • たとえば、JVMCI を有効にして新しいバージョンの GraalVM を使用すると、実行できるリクエストの数が増加しますか?

そのためには、イベントの起源に関するいくつかの戦略が必要です。

  • S3 から実際のデータを取得します
  • リポジトリ内のサンプルとしてデータを取得し、複数回コピーします
  • ローカルマシン上の特定の場所からデータを取得します

そして、私のルールとは異なる戦略も必要です:

  • 入力経由で渡されました
  • 高速実行スタブを使用します
  • プロダクションルールに基づいたサンプルを使用します
  • 私のマシンではこのパスを使用してください

これにどう対処しますか?そうですね、ユーザーにデータを提供してもらいましょう!

戦略のための API

json スキーマについて私がいつも注意を引いていたことを知っていますか?これはこちらです:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        //...
    }
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

これらのフィールドは $ で始まります。私の意見では、これらはメタデータを示すために使用されます。では、どの戦略が使用されているかのメタデータを示すために、これをデータ入力で使用してみてはいかがでしょうか?

{
    "dados": {
        "$strategy": "sample",
        "copias": 15000
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

たとえば、持っているデータをサンプルとして 15,000 部注文できます。または、Athena でクエリを作成して、S3 に何かをリクエストします:

{
    "dados": {
        "$strategy": "athena-query",
        "limit": 15000,
        "inicio": "2024-11-25",
        "fim": "2024-11-26",
        "cliente": "Abóbora"
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

それともローカルパスにありますか?

{
    "dados": {
        "$strategy": "localpath",
        "cwd": "/home/jeffque/random-project-file",
        "dir": "../payloads/esses-daqui/top10-hard/"
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

それで、今後の戦略の選択を私に任せることができます。

コードレビューとファサード

戦略に対処するための私の最初のアプローチは次のとおりでした:

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);
    }
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

そこで、私のアーキテクトはコードレビュー中に 2 つの質問をしました。

  • 「なぜすべてをインスタンス化し、Spring を機能させないのですか?」
  • 彼はコード内に DataLoaderFacade を作成しましたが、それを放棄しました 中途半端

このことから何が分かりましたか?ファサードを使用することは、処理を正しいコーナーに委任し、手動制御を放棄することをお勧めします?

そうですね、春のせいでたくさんの魔法が起こります。私たちは Java の専門知識を備えた Java ハウスにいるのですから、慣用的な Java/Spring を使用してみてはいかがでしょうか? 個人として、いくつかの事柄を理解するのが難しいと感じるからといって、必ずしもそれが複雑であることを意味するわけではありません。それでは、Java 依存関係注入の魔法の世界を受け入れてみましょう。

ファサード オブジェクトの作成

以前は:

final var dataLoader = getDataLoader(inputDados)
dataLoader.loadData(inputDados, workingPath);
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

次のようになりました:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        //...
    }
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

したがって、私の controller 層はこれを管理する必要はありません。ファサードまでお任せください

それでは、ファサードをどうするか?まず、すべてのオブジェクトをそれに注入する必要があります:

{
    "dados": {
        "$strategy": "sample",
        "copias": 15000
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

メインの DataLoader については、@Service に加えて @Primary として記述します。残りは @Service で書き留めるだけです。

ここでこれをテストし、Spring がコンストラクターをどのように呼び出しているかを試すためだけに getDataLoader を null を返すように設定して、うまくいきました。ここで、各サービスがどのような戦略を使用しているかをメタデータメモする必要があります...

これを行うには...

まあ、見てください! Java には アノテーション があります。そのコンポーネントで使用される戦略を含む ランタイム アノテーションを作成できます!

AthenaQueryDataLoader では次のようなものを作成できます。

{
    "dados": {
        "$strategy": "athena-query",
        "limit": 15000,
        "inicio": "2024-11-25",
        "fim": "2024-11-26",
        "cliente": "Abóbora"
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

エイリアスも持つことができます。そうしないのはなぜですか?

{
    "dados": {
        "$strategy": "localpath",
        "cwd": "/home/jeffque/random-project-file",
        "dir": "../payloads/esses-daqui/top10-hard/"
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

そして見せてください!

しかし、この注釈はどうやって作成するのでしょうか?そうですね、文字列のベクトルである属性を持たせる必要があります (Java コンパイラは、単独の文字列を提供し、それを位置 1 のベクトルに変換する処理をすでに処理しています)。デフォルト値は value です。次のようになります:

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);
    }
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

注釈フィールドが値でない場合は、それを明示的にする必要があり、EstrategiaFeia 注釈のように見苦しくなります。

final var dataLoader = getDataLoader(inputDados)
dataLoader.loadData(inputDados, workingPath);
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

私の意見では、それはあまり自然ではありません。

それでは、次のものがまだ必要です。

  • 渡されたオブジェクトからクラスのアノテーションを抽出します
  • 文字列マップを作成します rightarrow データローダー(または文字列) rightarrow と)

注釈の抽出とマップの組み立て

注釈を抽出するには、オブジェクト クラスにアクセスする必要があります。

dataLoaderFacade.loadData(inputDados, workingPath);
ログイン後にコピー
ログイン後にコピー

それに加えて、このクラスに Strategy:
のようなアノテーションが付けられているかどうかを尋ねてもいいですか?

@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...
    }
}
ログイン後にコピー
ログイン後にコピー

値フィールドがあることを覚えていますか?さて、このフィールドは文字列のベクトルを返します:

@Service
@Primary
@Estrategia("athena-query")
public class AthenaQueryDataLoader implements DataLoader {
    // ...
}
ログイン後にコピー

ショー!しかし、私には課題があります。以前は T 型のオブジェクトを持っていましたが、今はその同じオブジェクトを (T, String)[] にマップしたいと考えているからです。ストリームでは、これを行う古典的な操作は flatMap です。また、Java では、そのようなタプルを突然返すことはできませんが、それを使用してレコードを作成することはできます。

次のようになります:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        //...
    }
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

戦略の注釈が付けられていないオブジェクトがある場合はどうなりますか?それはNPEを与えるでしょうか?それはやめた方が良いので、NPE の前にフィルタリングして除外しましょう:

{
    "dados": {
        "$strategy": "sample",
        "copias": 15000
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

そう考えると、まだ地図をまとめる必要があります。そして、見てください。Java はすでにこのためのコレクターを提供しています。 Collector.toMap(keyMapper, valueMapper)

{
    "dados": {
        "$strategy": "athena-query",
        "limit": 15000,
        "inicio": "2024-11-25",
        "fim": "2024-11-26",
        "cliente": "Abóbora"
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここまではOK。しかし、私は flatMap が特に気になりました。 MapMulti と呼ばれる新しい Java API があり、これには次のような可能性が秘められています。

{
    "dados": {
        "$strategy": "localpath",
        "cwd": "/home/jeffque/random-project-file",
        "dir": "../payloads/esses-daqui/top10-hard/"
    },
    //...
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

美しさ。 DataLoader 用に取得しましたが、RuleLoader でも同じことを行う必要があります。それともそうではないでしょうか?お気づきかと思いますが、このコードには DataLoader に固有のものは何もありません。このコードを抽象化できます!!

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);
    }
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ファサードの下

純粋に実用的な理由から、私はこのアルゴリズムを注釈内に配置しました:

final var dataLoader = getDataLoader(inputDados)
dataLoader.loadData(inputDados, workingPath);
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

そしてファサードについては?まあ、仕事も同じことを言うのが良いです。私はこれを抽象化することにしました:

dataLoaderFacade.loadData(inputDados, workingPath);
ログイン後にコピー
ログイン後にコピー

ファサードは次のようになります:

@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...
    }
}
ログイン後にコピー
ログイン後にコピー

以上がJava でアノテーションを使用して戦略を立てるの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート