谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

Dapr Advent Calendar 4日目 - Daprでデータストアにアクセス

こんにちは、Dapr Advent Calendar 4日目です。ここは "3日坊主の向こう側" です!

データストアの読み書きをしてみよう

今回はDaprを使ってデータストアへの読み書きを行います。

f:id:cero-t:20211203231211p:plain
今回作るアプリケーション

ソースコードgithubに置いてあります。

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

起動したら、まずは書き込み用のAPIJSONでメッセージを渡してデータの登録を行います。

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"
  }
]

問題なく取得できましたね。

この状態でアプリケーションを終了し、もう一度起動しても、問題なくデータは取得できます。データがきちんと保存されていることが分かりました。

f:id:cero-t:20211203231226p:plain
Daprを経由したアクセス

このデータはどこに保存されて、どこから取ってきたのでしょうか。その辺りを次の項で説明します。

やったことの解説

それではコードや打ったコマンドなどについて説明します。

State management APIと設定ファイル

今回はDaprのState management APIにそのままリクエストを流しただけですから、このAPIについて説明します。

State management APIのドキュメントはここにあります。

docs.dapr.io

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 CLIdapr 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"
      }
    ]
  }
]'

配列を使って複数のデータを登録できます。

それぞれのデータは keyvalue という項目が必須で、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を使ってデータの登録を行いましたが、他のデータストアを使うこともできます。

サポートされているデータストアの一覧はこちらに記載されています。

docs.dapr.io

ここでは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では機能が全く足りないため、あまり使うメリットがないと判断しています。

ちょうどよいユースケースがあれば良いのですけどね。

それでは、また明日!