Dapr Advent Calendar 3日目 - Daprでサービスを呼ぶ
こんにちは、Dapr Advent Calendar 3日目です。ついに3日坊主達成です!
別のサービスを呼んでみよう
前回のエントリー ではDaprを使ってHello Worldアプリケーションを呼び出しましたが、今回はひとつのWebアプリケーションから、Dapr経由で別のWebアプリケーションを呼び出してみます。
https://github.com/cero-t/dapr-advent-2021
このソースコードの例ではMavenのマルチモジュール構成で、次のように作ってあります。
Daprを使わないWebアプリケーションの作成
まずはDaprを使わずに、呼び出される側と呼び出す側のWebアプリケーションをそれぞれ作成します。
呼び出される側のWebアプリケーションは、前回のHello World(helloモジュール)をそのまま使います。
(hello) HelloController.java
@RestController public class HelloController { @GetMapping("/hello") public Map<String, String> hello() { return Map.of("message", "Hello, world!"); } }
そして新たに別のWebアプリケーション(invokeモジュール)を作成します。上で作ったHello Worldのアプリケーションを呼び出すためのアプリケーションです。
(invoke) InvokeController.java
@RestController public class InvokeController { private RestTemplate restTemplate; public InvokeController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/invokeHello") public Map<String, ?> invokeHello() { Map<?, ?> result = restTemplate.getForObject("http://localhost:8080/hello", Map.class); return Map.of("remoteMessage", result); } }
helloアプリケーションの /hello
を呼び出すだけの簡単な処理です。
RestTemplateを使うため、ApplicationクラスでBean定義をしておきます。
(invoke) InvokeApplication.java
@SpringBootApplication public class InvokeApplication { public static void main(String[] args) { SpringApplication.run(InvokeApplication.class, args); } @Bean RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } }
Spring Bootの使い方みたいなところの説明は割愛しますね。
また、このアプリケーションをポート番号8081で起動するよう、設定ファイルでポート番号を指定します。
(invoke) application.properties
server.port=8081
これでアプリケーションは完成です。
まずはそれぞれのWebアプリケーションを別々のコンソールで起動します。
cd hello ../mvnw spring-boot:run
cd invoke ../mvnw spring-boot:run
アプリケーションが起動したらcurlコマンドでアクセスします。
curl localhost:8081/invokeHello
無事に実行結果が表示されました。
{"remoteMessage":{"message":"Hello, world!"}}
結果を見やすいようjqコマンドなどで整形するとこんな感じです(以降も基本的にjqで整形した結果を表示します)
{ "remoteMessage": { "message": "Hello, world!" } }
これで別サービスの呼び出しができました。
ただ、ソースコード内に相手側のアドレスやポート番号を書かなくてはならないという問題がありますね。これをDaprで解決しましょう。
URLを設定ファイルに移動させる
Daprを使ってアプリケーション起動する前に、ソースコードのURLの一部を設定ファイルに移動させます。
(invoke) InvokeController.java(抜粋)
@Value("${baseUrl}") private String baseUrl; @GetMapping("/invokeHello") public Map<String, ?> invokeHello() { Map<?, ?> result = restTemplate.getForObject(baseUrl + "/hello", Map.class); return Map.of("baseUrl", baseUrl, "remoteMessage", result); }
URL部分をbaseUrlの値で動的に変えられるようにしました。また、利用したbaseUrlの値を確認するために、レスポンスにbaseUrlを入れるようにしました。
baseUrlは application.properties
で定義します。
(invoke) application.properties
server.port=8081 baseUrl=http://localhost:8080
これでURLの一部を設定ファイルに移動させることができました。このURLをDapr向けに書き換えた設定ファイルも別に作っておきます。
(invoke) application-dapr.properties
baseUrl=http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method
DAPR_HTTP_PORT
は、Daprのポート番号を示しています。Dapr経由でアプリケーションを起動すると、この環境変数にDapr自身のポート番号が入ります。アプリケーションはこのポートを経由して、自身のサイドカーであるDaprプロセスのAPIを利用できるようになるのです。このポート番号を使い、前回説明したInvoke APIを使ってアプリケーションにアクセスするように記述しました。
念のため、この時点でアプリケーションの動作確認をしておきましょう。invokeアプリケーションをいったん停止してから再起動します。
../mvnw spring-boot:run
curlコマンドでアクセスします。
curl localhost:8081/invokeHello
実行結果にbaseUrlが入るようになりました。
{ "baseUrl": "http://localhost:8080", "remoteMessage": { "message": "Hello, world!" } }
これで準備が整いました。次はDaprを使ってアプリケーションを起動してみましょう。
Daprとともにアプリケーションを起動
まずはDaprを使ってhelloアプリケーションを起動します。
helloアプリケーションを停止させた後、次のコマンドで起動します。
dapr run --app-id hello-app --app-port 8080 --dapr-http-port 18080 ../mvnw spring-boot:run
続いて、invokeアプリケーションも同じように停止させたあと、次のコマンドで起動します。
dapr run --app-id invoke-app --app-port 8081 --dapr-http-port 18081 -- ../mvnw spring-boot:run -Dspring-boot.run.profiles=dapr
mvnwコマンドに -Dspring-boot.run.profiles=dapr
という引数を追加して、application-dapr.properties
の設定が有効になるようにしました。また引数を追加するためmvnwコマンドの前に --
を追加しました。
それではDapr経由でWebアプリケーションにアクセスしてみましょう。curlコマンドでアクセスします。
curl localhost:18081/v1.0/invoke/invoke-app/method/invokeHello
次のような結果が表示されるはずです。
{ "remoteMessage": { "message": "Hello, world!" }, "baseUrl": "http://localhost:18081/v1.0/invoke/hello-app/method" }
ふつうにアクセスした時と同じメッセージが取得でき、baseUrlはDaprのAPI形式になりました。
なおinvokeアプリケーションは、別にDaprを経由せずに呼び出しても構いません。
curl localhost:8081/invokeHello
上と同じ実行結果が得られます。
{ "remoteMessage": { "message": "Hello, world!" }, "baseUrl": "http://localhost:18081/v1.0/invoke/hello-app/method" }
これで、Dapr経由で2つのWebアプリケーションが連携できました。
あくまでも自身のサイドカーであるDaprと通信するだけで、別のDapr経由で起動しているアプリケーションに対してアクセスができた、ということです。
やったことの解説
今回は2つのアプリケーションを起動して、Dapr経由でアクセスすることができました。Daprがちょうどサービスディスカバリーやルーティングのような機能を有していると言えます。Spring CloudでいうEurekaやDiscovery Clientに相当する機能です。
この機能に関するドキュメントはこちらになります。
Dapr同士が協調して、Invoke APIに含まれるapp-id(今回は hello-app
)を名前解決して相手のアプリケーションを発見し、その相手のアプリケーションのサイドカーを経由してアプリケーションにアクセスする形となっています。
この名前解決には、ローカル環境ではマルチキャストDNSが利用され、k8s上ではk8sの名前解決が利用されるほか、Consulを利用することもできます。
この名前解決の機能を利用すれば、たとえばこのようなアクセスも可能です。
curl http://localhost:18080/v1.0/invoke/hello-app/method/hello curl http://localhost:18081/v1.0/invoke/hello-app/method/hello
Daprのポート番号 18080
と 18081
で異なっていますが、どちらのURLでアクセスしても、同じ結果が返ってきます。
{ "message": "Hello, world!" }
どのDaprにアクセスしても hello-app
という名前からアクセスすべきアプリケーションを探してアクセスを行うのです。Daprを使う時には「どのDaprにアクセスするか」はあまり関係なく、Daprネットワークにアクセスしている、という感覚が近いと言えるでしょう。
環境への依存を下げる
今回説明したサービス呼び出しに関連して、もう少し環境周りのパラメータを減らす方法を説明しておきます。
Daprのポート番号を指定せずに起動する
Daprの理解を深めるために、Daprのポート番号を指定せずにアプリケーションを起動するようにします。
helloアプリケーションを一度停止し、次のコマンドで起動します。--dapr-http-port
を指定しない形です。
dapr run --app-id hello-app --app-port 8080 ../mvnw spring-boot:run
invokeアプリケーションも一度停止して、同じように --dapr-http-port
を指定せずに起動します。
dapr run --app-id remote-call-app --app-port 8081 -- ../mvnw spring-boot:run -Dspring-boot.run.profiles=dapr
そしてcurlコマンドで直接invokeアプリケーションにアクセスします。
curl localhost:8081/invoke
これでも正常に実行結果が得られました。
{ "remoteMessage": { "message": "Hello, world!" }, "baseUrl": "http://localhost:65275/v1.0/invoke/hello-app/method" }
DaprのHTTPポート番号がランダムに決まったたとしても、環境変数 DAPR_HTTP_PORT
を使っているため自分のサイドカーとして起動しているDaprにアクセスできます。空いているポート番号を適当に使っても構わなくなるのは楽で良いですよね。
Daprの設定ファイルを使わずに起動する
ここまでの例では application-dapr.properties
を使っていましたが、これはアプリケーション側がDaprに依存している形にも見えます。このファイルを使わずに、コマンド引数で接続先を指定する形も試してみましょう。
次のコマンドで起動します。
dapr run --app-id remote-call-app --app-port 8081 -- ../mvnw spring-boot:run -Dspring-boot.run.arguments='--baseUrl=http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method'
baseUrl
をコマンド引数として渡すようにしました。このコマンド実行時点で ${DAPR_HTTP_PORT}
が解釈されてしまわないよう、引数はシングルクォートで囲っています。
これで起動したアプリケーションにcurlコマンドでアクセスすると
curl localhost:8081/invoke
正常に実行結果が得られました。
{ "remoteMessage": { "message": "Hello, world!" }, "baseUrl": "http://localhost:51939/v1.0/invoke/hello-app/method" }
このようにすればアプリケーション側のソースコードや設定ファイルからDaprに対する依存を避けることができます。Spring Bootが環境変数に応じて処理を切り替えやすい構造になっているおかげとも言えます。
もちろんコマンドライン引数でわざわざこんな長いURLを指定するのは面倒ですが、たとえばk8sのマニュフェストファイルで環境変数に指定する、というような状況であればこの形で指定することも妥当なやり方の一つになると思います。
まとめ
それでは今回の内容を簡単に振り返りましょう。
- Dapr経由でアプリケーションを複数起動すると、お互いに相手の名前を指定して通信することができます
- ローカル環境ではマルチキャストを使ってDapr同士が連携し、他のDaprを探しに行きます
- アプリケーションは
DAPR_HTTP_PORT
という環境変数で自分のサイドカーであるDaprのポート番号を認識します - 環境変数で処理を切り替えやすい構造にしていれば、より環境への依存を下げることができます
正直、僕はこの機能を見ただけでもDaprを使ってみようという気持ちになりました。他のサービスディスカバリーのサーバなしで名前解決できるのって、何気に嬉しいですよね。
それでは!