はじめに
AzureのサーバーレスAzure FunctionsとNoSQLストレージCosmos DBでAPIサーバを構築しました。
作ったもの → https://github.com/momotaro98/yarana-api
本記事ではその実装内容を記載します。
対象者
パブリッククラウドとしてAzureを採用している(しようとしている)人
APIサーバをサーバレスで構築しようとしている人
実装
APIを作っていきます。
Azure Functionsの実装では今回はC#のcsxスクリプトを採用しています。
本記事ではAzure Portal上でのAzure Functions構築方法は記載しません。以前に書いたAzure FunctionsでTable StorageをCRUDするまで手順という記事に記述しているので是非見てみてください。
本記事で扱うデータモデル&エンドポイント
以下のデータモデルを本記事では例として扱います。
{
"id":"mhlv43098tbsu7bgsc4voku398qx1410",
"userId":"Ufea0979d178607eb0483c4f9cmhd3d25",
"title":"筋トレ"
}
ID、ユーザーID、タイトル名の3項目あります。
また、以下の2つのエンドポイントを作ります。
POST
/koto
GET
/kotos?userId=string
データを登録する POST /koto
上記のデータモデルのJSONがPOSTでやってきた場合の処理です。Cosmos DBへデータを格納します。
Azure Functions上のソースコード
#r "Newtonsoft.Json"
using System.Net;
using Microsoft.Azure.WebJobs.Host;
using Newtonsoft.Json.Linq;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, IAsyncCollector<dynamic> kotoDocument, TraceWriter log)
{
dynamic data = await req.Content.ReadAsAsync<object>();
dynamic koto = JObject.Parse(data.ToString());
kotoDocument.AddAsync(koto);
return req.CreateResponse(HttpStatusCode.OK);
}
{
"bindings": [
{
"authLevel": "function",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"methods": [
"post"
],
"route": "koto"
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"type": "documentDB",
"name": "kotoDocument",
"databaseName": "Yarana",
"collectionName": "Kotos",
"createIfNotExists": false,
"connection": "yarana_DOCUMENTDB",
"direction": "out"
}
],
"disabled": false
}
コードはたった4行で済みます。
Cosmos DBのコレクションと紐付いたIAsyncCollector<dynamic>
型を利用することでPOSTでやって来るJSONをそのまま.AddAsync
メソッドでCosmos DBへ格納することができます。
Cosmos DBに登録されたデータを確認
ドキュメントが追加されています。
データを取得する GET /kotos?userId=string
上記で作成したモデルを含むデータを取得します。ここではユーザーIDをURLのuserId
クエリで指定し、そのユーザーIDを持つドキュメントを返す機能を実装します。
Azure Functions上のソースコード
#r "Newtonsoft.Json"
using System.Net;
using Newtonsoft.Json;
using System.Net.Http.Headers;
public static HttpResponseMessage Run(HttpRequestMessage req, IEnumerable<dynamic> documents, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
// Parse url query parameter
// ex. https://yarana-api.azurewebsites.net/api/kotos?userId=d59964bb713fd6f4f5ef6a7c7e029388
string userId = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "userId", true) == 0)
.Value;
if (userId == null)
return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a userId on the query string or in the request body");
log.Info($"userId: {userId.ToString()}");
// Query from documents in CosmosDB
IEnumerable<dynamic> docs = documents.Where(doc => doc.userId == userId);
// Create JSON to return
string responseJSON = JsonConvert.SerializeObject(docs);
log.Info($"JSON to response: {responseJSON}");
// Create response
var response = req.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(responseJSON);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
response.Content.Headers.ContentType.CharSet = "utf-8";
return response;
}
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"methods": [
"get"
],
"route": "kotos"
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"type": "documentDB",
"name": "documents",
"databaseName": "Yarana",
"collectionName": "Kotos",
"connection": "yarana_DOCUMENTDB",
"direction": "in"
}
],
"disabled": false
}
Cosmos DBからデータ取得する場合はコレクションと紐付いたIEnumerable<dynamic>
型を利用することでdocuments.Where(doc => doc.userId == userId)
のようにC#の文法でクエリできます。
GETで得られるJSONを確認
上記FunctionのURLへGETすると以下のようにデータが取得されます。
[
{
"id":"mhlv43098tbsu7bgsc4voku398qx1410",
"userId":"Ufea0979d178607eb0483c4f9cmhd3d25",
"title":"筋トレ","_rid":"a4EkALKyCQAUAAAAAAAAAA==",
"_self":"dbs/a4EkAA==/colls/a4EkALKyCQA=/docs/a4EkALKyCQAUAAAAAAAAAA==/",
"_etag":"\"00006b00-0000-0000-0000-5aad29c30000\"",
"_attachments":"attachments/",
"_ts":1521297859
},
{
"id":"l0wklkhyozgzg5m3095lmk6b8mf3tua6",
"userId":"Ufea0979d178607eb0483c4f9cmhd3d25",
"title":"英語","_rid":"a4EkALKyCQAVAAAAAAAAAA==",
"_self":"dbs/a4EkAA==/colls/a4EkALKyCQA=/docs/a4EkALKyCQAVAAAAAAAAAA==/",
"_etag":"\"00006c00-0000-0000-0000-5aad33a50000\"",
"_attachments":"attachments/",
"_ts":1521300389
}
]
_self
, _etag
, _attachments
, _ts
は、Cosmos DBが管理するメタデータです。上記のコードではこれらが含まれてしまいます。
これらを含めないようにするには、返したい要素のみを持つエンティティ(クラス)を定義し、そのエンティティのJSONを返すような実装にする必要があります。
【おまけ】 Swaggerが使えるAzure FunctionsのAPI Definition
Azure FunctionsではAPI Definitionという機能が提供されており、Swagger規格でAPIドキュメントを管理できます。
以下スクショ
Swagger UIが表示されます。
おわりに
サーバーレス技術はシンプルな機能を持つAPIを構築する場合、開発&運用のコストを抑える意味で力を発揮します。
また、NoSQLストレージを利用すればJSONをそのまま入れ込み、取得できるコードを書ける環境が整っているので実装が楽ちんです。