はじめに
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 を作成することができました。
また、ここでは紹介できませんでしたが、データ更新の際にストアドプロシージャを呼び出してバリデーションするといったこともできるみたいです。
他にも紹介できていない機能もありますので、気になった方は遊んでみてください!


