Dapr Advent Calendar 4日目 - Daprでデータストアにアクセス
こんにちは、Dapr Advent Calendar 4日目です。ここは "3日坊主の向こう側" です!
データストアの読み書きをしてみよう
今回はDaprを使ってデータストアへの読み書きを行います。
https://github.com/cero-t/dapr-advent-2021/tree/main/state
データを読み書きするアプリケーションの作成
まずはWebアプリケーションの作成です。DaprのState management APIを使い、データの書き込み処理と、読み込み処理をそれぞれ作成します。
StateController.java
@RestController public class StateController { private RestTemplate restTemplate; @Value("http://localhost:${DAPR_HTTP_PORT}/v1.0/state/statestore") private String stateUrl; public StateController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @PostMapping("/write") public void write(@RequestBody Object message) { restTemplate.postForObject(stateUrl, message, Void.class); } @GetMapping("/read/{key}") public Object read(@PathVariable String key) { return restTemplate.getForObject(stateUrl + "/" + key, Object.class); } }
writeメソッドは書き込み処理、readメソッドは読み込み処理ですが、DaprのState management APIをそのまま呼び出しているだけで、他に何もしてません。APIの詳細は後で説明します。
このアプリケーションをポート番号8082で起動するよう、設定ファイルでポート番号を指定します。
application.properties
server.port=8082
これでアプリケーションの作成は完了です。
Daprを使ったアプリケーションの起動
このアプリケーションはDaprのState management APIを活用する前提で実装しているため、Daprを使って起動します。
dapr run --app-id state-app --app-port 8082 ../mvnw spring-boot:run
起動したら、まずは書き込み用のAPIにJSONでメッセージを渡してデータの登録を行います。
curl -XPOST "localhost:8082/write" -H "Content-type:application/json" -d '[ { "key": "cero_t", "value": { "name": "Shin Tanimoto", "twitter": "@cero_t" } }, { "key": "BABYMETAL", "value": [ { "name": "SU-METAL", "alias": "Suzuka Nakamoto" }, { "name": "MOAMETAL", "alias": "Moa Kikuchi" }, { "name": "YUIMETAL", "alias": "Yui Mizuno" } ] } ]'
key
に登録するデータのキー、value
に登録する内容を指定しています。これがState management APIの仕様で、後ほど詳しく説明します。
続いて、読み込み用のAPIを使ってデータの取得を行います。
curl localhost:8082/read/cero_t
次のようなデータが取得できるはずです。
{ "name": "Shin Tanimoto", "twitter": "@cero_t" }
もう一つのデータも取得してみましょう。
curl localhost:8082/read/BABYMETAL
次のようなデータが取得できます。
[ { "alias": "Suzuka Nakamoto", "name": "SU-METAL" }, { "name": "MOAMETAL", "alias": "Moa Kikuchi" }, { "name": "YUIMETAL", "alias": "Yui Mizuno" } ]
問題なく取得できましたね。
この状態でアプリケーションを終了し、もう一度起動しても、問題なくデータは取得できます。データがきちんと保存されていることが分かりました。
このデータはどこに保存されて、どこから取ってきたのでしょうか。その辺りを次の項で説明します。
やったことの解説
それではコードや打ったコマンドなどについて説明します。
State management APIと設定ファイル
今回はDaprのState management APIにそのままリクエストを流しただけですから、このAPIについて説明します。
State management APIのドキュメントはここにあります。
DaprのState management APIのURLは http://localhost:${DAPR_HTTP_PORT}/v1.0/state/(データストア名)
となります。
上で作ったソースコードで、このように記述していましたが
@Value("http://localhost:${DAPR_HTTP_PORT}/v1.0/state/statestore")
データストア名を statestore
としています。もしこのストア名に異なる名前、たとえば my-store
などを指定すると、次のようなエラーとなります。
state store my-store is not found
なぜ statestore
と名前が決まっているのでしょうか。そして、これはどこのデータストアを指しているのでしょうか。
実はデータストアの設定は ~/.dapr/components/statestore.yaml
に記載されています。
~/.dapr/components/statestore.yaml
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: statestore spec: type: state.redis version: v1 metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" - name: actorStateStore value: "true"
dapr run
コマンドを実行してDaprを起動すると ~/.dapr/components
にある設定ファイルが読まれます。今回はそこにある statestore.yaml
が読み込まれたのです。
設定ファイルの仕様をよく知らなくても、なんとなくRedisを用いることと、localhost:6379を参照していることは分かると思います。また metadata.name
に記載された statestore
がデータストア名となります。
ここで指定されているRedisは、Dapr CLIで dapr init
した際に作成されたものです。docker ps
コマンドでRedisが起動していることを確認してみましょう。
docker ps --filter name=redis
Redisのコンテナが見つかるはずです。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES de8b1f054509 redis "docker-entrypoint.s…" 48 hours ago Up 48 hours 0.0.0.0:6379->6379/tcp dapr_redis
つまり、このRedisが6379番ポートで待ち受けており、それを使うよう ~/.dapr/components/statestore.yaml
に記載されており、dapr run
コマンド実行時にこのファイルが読み込まれて利用できるようになった、ということです。
ちなみに設定ファイルのパスは dapr run
コマンド実行時の引数に --components-path
を指定することで、任意のパスを指定することができます。それは後ほど試してみましょう。
データの登録と取得
データの登録と取得はState management APIを使いました。
登録のJSONは次のような形でした。
[ { "key": "cero_t", "value": { "name": "Shin Tanimoto", "twitter": "@cero_t" } }, { "key": "BABYMETAL", "value": [ { "name": "SU-METAL", "alias": "Suzuka Nakamoto" }, { "name": "MOAMETAL", "alias": "Moa Kikuchi" }, { "name": "YUIMETAL", "alias": "Yui Mizuno" } ] } ]'
配列を使って複数のデータを登録できます。
それぞれのデータは key
と value
という項目が必須で、key
が検索時にも使われるキーで、value
がメッセージ本体となります。メッセージ本体は任意の形式のJSONを指定することができます。一般的なスキーマレスのKVSと同じですね。
取得する際にはState management APIのパスに key
の値を渡せば1件ずつ取得できます。
{ "name": "Shin Tanimoto", "twitter": "@cero_t" }
他にも、複数のkeyを指定して取得するBulk APIや、削除するためのDelete API、またDapr v1.5ではフィルタリングをするためのQuery APIも追加されました(まだアルファ版です)
それぞれのAPIについてはAPIリファレンスを参考にしてください。
https://docs.dapr.io/reference/api/state_api/
異なるデータストアを使う
ここまでは、Daprが用意した設定ファイルとRedisを使ってデータの登録を行いましたが、他のデータストアを使うこともできます。
サポートされているデータストアの一覧はこちらに記載されています。
ここではPostgreSQLを使ってみることにします。
PostgreSQLの起動
まずはPosgreSQLを起動しましょう。手っ取り早くDockerを使って起動します。
docker run -d --name dapr_postgres -p 5432:5432 -e POSTGRES_PASSWORD=secretpassword postgres
ローカルホストの5432番ポートで待ち受けるようにしました。
設定ファイルの作成
続いて、このPostgreSQLに接続するための設定ファイルをcomponentsディレクトリに作成します。
state/components/posgresql-store.yaml
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: statestore spec: type: state.postgresql version: v1 metadata: - name: connectionString value: host=localhost user=postgres password=secretpassword port=5432 connect_timeout=10 database=postgres
上の方で作ったソースコード内で指定した statestore
の名前を変えなくて良いよう、ここでも metadata.name
には statestore
を使用しました。
そしてこの設定ファイルを使って起動するようDaprのコマンド引数に --components-path ./components
を追加します。
dapr run --app-id state-app --app-port 8082 --components-path ./components ../mvnw spring-boot:run
この時点で一度、PostgreSQLのデータがどうなっているか見に行ってみましょう。
起動中のdockerコンテナに入って
docker exec -it $(docker ps -q --filter name=dapr_postgres) /bin/bash
psqlコマンドでアクセスします。
psql -U postgres
テーブル一覧を参照します。
\d
結果がこうなるはずです。
List of relations Schema | Name | Type | Owner --------+-------+-------+---------- public | state | table | postgres
Daprが起動した時点で state
という名前のテーブルが作成されていました。
いちおうデータを参照してみると
select * from state;
もちろん空っぽです。
key | value | isbinary | insertdate | updatedate -----+-------+----------+------------+------------ (0 rows)
それでは別のコンソールでcurlコマンドを打って、データを登録してみましょう。
curl -XPOST "localhost:8082/write" -H "Content-type:application/json" -d '[ { "key": "cero_t", "value": { "name": "Shin Tanimoto", "twitter": "@cero_t" } }, { "key": "BABYMETAL", "value": [ { "name": "SU-METAL", "alias": "Suzuka Nakamoto" }, { "name": "MOAMETAL", "alias": "Moa Kikuchi" }, { "name": "YUIMETAL", "alias": "Yui Mizuno" } ] } ]'
その後、もう一度PostgreSQLのコンテナ内でデータを参照してみます。
select * from state;
今度はきちんとデータが登録されていました。
key | value | isbinary | insertdate | updatedate ----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+----------+---------- ---------------------+------------ state-app||cero_t | {"name": "Shin Tanimoto", "twitter": "@cero_t"} | f | 2021-11-3 0 22:05:34.759534+00 | state-app||BABYMETAL | [{"name": "SU-METAL", "alias": "Suzuka Nakamoto"}, {"name": "MOAMETAL", "alias": "Moa Kikuchi"}, {"name": "YUIMETAL", "alias": "Yui Mizuno"}] | f | 2021-11-3 0 22:05:34.760984+00 | (2 rows)
keyの部分だけ見てみると
key ---------------------- state-app||cero_t state-app||BABYMETAL
(app-id)||key
となっていることが分かり、app-idごとにデータが永続化されているということが分かりましたね。
え、なぜRedisではきちんと調べなかったのに、PostgreSQLではこんなにきちんと調べるか、ですって?
そりゃ僕がRedisを使ったことがなくて、コマンドを全然知らないからですよ。ガハハハハ。
まとめ
- State management APIを用いてデータの登録や取得ができます
- ローカル環境では
~/.dapr/components
にある設定ファイルが読み込まれます - Daprの初期化時に作成したRedisが利用されるように設定されています
- 設定ファイルの場所は
dapr run
コマンドの--components-path
で指定できます - PostgreSQLをはじめ、いくつかのRDBMSやNoSQLなどを同じAPIで利用できます
最後の最後でこういうことを言うのもアレなのですが、僕はこのState management APIを仕事で使ったこともなければ、使おうと思ったことすらありません。正直なところ、シンプルなCRUDで済むアプリケーションならまだしも、少しでも複雑な業務アプリケーションではこのAPIでは機能が全く足りないため、あまり使うメリットがないと判断しています。
ちょうどよいユースケースがあれば良いのですけどね。
それでは、また明日!