はじめに
API Managementは、API を簡単に管理できるサービスですが、REST API だけでなく GraphQL の API も作成できることができます。
API Management では、これまで既存の REST API をデータソースとして GraphQL の API を作成することができましたが、Cosmos DB と Azure SQL Database をデータソースとして使用できるようになりました。(※プレビュー段階)
これらのデータソースを使用することで、別途バックエンドを用意せずにデータベースを直接 API として公開することができます。
前回の記事では、Azure SQL Database のデータソースを利用しましたが、この記事では Cosmos DB のデータソースを使って、API Management で GraphQL の API を作成してみたいと思います。
Azure SQL Database 版
前提
- API Management のリソースが作成済みであること。
- この記事では API Management のリソース作成手順については説明しません。
- 価格レベルは何でも良いと思いますが、執筆時は従量課金レベルで検証しています。
- Cosmos DB のリソースが作成済みであること。
- この記事では Cosmos DB のリソース作成手順については説明しません。
-
Azure CLI がインストール済みであること。
- この記事では Azure CLI のインストール手順については説明しません。
事前準備
基本的にはドキュメントに従って進めていきます。
API Management のマネージド ID を有効にする
※SQL Database 版と同様となります。
Cosmos DB のロールの割り当て
ターミナルで以下のコマンドを実行し、マネージド ID に Cosmos DB のロールを付与します。
resourceGroupName="<リソースグループ名>"
cosmosName="<Cosmos DB のリソース名>"
apimName="<API Management のリソース名>"
# API Management のプリンシパルID を取得する
apimPrincipal=$(az apim show \
--resource-group $resourceGroupName \
--name $apimName \
--query "identity.principalId" \
--output tsv)
# Cosmos DB のリソースID を取得する
cosmosdbId=$(
az cosmosdb show \
--resource-group $resourceGroupName \
--name $cosmosName \
--query id \
--output tsv
)
# API Management の マネージド ID に Cosmos DB のロールを付与する
# ここでは「Cosmos DB Built-in Data Contributor」を付与
az cosmosdb sql role assignment create \
--resource-group $resourceGroupName \
--account-name $cosmosName \
--role-definition-name "Cosmos DB Built-in Data Contributor" \
--principal-id $apimPrincipal \
--scope $cosmosdbId
データベースとコンテナの作成
適当なデータベースとコンテナを作成し、適当なデータを投入します。
API の作成
Schema ファイルの作成
GraphQL API を作成する際に Schema ファイルを選択する必要があるため、事前に作成します。
今回は以下のように作成しました。
type Query {
getUser(id: ID!): User!
getUsers(): UserList!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type User {
id: ID!
name: String!
age: Int!
}
type UserList {
items: [User!]!
hasNextPage: Boolean!
endCursor: String
}
input UpdateUserInput {
id: ID!
name: String!
age: Int!
}
input CreateUserInput {
id: ID!
name: String!
age: Int!
}
GraphQL API の追加
※SQL Database 版と同様となります。
Resolver の作成
作成した API が一覧に表示されるので、クリックすると先ほど選択した Schema が見られます。
更に行番号の左にある+
をクリックすると、Resolver の登録画面に行くことができます。
Resolver の登録画面で Data source にAzure Cosmos DB
を設定し、Resolver policy(後述)を入力して Create をクリック。
※Schema から ジャンプした場合は Name、Type、Field は自動的に入力されています
ポリシーの設定
接続設定(共通)
<cosmosdb-data-source>
<!-- 接続設定 -->
<connection-info>
<connection-string use-managed-identity="true">
AccountEndpoint=https://<Cosmos DB リソース名>.documents.azure.com:443/;
</connection-string>
<database-name>demo-db</database-name>
<container-name>users</container-name>
</connection-info>
<!-- 省略 -->
</cosmosdb-data-source>
マネージドIDで接続するため、connection-string
の属性にuse-managed-identity="true"
を指定します。
database-name
に Cosmos DB のデータベース名を指定します。
container-name
に Cosmos DB のコンテナ名を指定します。
単一結果の取得
単一データを取得する場合は以下の様なポリシーとなります。
(getUser)
<!-- getUser -->
<cosmosdb-data-source>
<!-- 接続設定は省略 -->
<read-request>
<id>
@(context.GraphQL.Arguments["id"].Value<string>())
</id>
<partition-key>
@(context.GraphQL.Arguments["id"].Value<string>())
</partition-key>
</read-request>
</cosmosdb-data-source>
read-request
要素を設定します。
SQL Database とは異なり、こちらはクエリを書かずにキー(id
、partition-key
)を指定するだけで OK です。
id
、partition-key
ではリクエストパラメータを参照することができます。
参照するには ポリシー式 を指定します。
context.GraphQL.Arguments
にリクエスト時に指定した引数が格納されています。
複数データの取得
複数行の結果を取得する場合は以下の様なポリシーとなります。
(getUsers)
<!-- getUsers -->
<cosmosdb-data-source>
<!-- 接続設定は省略 -->
<query-request>
<sql-statement>
SELECT * FROM c
</sql-statement>
</query-request>
</cosmosdb-data-source>
結果セットが複数行の場合は、SQL Database の場合と同様sql-statement
要素にクエリを記述します。
また、こちらも SQL Database と同様parameters
に指定したパラメータをsql-statement
内で使用することができます。
<!-- getUsers -->
<cosmosdb-data-source>
<!-- 接続設定は省略 -->
<query-request>
<sql-statement>
- SELECT * FROM c
+ SELECT * FROM c WHERE CONTAINS(c.name, @name)
</sql-statement>
+ <parameters>
+ <parameter name="@name">@(context.GraphQL.Arguments["name"])</parameter>
+ </parameters>
</query-request>
</cosmosdb-data-source>
更新系
更新系の場合は以下の様なポリシーとなります。
(createUserの場合)
<!-- createUser -->
<cosmosdb-data-source>
<!-- 接続設定は省略 -->
<write-request type="upsert">
<partition-key>
@(context.GraphQL.Arguments["input"]["id"].Value<string>())
</partition-key>
<set-body template="liquid">
{
"id":"{{body.arguments.input.id}}",
"name":"{{body.arguments.input.name}}",
"age":{{body.arguments.input.age}}
}
</set-body>
</write-request>
</cosmosdb-data-source>
write-request
要素を設定します。
単一データの取得と同様、クエリを書かずにキー(partition-key
)を指定します。
逆に言うと、クエリによる更新はそもそも Cosmos DB ではサポートされていないため、できません。
write-request
要素のtype
属性で更新の種類を指定します。
insert
、replace
、upsert
が指定可能です。
2023/09/03 時点
試してみたところ、insert
を指定してもupsert
と同じ挙動だったり、ドキュメントにはreplace
を指定した場合はid
要素が必須となっているけど、そもそもid
要素が指定できなかったりとまだ怪しい感じでした
set-body
要素で入力するデータ(JSON)を組み立てます。
SQL Database の方でも登場しましたが、Liquid というテンプレート構文で記述します。
削除
削除の場合は以下の様なポリシーとなります。
(deleteUser)
<!-- deleteUser -->
<cosmosdb-data-source>
<!-- 接続設定は省略 -->
<delete-request>
<id>
@(context.GraphQL.Arguments["id"].Value<string>())
</id>
<partition-key>
@(context.GraphQL.Arguments["id"].Value<string>())
</partition-key>
</delete-request>
</cosmosdb-data-source>
delete-request
要素を設定します。
read-request
と同様、キー(id
、partition-key
)を指定します。
API のテスト
テスト方法は SQL Database 版と同様です。
おまけ
ページング
Cosmos DB では、クエリによる複数データを取得する際、ページングを制御することができます。
Resolver ポリシー
<!-- getUsers -->
<cosmosdb-data-source>
<!-- 接続設定は省略 -->
<query-request>
<sql-statement>
SELECT * FROM c
</sql-statement>
+ <paging>
+ <max-item-count>
+ @(context.GraphQL.Arguments["input"]["maxItemCount"]?.Value<string>())
+ </max-item-count>
+ <continuation-token>
+ @(context.GraphQL.Arguments["input"]["continuationToken"]?.Value<string>())
+ </continuation-token>
+ </paging>
</query-request>
</cosmosdb-data-source>
paging
要素を設定します。
max-item-count
要素で1ページの取得件数を指定します。(デフォルトは 100)
continuation-token
要素で次ページを特定するためのトークン(後述)を指定します。
max-item-count
、continuation-token
共にポリシー式で記述することができます。
※max-item-count
は文字列として設定しないとエラーになりました
<max-item-count>
<!-- ↓はエラー -->
@(context.GraphQL.Arguments["input"]["maxItemCount"]?.Value<int>())
</max-item-count>
Schema
Schema は以下のように変更しています。
type Query {
- getUser(id: ID!): User!
+ getUsers(input: GetUserInput!): UserList!
getUsers(): UserList!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type User {
id: ID!
name: String!
age: Int!
}
type UserList {
items: [User!]!
hasNextPage: Boolean!
endCursor: String
}
+input GetUserInput {
+ maxItemCount: Int
+ continuationToken: String
+}
"・・・省略・・・"
実行結果
最初はmax-item-count
を指定せずに実行。
全部で 3 件取得できました。
レスポンスを見てみるとendCursor
とhasNextPage
というプロパティがあります。
次にmax-item-count
に 2 を指定してみます。
結果が 2 件のみとなりました。
また、endCursor
とhasNextPage
の値が先ほどと変わりましたね。
全体の件数がmax-item-count
を超えている場合はhasNextPage
がtrue
となり、endCursor
に次ページを特定するためのトークンがセットされます。
では、continuation-token
に上記のトークンを指定してみます。
先ほど取得されなかった残りの 1 件が取得できました。
その他
レスポンスのカスタマイズや、オプショナルなパラメータを指定する方法は SQL Database 版と同様、こちらでも使えます!
おわりに
API Management と データベースだけで GraphQL の API を作成することができました。
また、ここでは紹介できませんでしたが、データ更新の際にストアドプロシージャを呼び出してバリデーションするといったこともできるみたいです。
他にも紹介できていない機能もありますので、気になった方は遊んでみてください!