48 多言語Coherenceアプリケーションの開発
この機能を使用するには、Oracle GraalVM Enterprise EditionでOracle Coherenceを実行する必要があります。GraalVM Enterprise EditionでのOracle WebLogic ServerおよびCoherenceの実行を参照してください。
ノート:
この章の例をビルドして実行するには、Maven 3.8.6以上がインストールされている必要があります。
ノート:
GraalVM多言語ランタイムは現在仮想スレッドをサポートしていないため、多言語機能を使用するキャッシュ・サービスに対して仮想スレッドが有効になっていないことを確認してください。
この章の内容は次のとおりです。
- プロジェクトの設定
Coherenceの多言語機能を示すために、Coherenceマップを移入する単純なアプリケーションを作成し、JavaScriptを使用して、作成したマップ・エントリにアクセスして処理します。 - JavaScriptクラスの実装
ここでは、Coherenceマップのエントリへのアクセスおよび更新に使用できるJavaScriptクラスを実装します。 - 依存関係のインストールおよび使用
この項では、JavaScriptクラスを実装する際にサード・パーティの依存関係を使用する方法を説明します。これが、そもそもJavaScriptを使用する理由であることがよくあります。 - プロジェクトのビルド
JavaScriptクラスを実行時にCoherence多言語フレームワークで使用できるようにするには、JARファイルにパッケージ化する必要があります。 - JavaアプリケーションでのJavaScriptクラスの使用
この項では、以前に作成したJavaScriptクラスを使用するJavaアプリケーションを記述して、多言語の例を完了します。
親トピック: 多言語Coherenceアプリケーションの開発および実行
プロジェクトの設定
すべてのCoherence記憶域メンバーにデプロイされるサーバー側クラスのMaven Javaプロジェクトがすでにある場合は、src/main/js
ディレクトリを作成して、その中にJavaScriptプロジェクトを作成できます。
src/main/js
ディレクトリの下にJavaScriptプロジェクトを作成する必要があります:
- Mavenプロジェクト・ディレクトリに移動します。
cd coherence-js-app export PRJ_DIR=`pwd` mkdir -p src/main/js cd src/main/js export JS_DIR=`pwd`
このメイン・プロジェクト・ディレクトリは、シェル環境変数
${PRJ_DIR}
で参照できるようになりました。JavaScriptプロジェクト・ディレクトリは、${JS_DIR}
環境変数を使用して参照できます。 - JavaScriptプロジェクト・ディレクトリで、次のように実行します:
npm init -y
このコマンドを実行すると、
package.json
ファイルが作成されます。このファイルは後で編集する必要があります。 - 他のモジュールに定義されているすべてのJavaScriptクラスをエクスポートするために使用されるアグリゲータ・モジュール
main.mjs
を作成します。echo > main.mjs
- 新しく作成した
main.mjs
モジュールを指すように、package.json
内のメイン属性を変更します。{ "name": "coherence-js-app", "version": "1.0.0", "main": "main.mjs", … }
親トピック: 多言語Coherenceアプリケーションの開発
JavaScriptクラスの実装
firstName
、lastName
、age
、gender
などの属性を持つPerson
オブジェクトが移入されているとします。public class Person { private String firstName; private String lastName; private int age; private String gender; // constructors and accessors omitted for brevity }
これで、問合せ、集計および更新を可能にするJavaScriptクラスを実装できます。
この項には次のトピックが含まれます:
フィルタの使用
NamedMap
には、エントリのキーに基づいてエントリにアクセスできるget()
およびput()
メソッドがあります。ただし、多くの場合、キー以外の属性に基づいてエントリを取得する必要があります。Coherenceでは、このような状況に対応するFilter
インタフェースを定義しています。
たとえば、10代、つまり13-19
歳の年齢層に属する人をすべて見つけるとします。これを実装する方法の1つは、すべてのエントリを取得して、年齢が13から19の間のエントリのみを選択することですが、この方法は非効率的です。Coherenceは、述語を記憶域ノードに移動する際に役立ちます。これにより、大量のデータ移動が回避されるだけでなく、記憶域ノードに対する述語のパラレル実行も可能になります。
Coherenceでは、記憶域ノードのエントリにアクセスするための組込みフィルタがいくつか提供されます。「キャッシュ内のデータの問合せ」を参照してください。
この項には次のトピックが含まれます:
フィルタ・インタフェースの使用
Filter
インタフェースでは、1つのメソッドevaluate(object)
が定義されます。このメソッドは、評価するオブジェクトを引数とし、指定されたオブジェクトがフィルタによって定義された条件を満たす場合はtrue
を返し、指定されたオブジェクトがフィルタによって定義された条件を満たさない場合はfalse
を返します。
- 1つの引数を取る
evaluate
というメソッドを含むクラスを定義してエクスポートする必要があります。 obj
がフィルタで表される条件を満たす場合、evaluate(obj)
はtrue
を返す必要があります。それ以外の場合は、false
を返す必要があります。
親トピック: フィルタの使用
フィルタの実装
その人が10代であるかどうかを確認するフィルタを作成するには、次を実行します。
ディレクトリを${JS_DIR}/src/main/js
に変更します。filters.mjs
ファイルを作成し、次の内容を追加します:
export class IsTeen { // [1] evaluate(person) { // [2] return person.age >= 13 && person.age <= 19; } }
- [1]
IsTeen
というクラスを定義し、エクスポートします。 - [2] 1つのパラメータを取る
evaluate
というメソッドを定義します。このメソッドは、age
属性が13から19の間にあるかどうかをチェックします。
最後に、filters.mjs
モジュールに定義されているすべてのクラスをアグリゲータ・モジュールmain.mjs
から再エクスポートします:
export * from "./filters.mjs"
親トピック: フィルタの使用
EntryProcessorsの使用
Coherenceには、キャッシュ・エントリに対してパラレル更新を実行するのに役立つ組込みのEntryProcessors
が用意されています。「エントリ・プロセッサ・エージェントの概要」を参照してください。
すべてのlastName
属性が大文字になるように、(この章で使用している例のようにPerson
オブジェクトで占められた)キャッシュ・エントリを更新する場合、その方法の1つは、すべてのエントリを取得し、反復処理して1つずつ更新し、最終的にキャッシュに書き戻すことです。
これは非効率的な方法です。Coherenceでは、データが存在する処理ロジックをより効率的に送るアプローチが用意されているため、データ移動の必要性がなくなります。キャッシュ・エントリのパラレル更新をより効率的に実行する必要がある場合は、EntryProcessor
を使用する必要があります。
この項には次のトピックが含まれます:
EntryProcessorインタフェースの使用
EntryProcessor
は、処理するマップ・エントリを引数とし、処理結果を返すprocess(entry)
という必須メソッドを定義します。JavaScriptでEntryProcessor
を実装するには、クラスが次のようになっている必要があります。
- 1つの引数(マップ・エントリ)を取る
process
という名前のメソッドを含む、エクスポートするクラスを定義します。 process(entry)
メソッドでマップ・エントリの値を変更する場合は、エントリを明示的に更新する必要があります。
親トピック: EntryProcessorsの使用
EntryProcessorの実装
lastName
を大文字の値に更新するEntryProcessor
を記述するには、processors.mjs
ファイルを作成し、次の内容を追加します:
export class UpperCaseProcessor { // [1] process(entry) { // [2] let person = entry.value; person.lastName = person.lastName.toUpperCase(); // [3] entry.value = person; // [4] return person.lastName; // [5] } }
- [1]
UpperCaseProcessor
というクラスを定義し、エクスポートします。 - [2] 1つのパラメータを取る
process()
というメソッドを定義します。処理が必要なNamedMap
エントリは、このメソッドに渡されるentry
引数を通じてアクセス可能です。 - [3] その人の
lastName
を大文字に変換します。 - [4]
entry
を新しい値で更新します。 - [5] 更新された(大文字の)姓をプロセッサの実行結果として返します。
processors.mjs
モジュールに定義されているすべてのクラスをアグリゲータ・モジュール main.mjs
から再エクスポートします:export * from "./ processors.mjs"
親トピック: EntryProcessorsの使用
Aggregatorの使用
Coherence Aggregator
を使用して、特定の基準に基づいて、キャッシュから単一の集計結果を取得できます。たとえば、前述の例で作成したCache
内の最も古いPerson
のキーを取得します。すべてのエントリを取得し、最も年齢の高いPerson
を見つけることもできますが、大量のデータをクライアントに移動することになります。
Coherenceでは、部分的な結果を計算し、それらの部分的な結果を結合して単一の集計結果を取得できるAggregator
インタフェースを定義しています。データ・グリッドの集計の実行を参照してください。
この項には次のトピックが含まれます:
Aggregatorインタフェースの使用
Aggregator
インタフェースでは、次のメソッドを実装する必要があります:
accumulate(entry):
すべてのメンバーに対してパラレルに実行し、単一エントリをそのメンバーの部分的な結果に累計します。このメソッドでは、entry
の1つ以上の属性を使用して部分的な結果が計算されます。このメソッドは、特定のクラスタ・メンバーでの集計用に選択されたエントリごとに1回ずつ、Aggregator
に対して複数回コールされます。getPartialResult():
各メンバーからのパラレル集計の部分的な結果を返します。combine(partialResult):
各クラスタ・メンバーから返された部分的な結果を最終結果に結合します。このメソッドでは、entry
の1つ以上の属性を使用して部分的な結果が計算されます。このメソッドは、各クラスタ・メンバーの部分的な結果ごとに1回ずつ、ルートAggregator
インスタンスに対して複数回コールされます。finalizeResult():
集計の最終結果を計算し、返します。
親トピック: Aggregatorの使用
Aggregatorの記述
最も古いPerson
のキーを返すAggregator
を定義するには、main.js
ファイルを作成して、次の内容を追加します:
export class OldestPerson { constructor() { this.key = -1; this.age = 0; } accumulate(entry) { // Compare this entry's age with the result computed so far. if (entry.value.age > this.age) { this.key = entry.key; this.age = entry.value.age; } return true; } getPartialResult() { // Return the partial result accumulated / computed so far. return JSON.stringify({key:this.key, age:this.age}); } combine(partialResult) { // Compute a (possibly) new result from the previously computed // partial result. let p = JSON.parse(partialResult); if (p.age > this.age) { this.key = p.key; this.age = p.age; } return true; } finalizeResult() { // Return the final computed result. return this.key; } }
最後に、Aggregator
モジュールmain.mjs
から定義されたクラスを再エクスポートします:
export * from "./aggregators.mjs"
親トピック: Aggregatorの使用
依存関係のインストールおよび使用
npm install
を使用する標準アプローチを使用して依存関係をインストールし、import
文の機能を使用してそれらを使用できます。たとえば、lodash-es
および@paravano/utils
パッケージをインストールするには、次のコマンドを使用します:
cd ${JS_DIR} npm install -s lodash-es npm install -s @paravano/utils
依存関係のインストール後、lodash-es
パッケージのnow
関数と@paravano/utils
パッケージのcamel
関数を使用するエントリ・プロセッサを実装できます:
import {now} from 'lodash-es'; import {camel} from "@paravano/utils"; export class CamelCase { process(entry) { console.log(`> CamelCase: entry=${entry} time=${now()}`) entry.value = camel(entry.value); return entry.value; } }
親トピック: 多言語Coherenceアプリケーションの開発
プロジェクトのビルド
JavaScriptクラスを実行時にCoherence多言語フレームワークで使用できるようにするには、JARファイルにパッケージ化する必要があります。
Coherence多言語フレームワークは、クラス・パスの任意のscripts/js
ディレクトリから.mjs
拡張子を持つすべてのスクリプトを自動的にロードします。そのため、最も単純なシナリオでは、外部依存関係がない場合、すべてのソース・ファイルをターゲット・ディレクトリ内の正しい場所にコピーするようMavenプロジェクトを構成するだけです。これにより、それらのファイルが他のすべてのクラスおよびリソースとともに最終JARにパッケージ化されます:
<build> <resources> <resource> <directory>${project.basedir}/src/main/resources</directory> </resource> <resource> <directory>${project.basedir}/src/main/js</directory> <targetPath>${project.build.outputDirectory}/scripts/js</targetPath> <includes> <include>**/*.mjs</include> </includes> </resource> </resources> </build>
これを行う場合は、デフォルト・リソースも再定義する必要があります。そうすると、src/main/resources
のすべてのファイルも、通常どおり出力ディレクトリにコピーされます。
ただし、外部依存関係がある場合(多くの場合その可能性が高い)、ソース・コードと依存する外部依存関係のコードの両方を出力ディレクトリにバンドルするには、バンドラを使用する必要があります。
このタスクで推奨されるバンドラはesbuildです。これは、高速で使いやすいためです。次に、これをインストールして使用し、JavaScriptプロジェクトを出力ディレクトリにバンドルします:
- esbuildをインストールします。
npm install –-save-dev esbuild
package.json
内のビルド・スクリプトを構成して、メイン・モジュールに対してesbuildを実行します。{ "name": "coherence-js-app", "version": "1.0.0", "main": "main.mjs", "scripts": { "build": "esbuild main.mjs --bundle --format=esm --charset=utf8 --outdir=../../../target/classes/scripts/js/ --out-extension:.js=.mjs" }, "devDependencies": { "esbuild": "^0.24.0" } }
- Node.jsおよびnpmをローカルにインストールします。
npm install
を実行して依存関係をインストールします。- 前に定義した
npm run build
コマンドを使用してesbuildを実行して、スクリプトをバンドルします。
pom.xml
ファイルに追加します:<plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <executions> <execution> <id>install node and npm</id> <goals> <goal>install-node-and-npm</goal> </goals> </execution> <execution> <id>npm install</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>install</arguments> </configuration> </execution> <execution> <id>npm run build</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>run build</arguments> </configuration> </execution> </executions> <configuration> <nodeVersion>v20.17.0</nodeVersion> <installDirectory>target</installDirectory> <workingDirectory>src/main/js</workingDirectory> </configuration> </plugin>
プロジェクト・ディレクトリでmvn install
を実行すると、Node.jsおよびnpmがインストールされ、スクリプトとその依存関係がesbuildを使用して1つのtarget/classes/scripts/js/main.mjs
ファイルにバンドルされていることがわかります。
これで、Javaアプリケーションでそのモジュールに定義され、そのモジュールからエクスポートされたクラスを使用する準備ができました。
親トピック: 多言語Coherenceアプリケーションの開発
JavaアプリケーションでのJavaScriptクラスの使用
この項には次のトピックが含まれます:
ランタイム依存関係の追加
pom.xml
ファイルに追加する必要があります:<dependencies> <dependency> <groupId>com.oracle.coherence</groupId> <artifactId>coherence</artifactId> <version14.1.2-0-0</version> </dependency> <!-- GraalVM Polyglot support --> <dependency> <groupId>org.graalvm.polyglot</groupId> <artifactId>polyglot</artifactId> <version>23.1.4</version> </dependency> <dependency> <groupId>org.graalvm.js</groupId> <artifactId>js-language</artifactId> <version>23.1.4</version> </dependency> </dependencies>
Javaアプリケーションの実装
これで、Coherenceクラスタを起動し、ピープル・マップにいくつかのエントリを移入し、前に作成したJavaScriptクラスを使用して、ピープル・マップのエントリを問い合せて変更するJavaアプリケーションを実装する準備ができました。
次のコードを含むMain.java
というファイルをメイン・プロジェクト内に作成します:
package com.oracle.coherence.example.js; import com.tangosol.net.Coherence; import com.tangosol.net.NamedMap; import com.tangosol.util.Aggregators; import com.tangosol.util.Filters; import com.tangosol.util.Processors; import com.tangosol.util.filter.AlwaysFilter; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { System.setProperty("coherence.log.level", "1"); try (Coherence coherence = Coherence.clusterMember().start().join()) { NamedMap<Integer, Person> people = coherence.getSession().getMap("people"); populatePeople(people); displayAllTeens(people); convertLastNameToUppercase(people); displayOldestManAndWoman(people); } } private static void populatePeople(NamedMap<Integer, Person> people) { printHeader("populatePeople"); Map<Integer, Person> map = new HashMap<>(); map.put(1, new Person("Ashley", "Jackson", "F", 84)); map.put(2, new Person("John", "Campbell", "M", 36)); map.put(3, new Person("Jeffry", "Trayton", "M", 95)); map.put(4, new Person("Florence", "Campbell", "F", 35)); map.put(5, new Person("Kevin", "Kelvin", "M", 15)); map.put(6, new Person("Jane", "Doe", "F", 17)); people.putAll(map); print(people); } private static void displayAllTeens(NamedMap<Integer, Person> people) { } private static void convertLastNameToUppercase(NamedMap<Integer, Person> people) { } private static void displayOldestManAndWoman(NamedMap<Integer, Person> people) { } private static void printHeader(String header) { System.out.println(); System.out.println(header); System.out.println("-".repeat(header.length())); } private static void print(NamedMap<Integer, Person> map) { print(map.entrySet(AlwaysFilter.INSTANCE)); } private static void print(Set<Map.Entry<Integer, Person>> entrySet) { TreeMap<Integer, Person> map = new TreeMap<>(); for (Map.Entry<Integer, Person> e : entrySet) { map.put(e.getKey(), e.getValue()); } for (Map.Entry<Integer, Person> e : map.entrySet()) { System.out.printf("%d: %s%n", e.getKey(), e.getValue()); } } }
JavaからのJavaScriptオブジェクトの起動
最後のステップとして、空の本体を使用して前の3つのメソッドを実装する必要があります。これには、前に実装したJavaScriptクラスを使用します。
この項には次のトピックが含まれます:
JavaScriptフィルタの起動
JavaScriptで記述されたFilter
は、com.tangosol.util.Filters
ユーティリティ・クラスでscript()
メソッドをコールすることでインスタンス化できます。次のように定義されます。
public static <V> Filter<V> script(String language, String filterName, Object... args);
最初の引数は、スクリプトが実装されている言語を指定するために使用されます。JavaScriptの場合は、js
を使用します。2番目の引数はFilter
のexported
名を指定するために使用し、最後の引数(オプション)はフィルタにコンストラクタ引数(ある場合)を渡すために使用できます。
Main.java
に次のメソッドを追加します:
private static void displayAllTeens(NamedMap<Integer, Person> people) { printHeader("displayAllTeens"); print(people.entrySet(Filters.script("js", "IsTeen"))); }
親トピック: JavaからのJavaScriptオブジェクトの起動
JavaScript EntryProcessorを使用したパラレル更新の実行
JavaScriptで記述されたEntryProcessor
は、com.tangosol.util.Processors
クラスのscript()
メソッドを呼び出すことでインスタンス化できます。
Main.java
に次のメソッドを追加します:
private static void convertLastNameToUppercase( NamedMap<Integer, Person> people) { printHeader("convertLastNameToUppercase"); people.invokeAll(Processors.script("js", "UpperCaseProcessor")); print(people); }
親トピック: JavaからのJavaScriptオブジェクトの起動
JavaScriptアグリゲータの実行
JavaScriptで記述されたAggregator
は、com.tangosol.util.Aggregators
クラスのscript()
メソッドを呼び出すことでインスタンス化できます。
OldestPerson
アグリゲータを組込みフィルタとともに使用して、最も年齢の高い男性と女性を見つけます。
Main.java
に次を追加します:
private static void displayOldestManAndWoman(NamedMap<Integer, Person> people) { printHeader("displayOldestManAndWoman"); Integer oldestManId = people.aggregate( Filters.equal(Person::getGender, "M"), Aggregators.script("js", "OldestPerson")); Integer oldestWomanId = people.aggregate( Filters.equal(Person::getGender, "F"), Aggregators.script("js", "OldestPerson")); System.out.printf("%d: %s%n", oldestManId, people.get(oldestManId)); System.out.printf("%d: %s%n", oldestWomanId, people.get(oldestWomanId)); }
これで、Javaアプリケーションを実行できます。すべてが正しく実装されている場合は、次の出力が表示されます:
Oracle Coherence Version 14.1.2.0.0 (dev-aseovic) Build 0
Grid Edition: Development mode
Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
populatePeople
--------------
1: Person[lastName=Jackson, firstName=Ashley, age=84, gender=F]
2: Person[lastName=Campbell, firstName=John, age=36, gender=M]
3: Person[lastName=Trayton, firstName=Jeffry, age=95, gender=M]
4: Person[lastName=Campbell, firstName=Florence, age=35, gender=F]
5: Person[lastName=Kelvin, firstName=Kevin, age=15, gender=M]
6: Person[lastName=Doe, firstName=Jane, age=17, gender=F]
displayAllTeens
---------------
5: Person[lastName=Kelvin, firstName=Kevin, age=15, gender=M]
6: Person[lastName=Doe, firstName=Jane, age=17, gender=F]
convertLastNameToUppercase
--------------------------
1: Person[lastName=JACKSON, firstName=Ashley, age=84, gender=F]
2: Person[lastName=CAMPBELL, firstName=John, age=36, gender=M]
3: Person[lastName=TRAYTON, firstName=Jeffry, age=95, gender=M]
4: Person[lastName=CAMPBELL, firstName=Florence, age=35, gender=F]
5: Person[lastName=KELVIN, firstName=Kevin, age=15, gender=M]
6: Person[lastName=DOE, firstName=Jane, age=17, gender=F]
displayOldestManAndWoman
------------------------
3: Person[lastName=TRAYTON, firstName=Jeffry, age=95, gender=M]
1: Person[lastName=JACKSON, firstName=Ashley, age=84, gender=F]
問題が発生した場合は、この章のプロジェクト例をGitHubで確認できます。
親トピック: JavaからのJavaScriptオブジェクトの起動