APIモックを作る?
SORACOM APIをC#から触れるようにしたのとおり、先日SORACOM APIの非公式な.NET版クライアントライブラリを書きました。これについてソラコム Developers Conference #0のLTでひたすら「API sandboxがないので管理クライアントや他言語SDKの開発がつらい」という話をしてから1週間ほど経ちます。
「よーし足りないAPIを追加するかー」と思ったものの、やはり最低限はテスト環境が無いとリグレッション検出などで結構困るので、テスト用のモックサービスを作ることにしました(手間がでかすぎてyabaiというほどでもないし)。
試験前に部屋の片付けをするノリで作ったので本当に最低限だけです*1が、完成したので
- 実際に動いているもの: http://SoraCommonNetSandbox.azurewebsites.net/
- 挙動
- 送信されたデータのバリデーションは概ね本家のポリシーに従った
- 送信されたデータの保存は一切おこなわない(つまり、SIMを登録しても一覧には反映されないし、解約を叩いても解約済み状態にはならない)
- コード: https://github.com/muojp/SoraCommonNet/tree/master/SoraCommonNetSandbox
ともかく、心置きなくSIM解約のテストができるの素晴らしい!
以下は、作り方が気になる方のための簡単ASP.NET 5ガイドです。
[*1] パスワード指定ポリシーは本家のAPIに準拠したりしていますが。
構成
- 開発言語: C# (C# 6)
- フレームワーク: ASP.NET 5(ASP.NET MVC 6 beta5)
- 開発環境: Visual Studio Community 2015
- 稼働環境: Microsoft Azure Web Service(Web Apps無料版)
ASP.NET 5(ASP.NET MVC 6)でRESTfulサービスを作る
ASP.NET 5とDNXの1.0.0-beta8がリリースされてたで書いたようにASP.NET 5はfeature complete状態にあります。つまりコードの安定性はさておき、現時点で提供されているAPIセットはよほどのことがない限り製品版まで変更されないはずです。最悪落ちても辛くない系プロダクトを書き始めるのにちょうど良いタイミングということです。基本的にVisual Studio 2015(以下VS2015)のASP.NET Web Application内にあるWeb APIテンプレートから作成したものを下地としてリファレンス片手にIntelliSense眺めつつ気合で書き換えていきますが、取っ掛かりとしてはBuilding Your First Web API with MVC 6が良いチュートリアルです。
VS2015でのASP.NET 5 Web Application(preview)プロジェクト作成
VS2015に含まれているASP.NET 5のプロジェクトテンプレートは多少古く、beta5用のものです。じきにVS自体のアップデートで更新されると思いますが、現段階ではひとまずbeta5を使っておきましょう。
project.jsonの中身を直接書き換えて依存関係を更新する策もありますが、これを書いている時点ではMicrosoft.AspNet.Server.IISだけbeta8版が出ていないので、更新待ちです*2。
[*2] 軽い気持ちで混ぜたところやっぱりダメだったので混ぜるな危険。どうしても新betaを使いたければIISをやめてKestrelを使うなど、手はあります。それはそれでハマりそうですが。
レスポンスの返し方について少々メモ
プログラムにエラー処理はつきものです。当然、RESTfulなAPIでもさまざまなエラー処理が必要です。
Visual Studioのテンプレートから生成されるValuesControllerは普通に各APIからの戻り値をstringやIEnumerable<string>と宣言します。こうしておくとASP.NET MVCのランタイム側で適当にJSONへ詰め込んで返してくれます。
このフローは型情報をきっちり書く面ではとても良いのですが、不便なところがあります。HTTPのエラー情報を返すうえで厄介なのです。
従来のASP.NET MVCではこの処理に例外を使っていました。
throw new HttpResponseException(HttpStatusCode.NotFound);
しかしASP.NET 5でこのように書いても単純に内部でハンドルされていない例外が発生した扱いとなるだけです。
ではどうするのかというと、レスポンスをラッピングできるIActionResultというインタフェースに従った値を返すようにします。
HTTPのエラーコードはいろいろありますが、要はテンプレコードです。なるべく省力でカバーしたいものです。
たとえば400のbad requestを返したい場合、ASP.NET MVC的にはBadRequestResultとBadRequestObjectResultのふたつの選択肢があります。レスポンスのボディがあるか無いかという違いを考えると、このようにわかれているのは仕方ないのですが、呼び分けるのが微妙に面倒です。
ASP.NET MVCのControllerは至れり尽くせりで、HttpBadRequestというstaticメソッドがオブジェクトを引数として渡すか否かで生成オブジェクトを切り替えてくれます。
これを使って次のようにサクサクとエラー処理を書けます。
[HttpGet] public IActionResult Get() { return HttpBadRequest(new { foo = "shinchoku", errmsg = "damedesu" }); }
粛々とAPI仕様に従ってコントローラを書く
書きます。最初のうちは動作確認をcurlでおこなっていましたが、途中からFiddlerへ変更してサクサク確認していきました。IEからの通信を自動キャプチャとかとても便利でした。
ASP.NET 5(ASP.NET MVC 6)でサクサクとWeb APIを作る
ASP.NET MVC 6ベースのRESTful API構築は割と生産性高くて良いです。面倒なボイラープレート系のコードはほとんどなく、コーディング時間のうちほとんどをAPI定義→C#コードへの翻訳に使えました。RESTfulなサービスにおいて、HTTPのリクエストではおおまかに
- URIのパスにfoo/bar/hoge/fuga形式で記述
- クエリ文字列に?foo=bar&hoge=fuga形式で記述
- リクエストボディにJSON({"foo":"bar", "hoge":"fuga"})で記述
今回はクエリ文字列解釈を使っていませんが、それ以外のふたつは
public class SetGroupRequest { public List<string> configuration { get; set; } public long createdTime { get; set; } public string groupId { get; set; } public long lastModifiedTime { get; set; } public string operatorId { get; set; } public Dictionary<string, string> tags{ get; set; } } [HttpPost("{imsi}/set_group")] public IActionResult Post(string imsi, [FromBody] SetGroupRequest req) { ... }
というようになります。
- URIパス内の特定部分に名前をつけて受け取る
- リクエストボディのフォーマットを規定したら[FromBody]属性をくっつけて受け取る
前述のエラー処理に関するポイントと共におさえておくと、楽にサービスを書き始められると感じました。
C# 6のNull conditional operatorもhttps://github.com/muojp/SoraCommonNet/blob/master/SoraCommonNetSandbox/Controllers/Subscribers.cs#L40のような場所で役に立ちますね。
テストのテストをどうするかという話
Ruby版SDKなど、他言語用のバインディングを使ってテストする策がありますが、今のところこれ自体のテストは書いていません。適当にデプロイして様子を見る
どうせ誰も使わないので適当に公開しておきます。- コード: https://github.com/muojp/SoraCommonNet/tree/master/SoraCommonNetSandbox
- 実際に動いているもの: http://SoraCommonNetSandbox.azurewebsites.net/
ちなみにこれは無料プランゆえのtipsですが、自動テストから叩く際にはSetUp的なメソッド内で適当なAPIを叩いてスピンアップさせるのが無難です。さもないと序盤のテストケースで10秒程度固まり、標準の2秒タイムアウトだと何かと面倒な感じになるでしょう。
[*3] 無駄に検索エンジンから拾われるのもアレなのでrobots.txtぐらいは置いておいたほうが良いのかもしれません。
0 件のコメント:
コメントを投稿