Rental Management System(RMS)はHelidonを使ってマイクロサービスやMicroProfileの利用法や効果を確認することを目的としたリファレンス的なアプリケーションです。このリポジトリはRMSの説明や親pom、GitHub Actionsの共通的なワークフローなどRMS全体で共通となる情報や定義を格納したものになります
RMSは会員がレンタル品を予約するアプリケーションで管理機能としてマスターデータをメンテナンスする機能を持っている。なお、実装している機能は予約まででレンタルを行う機能はまだ持っていない
分類 | 機能 | 内容 |
---|---|---|
会員機能 | レンタル品検索 | レンタル品を検索し予約状況の確認ができる |
レンタル品予約 | 予約したいレンタル品を選択しレンタル期間を指定した予約ができる | |
予約確認 | 自分の予約を確認したりキャンセルしたりすることができる | |
管理機能 | レンタル品管理 | レンタル品の登録や更新などを行うことができる |
予約管理 | 登録されたレンタル予約の削除や変更を行うことができる | |
ユーザ管理 | ユーザの登録や更新、削除などを行うことができる | |
共通機能 | ログイン/ログアウト | システムへのログインとログアウト |
ユーザプロファイル | ユーザが自分の登録情報を確認、変更ができる |
💁 INFO
手っ取り早く動くものを見たい方はこちらのデモアプリをお試を
RMSは次のアプリケーションから構成されるマイクロサービスアーキテクチャになっている
DBはh2のインメモリデータベースを使ったDatabase per Service構成にしている
要素 | 説明 |
---|---|
ApiGateway | 主に以下の役割を担う - フロントエンドに対するFacade - フロントエンドとバックエンドのデータモデルの変換 - ユーザ認証と後段サービスへの認証情報の伝播 |
RentalItemService | レンタル品を管理するサービス |
ReservationService | 予約を管理するサービス |
UserService | ユーザを管理するサービス |
SPAクライアント | ReactによるSPAのUIアプリ |
CLIクライアント | JavaSEによるコンソールUIアプリ |
なお、SPA,CLIのどちらでもレンタル品の予約や管理を行うことができるが、CLIアプリはSPAアプリよりもできることが少なくなっている。
サンプル的な側面があるため小さいサービスでマイクロサービスを構成しているが、実業務でこのような小粒度のサービスをマイクロサービス化することは勧めない。マイクロサービスアーキテクチャで得られるメリットよりもデメリットの方が遥かに大きくなる可能性が高い
RMSを構成する各アプリケーションやライブラリは次のようにマルチレポ形式で管理している
repository | 説明 |
---|---|
msa-rms-parent | 親pomや共通的なGitHub Actionsのワークフローなどの共通定義を格納しているこのリポジトリ |
msa-rms-platform | サービスに依らない基盤的な仕組みを提供する。このリポジトリ内だけはMavenのマルチモジュールによるモノレポ形式になっている。詳細はレポジトリのREADMEを参照 |
msa-rms-apigateway | ApiGatewayを格納するリポジトリ |
msa-rms-service-item | RentalItemServiceを格納するリポジトリ |
msa-rms-service-reservation | ReservationServiceを格納するリポジトリ |
msa-rms-service-user | UserServiceを格納するリポジトリ |
msa-rms-ui-console | RMSのコンソールアプリを格納するリポジトリ |
ReactアプリのrepositoryにはApiGatewayのソースコードからMicroProfile OpenAPIで出力したOAS情報(openapi.yml)をもとにOpenAPI Generatorで自動生成したAPI Clientのrepositoryも含まれる。このrepository間の関係は次のとおり
repository | 説明 |
---|---|
rms-ui-react | ReactによるrmsののSPAフロントエンドアプリのリポジトリ |
rms-generated-client-js | OpenAPI Generatorで生成したAPI Clientコードを格納するリポジトリ |
msa-rms-apigateway | openapi.ymlを出力するアプリのリポジトリ |
RMSはローカルでも動作するがAWS上の次の構成をアプリのターゲット環境としている。なお、CI/CDにはGitHub ActionsをContainerRegistryにはGitHub Packagesを使っている。
サービスを稼働させるコンテナ環境は次のとおり
サービス | 稼働環境 |
---|---|
ApiGateway | EC2(Installed Docker) |
RentalItemService | ECS(Fargate) |
ReservationService | ECS(Fargate) |
UserService | ECS(Fargate) |
データを管理するサービスはPrivate subnetに配置することのみを必須とし、その他はデモ環境でもあるため、コストを最優先の構成にしている。よって、敢えてシングルAZ構成で高額なALBも利用していない
💁 INFO
実際のアプリのお試しはこちらからどうぞ
ℹ️ 関連記事
・1日50円で使えるマイクロサービスなアプリを動かすAWS環境を作ってみた | 豆蔵デベロッパーサイト
上述のとおり、CI/CD環境はすべてGitHubのサービスでまかなっている。
- JavaのコンパイルからコンテナイメージのContainerRegistryへのpushまでの操作はすべてMavenで行いってる
- コンテナイメージのbuildからpushの定義は親pomにdocker-maven-pluginを使って行っている
- コンテナイメージのtagはGitHub Actionのbuild-to-repo-job.ymlでgitのコミットハッシュをつけている
- EC2のDockerコンテナ上で稼働するApiGatewayへのデプロイはdeploy-to-ec2-job.ymlでビルドしたコミットハッシュのコンテナイメージを起動するように書き換えたシェルスクリプトをAWS CodeDeployでEC2にデプロイし、コンテナを再起動している
- Fargate上の各サービスのデプロイはdeploy-to-ecs-job.ymlでAWS CLIでコンテナのtagをコミットハッシュに書き換えた新しいリビジョンのタスク定義を作成し、サービスを更新することで再デプロイを行っている
ℹ️ 関連記事
・今さら聞けないMaven – コンテナのビルドと一緒にpushもMavenでしたい。 | 豆蔵デベロッパーサイト
RMSで利用、採用している主なものは下記のとおり
- ランタイム系
- Java17(OpenJDK v17.0.3)
- MicroProfile 5.0
- Helidon MP 3.2.x
- H2 Database
- Text-IO 3.4.1 -> コンソールアプリ向けのframework
- テスト系
- JUnit 5.7
- ArchUnit 0.23
- Helidon MP Testing with JUnit5
ApiGatwayやReservationServiceなどのバックエンドアプリはいずれも次に示すdomainレイヤをリラックスレイヤにした一般的なレイヤーアーキテクチャを採用している
- webapi・・レンタル予約システムアプリをRESTで外部に公開する
- Serivce・・レンタル予約システムのアプリケーション実装
- domain・・エンティティや制約
- external・・RESTによるアプリ間連携を行う
- persistence・・データの永続化
- platform-fw・・アプリ間で共通となる制御構造や処理の流れを実装したRMS向けフレームワーク
- platform-core・・処理形態および業務依らない基盤的な仕組み
レイヤ間やパッケージやライブラリへの依存関係のルールはArchUnitの実装で細かく定義している
📌 POINT
Persistenceレイヤは設定を切り替えるだけでFileによる永続化とJPAを使ったRBDへの永続化のどちらでも使えるようにしている
ℹ️ 関連記事
・ArchUnitで考えるアーキテクチャ構造とその検証 | 豆蔵デベロッパーサイト
MicroProfileの機能を使って実現しているRMSアプリケーションの主な仕組みは以下のとおり
モジュールごとにデフォルト値を設定した設定ファイルをそれぞれ持ち、実行環境ごとに定義が必要な設定はMicroProfile Configの優先度の仕組みを使い、環境変数で設定を上書きするようにしている。
各モジュールが持つMicroProfile Configの設定ファイルと優先度は次のとおり
モジュール | 設定ファイル | 優先度 | 内容 |
---|---|---|---|
(環境変数) | (docker_run) | 300 | rms.* で定義している設定キーは実行環境ごとに環境変数で上書きされることを期待している |
apigateway | application.yaml | 100 | apigateway向けの設定を定義 |
item-service | application.yaml | 100 | item-service向けの設定を定義 |
reservation-service | application.yaml | 100 | reservation-service向けの設定を定義 |
user-service | application.yaml | 100 | user-service向けの設定を定義 |
console-ui | application.yaml | 100 | console-ui向けの設定を定義 |
platform-fw | application.yaml | 50 | fwが持つ機能のデフォルト動作などを定義 |
platform-core | application.yaml | 30 | coreが持つ機能のデフォルト動作などを定義 |
パスフレーズなどの機密情報はターゲット環境のセキュアな領域に配置された定義を読み込むようにしている。なお、MicroProfile ConfigのConfig Profile機能はYAMLが使えないなど使いづらい部分があるため使用していない
ℹ️ 関連記事
・お手軽便利MicroProfile Config | 豆蔵デベロッパーサイト
・MicroProfile Config 3.0へのキャッチアップ | 豆蔵デベロッパーサイト
RMSでは次の箇所でMicroProfile Rest Clientの機能を使っている
- REST呼び出し(RestClinetインタフェース)
- 例えばApiGatwayからReservationServiceの呼び出しはReservationApiRestClientで、ReservationServiceからRentalItemServiceへの呼び出しはRentalItemCheckApiRestClientで行っている
- 他についてもRMSではMicroProfile Rest ClientのRestClientインタフェースを使って対向サービスへのREST呼び出しを行っている
- REST呼び出しにおけるHTTPヘッダの設定(ClientHeadersFactory)
- ApiGatwayと各サービス間は閉域ネットワークのため、ApiGatewayから各サービスに伝播する認証情報はMicroProfile Rest ClientのClientHeadersFactoryを実装したPropagateLoginUserClientHeadersFactoryでHTTPヘッダーに平文で設定を行っている
- Console-UIはサーバーで発行されたIDトークン(JWT)を常にAuthorizationヘッダに設定する必要があるがこれについても、ClientHeadersFactoryを実装したPropagateJwtClientHeadersFactoryで行っている
- REST呼び出しに対する例外ハンドリング(ResponseExceptionMapper)
- Responseのステータスコードが400番台や500番台のエラーを示している場合、アプリにはそのエラーに応じた例外を返却するようにMicroProfile Rest ClientのResponseExceptionMapperを使い、PropagateResponseExceptionMapperでResponseに対するエラーハンドリングを一元的に行っている
ℹ️ 関連記事
・らくらくMicroProfile RestClient | 豆蔵デベロッパーサイト
・MicroProfile RestClient 3.0の確認と小技機能の紹介 | 豆蔵デベロッパーサイト
RMSではMicroProfile OpenAPIを使いソースコードをもとにOAS(openapi.yml)を生成するスキーマファーストではなく、ソースコードを起点にしたボトムアップアプローチを採っている
具体的にはアプリケーションに固有なAPI情報はアプリのRESTインタフェースにMicroProfile OpenAPIのアノテーションを使いOAS情報を定義している。このアノテーションが使われているアプリのインタフェースは次のとおり
これに対しエラーステータスなどアプリで共通となる情報はplatform-fwのCommonOpenApiModelReaderでMicroProfile OpenAPIのプログラムAPIを使いコードでOAS情報を定義し、それを各アプリが参照する形にしている
また、OASで扱う共通的な型情報については、platform-coreのapplication.yamlでMicroProfile OpenAPIの設定機能で行っている
MicroProfile OpenAPIは/openapi
のリクエストに対しソースコードの情報をもとにリアルタイムでOAS情報(openapi.yml)を生成してレスポンスとして返す。このため、OpenAPI Generatorでopenapi.ymlファイルをもとにコード生成を行う場合、あらかじめ/openapi
のレスポンスをファイルに出力しておく必要がある。
RMSでは/openapi
を呼び出してファイル出力するGenerateOasFileTestを用意し、このテストクラスを使ってopenapi.ymlを生成するようにしている
Helidonの独自機能となるがOpenAPI UI機能を使うことで、/openapi
のレスポンスをこちらのようなswagger-uiで簡単にみることができる
ℹ️ 関連記事
・コードが仕様の源泉MicroProfile OpenAPI | 豆蔵デベロッパーサイト
・MicroProfile OpenAPI 3.0の新機能と既存機能の比較 | 豆蔵デベロッパーサイト
RMSではID/Passwordによる独自のログイン機能を用意し、ログイン成功時にJWTを発行し、クライアントは以降のリクエストではAuthorizationヘッダのbearerトークンに発行されたJWTを設定する。これに対しサーバーサイドは認証配下のリクエストに対し常にJWT認証を行う
RMSではこのJWT認証にMicroProfile JWT Authの機能を使って実現している。MicroProfile JWT Authを利用する上で必要な実装はなく、ApiGatewayApplicationのように認証配下とするRESTリソースを管理するJAX-RSのApplicationクラスに@LoginConfig(authMethod = "MP-JWT")
を付けるだけとなる
また、MicroProfile JWT Authとは関係ないが、ログイン成功時にJWTを発行する仕組みはplatform-coreのjwtパッケージに、そして発行されたJWTをクライアントに伝播させる仕組みはplatform-fwのloginパッケージに実装している
JWTを生成する処理はAuth0 java-jwtを使ったAuth0RsaJwtGeneratorとjose4jを使ったJose4jRsaJwtGeneratorを用意している
ℹ️ 関連記事
・基本から理解するJWTとJWT認証の仕組み | 豆蔵デベロッパーサイト
・MicroProfile JWT Authがやってくれること・できること | 豆蔵デベロッパーサイト
・Auth0 java-jwtを使った素のJWT認証 | 豆蔵デベロッパーサイト
・続・Auth0 java-jwtを使った素のJWT認証 - 公開鍵方式でやってみた | 豆蔵デベロッパーサイト
RMSではコンテナに対する独自のヘルスチェックコマンドとして、MicroProfile HealthのReadiness Probeを実装している
DBを持つサービスにはping sqlを発行し、DBの死活状態を確認するDbReadinessCheckを、そしてApiGatewayのように他のサービスへ連携を行うサービスについては、設定で定義されたすべての連携先に対してReadiness Probe(/health/ready
)を行いすべてOKなら自身のステータスもOKとするReadinessOfOutboundServersHealthCheckを用意している
ℹ️ 関連記事
・MicroProfile Healthの機能と利用 | 豆蔵デベロッパーサイト
RMSでの利用はなし
(MicroProfile Metricsがデフォルトで取集するメトリクスに対するGrafanaの素敵なダッシュボードがないため。誰か作って・・)
ℹ️ 関連記事
・MicroProfile Metricsの機能と利用 | 豆蔵デベロッパーサイト
RMSではMicroProfile OpenTracingとJaegerで分散トレース情報の収集と参照を行っている
MicroProfile OpenTracingの利用は簡単で、必要なライブラリをアプリケーションに組み込むだけで利用可能となる。よって、通常の利用方法において必要となる実装はない
ただし、RMSでは一部非同期によるREST呼び出しを行っている箇所があり、この箇所におけるトレース情報の伝播がMicroProfile OpenTracingのデフォルトの機能では行われないため、MicroProfile OpenTracingのAPIを使って、マニュアルでトレース情報を対向システムに伝播するようにしている。この実装はReservationGwServiceのcomposeModelメソッドで行っている
なお、RMSの実際の分散トレースはこちらから参照することができる
ℹ️ 関連記事
・MicroProfile OpenTracingとJaegerで理解する分散トレーシング | 豆蔵デベロッパーサイト
RMSでは、REST連携する対向システムがダウンしている際に未処理のリクエストが溜まり連鎖的に障害が発生することを防ぐため、RentalItemApiProxyなどREST連携を行う箇所でサーキットブレーカーを下記のように設定している
@NetworkConnectionErrorAware
@CircuitBreaker(
requestVolumeThreshold = 4,
failureRatio=0.5,
delay = 10000,
successThreshold = 3,
failOn = RmsNetworkConnectionException.class)
public class RentalItemApiProxy implements RentalItemApi {..
この設定内容は次のとおり
@NetworkConnectionErrorAware
によりネットワークエラーはRmsNetworkConnectionException
に変換されるRmsNetworkConnectionException
を失敗としてカウントし、それ以外の例外が発生しても失敗にはカウントしない- 直近処理した4件の失敗率が0.5、つまり2件以上であった場合にサーキットブレーカーをopenにし、以降10秒間のメソッド呼び出しに対しては
CircuitBreakerOpenException
を送出する - 10秒経過後、3回連続して処理が成功した場合、つまり
CircuitBreakerOpenException
以外が返却された場合に正常状態に復帰する - 3回連続する前に処理が1度でも失敗した場合は、3.に戻り同様の制御を繰り返し行う
ℹ️ 関連記事
・MicroProfile Fault Tolerance(1) - 例で理解する基本機能編 | 豆蔵デベロッパーサイト
・MicroProfile Fault Tolerance(2) - 例で理解する非同期編 | 豆蔵デベロッパーサイト
・MicroProfile Fault Tolerance(3) - 例で理解する設定編 | 豆蔵デベロッパーサイト