背景

弊社では、LaravelとVue.jsを使ってSPA + APIサーバーの構成でアプリケーションを構築しています。 APIとクライアントを別で実装すると各々を並列に開発できるので、開発効率も上がるのですが、APIのインターフェースをドキュメント化するのにどうしても工数がかかってしまいます。

最近はWebのgRPCが出てきたり、そこまで行かなくてもProtocol Bufferのver3でjson生成してRESTAPIとする等の方法で互いの通信プロトコルを定義するプロジェクトが増えているので、そちらを使うという選択肢もあります。 しかし、Laravelに標準で入っているFormRequestだったり、Eloquentの便利なリレーションの機能を使おうとするとこれらの機能と競合するため、フロントでは純粋なjsonを使う必要が出てきます。

そこで当初はSwaggerを使ってAPIのインターフェースを定義していたのですが

  • 独自記法を覚える必要がある
  • どうしても記述量が多くなる
  • #definitions/スキーマをモデルごとに書く必要がある
  • 細かいカラムの表示制御(あるAPIではemailを含めるが、他のAPIでは情報保護のためにemailは表示しないなど)を表現しづらい
  • API仕様書自体の保守コストがかかり、実装と違う記述になる

という短所があり、うまくワークしませんでした。 Swagger-PHPも検討しましたが、メソッドのコメントに記述する量が膨大で移行コスト対効果は認められないという判断となりました。

エディターベースのAPI定義: .rest

そういった中で、.rest.httpといったファイル形式でAPI仕様書を書くという方法に着目しました。 エディタごとに多少仕様はことなるのですが、RFC 7230](https://tools.ietf.org/html/rfc7230#page-19)ベースで[API仕様を記述するだけで、実行可能なAPI仕様書を書くことができます。

今まではVimやEmacs、Atom等のエディタにプラグインを入れる形で上記の環境を整備出来ていましたが、PhpStormやIntelliJIDEAなどのJetBrains系IDEにも2017.3 EAPから公式導入され、よりプログラマにとって嬉しい環境が整ってきました。

この.rest, .http形式で記述することのメリットとしては、

  1. RFC 7230ベースでの記述となるので、見やすく、学習コストが非常に低く、自動出力の実装コストも低い。
  2. 依存関係がないのでファイルの分割粒度をリクエスト単位に分けても問題無い
  3. エディタのサポートがあることで、API仕様書用に環境を整備しなくても実行可能な環境が構築できる。
  4. ローカルファイルに記述するので、セキュリティー等考える必要がない。

があげられるかと思います。 特に1.の自動出力の実装コストが低いというところは、当初のニーズにマッチしていたため、この方法を採用しました。

Laravelのテストとの親和性

弊社ではLaravelというPHPのフレームワークを使用しているのですが、LaravelにはPHPの標準UnitテストフレームワークのPHPUnitをLaravel用に拡張したテスト用便利機能が実装されております。 特にAPIを叩き、レスポンスをテストする拡張が非常に使いやすく、下記のように記述するだけです。

1
2
3
4
5
6
7
8
9
// GET
$res = $this->getJson(
   "/api/user/" 
);
// POSTなどでのペイロードは第二引数に
$res = $this->postJson(
   "/api/user/",
   ["name" => "hoge"]
);

こちらは単なるクラスメソッドですので、オーバーロードをすることで、リクエスト、レスポンスに対して何かしらのフックを仕込むことが可能です。

ということで簡単なプラグインを作成

プラグインはこちら ※プルリクお待ちしております! 使用方法は下記です。 (こちら https://github.com/kotamat/laravel-apispec-sample に実装サンプルを用意しました。diff)

まずcomposer requireでパッケージをインストールします

1
composer require --dev kotamat/laravel-apispec-generator

その後、上記APIレスポンスのテストケースを書くクラスにて、ApiSpec\ApiSpecTestCase を継承します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

namespace Tests\Feature;

use ApiSpec\ApiSpecTestCase;
use Tests\CreatesApplication;

class HttpTest extends ApiSpecTestCase
{
    use CreatesApplication;
    protected $isExportSpec = true;
}

ファイル出力するかどうかは$isExportSpecで切り替える事が可能です(デフォルトはfalse)。 envの値で真偽値を切り替えるといいかと思います。

テストケースで下記のように記述し、実行すると

1
2
3
4
public function testPost()
{
    $this->postJson("/api/test",['hoge'=>'huga']);
}

/storage/app/api/test/POST.http に当該のファイルが生成されます。(下部のコメントアウトされているところは、レスポンスの値となります。) Laravelにおいて/storage配下はGitに含まれないディレクトリとなりますので、生成後のファイルがGit汚染することはありません。

ためしに、php artisan serve --port 9000http://0.0.0.0:9000にサーバーを立ち上げ

1
2
- POST /api/test
+ POST http://0.0.0.0:9000/api/test

としてあげると、

このように実行結果が帰ってきます。

まとめ

Swaggerに変わるAPI定義書として、.rest.httpを紹介させていただきました。 今回紹介したようにテストケースとして記述しておけば、そのテストが通っている限り、APIの仕様が最新であることが保証でき、副産物としてテストの記述もれの防止やコードレビューでの補助ツールとして大きくプロジェクト品質に貢献するかと思います。