このエントリは以下のエントリをベースにしています。
This entry is based on the following one written by Paul Parkinson.
https://medium.com/oracledevs/long-running-actions-for-microprofile-on-helidon-data-integrity-for-microservices-2bd4d14fe955
マイクロサービスはデータの一貫性と整合性の問題を引き起こし、マイクロサービスが使用するトランザクション処理とデータパターンの変更が必要になります。
従来のシステムは、同期通信、リソースのロック、およびロールバックによる回復(または場合によってはコミット)を使用する2フェーズコミットおよびXAプロトコルに依存しています。これは強力な一貫性と分離を提供するものの、保持されたロックの遅延のためにマイクロサービス環境ではうまくスケーリングしないため、このようなユースケースのごく一部のサブセットにのみ適しています(通常、スループット要件が低いもの)。
saga patternは、非同期通信とローカルリソースのみ(分散ロックなし)、そして補正アクションを使ったリカバリを使用します。これは拡張性が高いため、マイクロサービス環境で長時間実行されるトランザクションに適しています。追加のアプリケーション設計の考慮事項が必要ですが、読み取りの分離と補償のロジックとデバッグが困難な場合があります。
MicroProfile LRA (Long Running Actions) とは以下のようなものです。
The specification introduces APIs for services to coordinate activities.
https://github.com/eclipse/microprofile-lra
The main thrust of the proposal introduces an API for loosely coupled services to coordinate long running activities in such a way as to guarantee a globally consistent outcome without the need to take locks on data.
(この仕様では、アクティビティを調整するサービスのAPIを導入します。
この提案の主な目的は、疎結合サービスがデータをロックせずにグローバルに一貫した結果を保証するような方法で、長期実行アクティビティを調整するためのAPIを導入することです。)
多くの点で、Long Running Activities(LRA)APIは、マイクロサービス間のSagaに対するものです。従来のアプリケーション(または単一のマイクロサービス内での利用)のJava Transaction API(JTA)が2フェーズコミットとXAに対するものと同等です。
このトピックについてはすでに多数の記事があるため、ここではHelidonでLRAを実行することに焦点を当てます。HelidonはLRA仕様のNarayana実装を利用しています。
Using LRA in Helidon MP
2個の主要なコンポーネントが関係しています。
- 参加者 (Participants): これらはLRAに関与するJAX-RSマイクロサービスで、標準のorg.eclipse.microprofile.lra.annotation.ws.rs.LRAの値で注釈がつけられています。選択できる値は下表の通りです。
Annotation | Description |
---|---|
@LRA | LRAのライフサイクルを管理 |
@Compensate | LRAをキャンセルした場合に呼び出されるメソッドを示す |
@Complete | LRAをクローズした場合に呼び出されるメソッドを示す |
@Forget | このLRAに割り当てられた任意のリソースをリリースできるメソッドを示す |
@Leave | このクラスがもはやこのLRAに関心がないことを示す |
@Status | この注釈が付いたメソッドを呼び出す場合、ステータスを報告する必要がある |
- LRAコーディネータ: これは、参加者が(LRAアノテーションの結果として)暗黙的に登録する特化したサービスであり、LRAの完了、補償などを実行する責任をもちます。
Update your LRA participant pom.xml(s)
LRA アノテーションとランタイムの依存関係を追加します。
<dependency> | |
<groupId>org.eclipse.microprofile.lra</groupId> | |
<artifactId>microprofile-lra-api</artifactId> | |
<version>1.0-RC1</version> | |
<scope>compile</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.jboss.narayana.rts</groupId> | |
<artifactId>lra-client</artifactId> | |
<version>${narayana.lra.version}</version> | |
<scope>runtime</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.jboss.narayana.rts</groupId> | |
<artifactId>narayana-lra</artifactId> | |
<version>${narayana.lra.version}</version> | |
</dependency> |
ランタイムには必要ありませんが、ビルド時のLRAアノテーションチェッカーを使用できます。
<plugin> | |
<groupId>org.jboss.narayana.rts</groupId> | |
<artifactId>lra-annotation-checker-maven-plugin</artifactId> | |
<version>${narayana.lra.version}</version> | |
<executions> | |
<execution> | |
<goals> | |
<goal>check</goal> | |
</goals> | |
</execution> | |
</executions> | |
</plugin> |
Add LRA feature in your application
Helidon MP アプリケーション (javax.ws.rs.core.Application) はすでに以下のような感じになっているはずなので、io.narayana.lra.filter.FilterRegistration の DynamicFeatureを含む行を追加します。
@Override | |
public Set<Class<?>> getClasses() { | |
Set<Class<?>> s = new HashSet<Class<?>>(); | |
s.add(FilterRegistration.class); | |
//... | |
return s; | |
} |
Annotate the endpoints
LRA アノテーションをマイクロサービスのエンドポイントに追加します。この例では、2個のサービス(order serviceとinventory service)がLRAに関与しています。
Order Service:
- @LRA(value = LRA.Type.RequiresNew)アノテーションがついているため、クライアントによるplaceOrderの呼び出しでLRAを開始し、LRAコーディネーターにサービスを登録。
- インベントリサービスを呼び出して、LRA idを持つLRA_HTTP_CONTEXT_HEADERを使用して在庫を確認(クライアントLRAフィルターを登録することで実現)。
- 在庫があれば(デフォルト)order serviceにsuccessを返し、暗黙のうちにLRAを完了し、コーディネータが@CompleteのアノテーションがついたメソッドのcompleteOrderを呼び出す。
- 在庫がない場合(inventory serviceのremoveInventoryエンドポイントを呼ぶことで実現)、order serviceにfailureを返し、LRAをキャンセル、コーディネータは@CompensateのアノテーションがついたメソッドのcancelOrderを呼び出す。
@Path("/placeOrder") | |
@GET | |
@Produces(MediaType.APPLICATION_JSON) | |
@LRA(value = LRA.Type.REQUIRES_NEW) | |
public Response placeOrder(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) { | |
//... | |
} | |
@Path("/cancelOrder") | |
@PUT | |
@Produces(MediaType.APPLICATION_JSON) | |
@Compensate | |
public Response cancelOrder(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) throws NotFoundException { | |
//... | |
} | |
@PUT | |
@Path("/completeOrder") | |
@Produces(MediaType.APPLICATION_JSON) | |
@Complete | |
public Response completeOrder(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) throws NotFoundException { | |
//... | |
} |
Inventory Service:
- @LRA(value = LRA.Type.Mandatary)というアノテーションがついているため、ororder serviceが開始、伝播したLRA内で、order serviceがreserveInventoryForOrderを実行し、LRAコーディネータにサービスを登録する。
- 在庫を確認し、その結果(あり・なし)をorder serviceに返す
@Path("/reserveInventoryForOrder") | |
@GET | |
@Produces(MediaType.APPLICATION_JSON) | |
@LRA(value = LRA.Type.MANDATORY, end = false) | |
public Response reserveInventoryForOrder(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) { | |
//... | |
if(inventoryExists) return Response.ok().entity("inventorysuccess").build(); | |
else return Response.ok().entity("inventoryfailure").build(); | |
} | |
@Path("/completeOrder") | |
@PUT | |
@Produces(MediaType.APPLICATION_JSON) | |
@Complete | |
public Response completeOrder(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) throws NotFoundException { | |
//... | |
} | |
@Path("/cancelOrder") | |
@PUT | |
@Produces(MediaType.APPLICATION_JSON) | |
@Compensate | |
public Response cancelOrder(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) throws NotFoundException { | |
//... | |
} |
Build and deploy the LRA coordinator
LRAコーディネータサービスのJARをビルド、実行するための依存関係を追加します。
<dependency> | |
<groupId>org.jboss.narayana.rts</groupId> | |
<artifactId>lra-coordinator</artifactId> | |
<version>${narayana.lra.version}</version> | |
<type>war</type> | |
<scope>runtime</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.jboss.narayana.rts</groupId> | |
<artifactId>lra-service-base</artifactId> | |
<version>${narayana.lra.version}</version> | |
<scope>runtime</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.jboss.narayana.arjunacore</groupId> | |
<artifactId>txoj</artifactId> | |
<version>${narayana.lra.version}</version> | |
<scope>runtime</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.glassfish.jersey.media</groupId> | |
<artifactId>jersey-media-json-binding</artifactId> | |
<scope>runtime</scope> | |
<exclusions> | |
<exclusion> | |
<groupId>jakarta.json</groupId> | |
<artifactId>jakarta.json-api</artifactId> | |
</exclusion> | |
</exclusions> | |
</dependency> |
Build and start the LRA app
./build.sh
サンプルスクリプトを実行してorder serviceとinventory serviceというparticipantサービス、そしてコーディネータをパッケージングします。
サービスを開始します。
java -jar lra-coordinator-helidon/target/lra-coordinator-helidon-0.0.1-SNAPSHOT.jar | |
java -jar order/target/order-0.0.1-SNAPSHOT.jar | |
java -jar inventory/target/inventory-0.0.1-SNAPSHOT.jar |
Run the LRA app for the success case
curl http://localhost:8091/inventory/removeInventory | |
curl http://localhost:8090/order/placeOrder |
order serviceからの出力に着目してください。
OrderResource.placeOrder in LRA due to LRA.Type.REQUIRES_NEW lraId:[...]
OrderResource.placeOrder response from inventory:inventorysuccess
OrderResource.completeOrder
inventory serviceからの出力に着目してください。
InventoryResource.addInventory
InventoryResource.placeOrder in LRA due to LRA.Type.MANDATORY lraID:[...]
InventoryResource.completeOrder prepare item for shipping lraId:[...]
Run the LRA app for the compensating case
order serviceからの出力に着目してください。
OrderResource.placeOrder in LRA due to LRA.Type.REQUIRES_NEW lraId:[...]
OrderResource.placeOrder response from inventory:inventoryfailure
OrderResource.cancelOrder
inventory serviceからの出力に着目してください。
InventoryResource.removeInventory
InventoryResource.placeOrder in LRA due to LRA.Type.MANDATORY lraID:[...]
InventoryResource.cancelOrder put inventory back lraId:[...]
What next?
LRA 仕様は現時点で88%完成しており、次期MicroProfileのリリースに含まれることが期待されています。進捗は以下からご覧いただけます。
microprofile-lra
https://github.com/eclipse/microprofile-lra/milestone/1
HelidonはLRAならびにLRAに関わるマイクロサービスデータパターンの他の領域を継続して促進していきます。
- Equivalent Helidon SE support
- Messaging (and event sourcing) features
- HA configurations
- Database integration features
サンプルコードは以下からどうぞ。
Helidon Examples of Long Running Actions
https://github.com/paulparkinson/helidon-examples-lra