SendGridのEventデータをDocumentDBに突っ込む


少し前からSendGridのイベントデータの突っ込み先としてAzure DocumentDBが使えないか調べていたところ、しばやんさんがこんなツイートしているのを見かけました。たまたまだったのですが、ちょうど突っ込む部分ができたのでGitHubに上げておきました。

SendGridのイベントデータはEvent Webhookという機能を使って取得します。全てのイベントデータを自由に扱うために不可欠な重要な機能なのですが、取れるデータがスキーマレスのJSON形式のデータなので、いわゆるRDBに突っ込もうと思うとそれなりにスキーマの調整に手間がかかります。

こういうデータの保存先としては、MongoDB(特にMongoLab最強)やTreasure Dataなんかが相性がいいわけですが、ちょっと前にAzureでDocumentDBというサービスが追加されたとのこと、ちょっと出遅れた感はありましたが、丁度良い機会だったので試してみました。

DocumentDBのガイドをみると興味深い特徴がいくつもあります。
  • スキーマレス
  • JSON
  • REST API
  • トランザクション
  • ストアドプロシージャ
  • トリガ
  • SQLクエリ
MongoDBっぽい使い方ができて、MongoDBがカバーしていないところをカバーしようとしている雰囲気が伺えます。いかにもMS様っぽい登場のしかたですね。
パッと見、この特徴だけ見るとSendGridのイベントデータをREST APIを使ってそのまま突っ込めばトランザクションも効いてトリガも使えて最強かっ!!と期待したのですが、残念ながらそんなに話はうまくいかなかったので、その辺中心にポイントとなる箇所をまとめてみます。

配列を受け付けてくれないREST API

REST APIを見て気になったのが、データを生成する際は配列を渡せないこと。
Create a Documentでは一つずつ渡すインターフェイスなんですね。
SendGrid側は基本的にイベントデータを配列で渡してくるので、REST APIでデータを突っ込もうと思ったらこのAPIを配列長分繰り返し呼ばなくてはいけません。パフォーマンス的に無理があるというのは直感的に想像がつきます。

認証に一手間必要なREST API

REST APIでアクセスする場合、タイムスタンプとアクセス先のリソースタイプに応じたハッシュを生成する必要があります。MongoDBのREST APIとかだと、基本認証程度の簡単な認証でアクセスできるので、これに比べると圧倒的にめんど(ry じゃなくて、高いセキュリティを確保できて安心ですます。
当初、RubyからREST APIを叩く予定だったので、JavaScriptのサンプルコードと同じ処理をわざわざRubyで実装してみましたが、結局この後のトランザクションの制限でREST APIにメリットを感じなくなったので使いませんでした。

トランザクションはストアドプロシージャ経由で

じゃあ、トランザクションはどうやったら使えるんだ?ということになるわけですが、ストアドプロシージャ(とトリガーも?)から使えるようです。で、ストアドはJavaScriptとして実装してそいつを突っ込んでおくと呼び出せるようです。なるほど。

private static async Task TryRefleshStoredProcedure(string colSelfLink)
{
 string path = HttpContext.Current.Server.MapPath("~/App_Data/BulkImport.js");
 string body = File.ReadAllText(path);
 StoredProcedure sproc = new StoredProcedure
 {
  Id = "BulkImport",
  Body = body
 };

 await TryDeleteStoredProcedure(colSelfLink, sproc.Id);
 sproc = await client.CreateStoredProcedureAsync(colSelfLink, sproc);
 return sproc;
}

ちゃんと神サンプルありました。すばらしい。
配列データを突っ込んで、もし途中でエラーが起きてもロールバックしてくれて、SendGrid側にエラー(2xx以外)を返すとSendGrid側の再送機構も使えるわけです。これで完璧です。

データには一意なidが必須

いよいよデータを突っ込むことになるわけですが、受け取ったデータをそのまま突っ込んでみると「idが無いよー」ってエラーが発生。どうやら、idが必須、しかも一意制約があるようです。
SendGridのイベントデータにはそんなものはないので、一意となるidを自前で生成して追加した上で突っ込むことにしました。とりあえず、GUIDとかでいいかな、ということで。
MongoDBだとこの辺勝手になんとかしてくれるわけですが、DocumentDBはどういう意図でこういう設計にしているんでしょうかね?パフォーマンス?ちょっと興味あります。

public async Task RunBulkImport(string srcJson)
{
 List dstJsonList = new List();
 List srcJsonList = JsonConvert.DeserializeObject>(srcJson);
 foreach (dynamic json in srcJsonList)
 {
  Guid guidValue = Guid.NewGuid();
  json["id"] = guidValue.ToString();
  dstJsonList.Add(json);
 }
 StoredProcedureResponse scriptResult = await client.ExecuteStoredProcedureAsync(sproc.SelfLink, dstJsonList);
 Console.WriteLine("Finish insert: {0}", scriptResult.Response);
}

Ruby SDKはまだない

今のところRuby SDKはないようです。そもそも帝国ではRubyはアウェイ感があります。GoAzure 2015にMatzと池澤さんが来ていたのでそんな状況もそろそろ解消されるんでしょうか。

さいごに

データを突っ込むところはちゃんと動きました。パフォーマンスとかトリガーとかあまり深く触れていないけどとりあえずイケそうです。


願わくばREST APIが配列をそのまま受け付けてさえくれて、idを自動生成してくれたら最強になりそうだなー。

あと、どうでもいい話ですが、今回.NET Framework 1.1以来で久々にASP.NETを触りました。色々様変わりしてて、さながら小さいころ初恋だった当時クラスでNo.1の女の子に40歳過ぎて久々に会ったら既に3人の子持ちで、明るくたくましく家庭を切り盛りするお母さん的な何かを感じました。
ちょっと自分で言ってて意味がよくわかりませんが、元気そうでなによりです。



コメント

このブログの人気の投稿

Execノードを使う

SendGridのX-SMTPAPIヘッダの使い方(Section Tags、Substitution Tags編)

Joinノードを使う(その4)