既存アプリのREST APIスキーマを自動生成する

Posted on | 91 words | ~1 min

ツールを書いた。

https://github.com/hrfmmr/oasbuilder

Context

シゴトでiOSアプリ開発をしていて、既存(REST)APIクライアントがSwiftyJSONやPromiseなど現在となってはSwift標準ライブラリで置き換えが可能なライブラリに依存している且つアプリケーションレイヤで扱うモデルにもこれらへの依存が発生していたので、これらをうまいことリファクタしたいなぁというモチベーションがあった、というコンテキスト。

APIクライアントとそれらにぶら下がる層、わりと巨大なので、地道なリファクタ以外の方法でシュッとやれないものか、と考えていたところ、(今更ながら)Open API Generatorの存在(+Swift w/ Alamofireでのクライアントコード生成も可能なこと)を知り、「APIスキーマさえ手に入ればコード生成したものでレガシーコード諸々置き換えイケそう」という考えに至った。一方で、API&モデルのボリュームがそこそこあるので、「動作しているアプリのAPIスキーマの自動生成」がテーマとなって今回のツール作成につながったかんじ。

どうやるか

APIスキーマの自動生成、実はバックエンド側でREST APIサーバのライブラリでつかわれているGrape関連ツールのgrape-swaggerを用いたスキーマ生成が、deploy済APIを参照する用のSwagger UI定義としてすでに用いられていたので、このツールで吐かれたスキーマをつかえばクライアントコード生成も楽勝かとおもったが、実際のところpaginationなどの表現が欠けているなどして、そのまま流用することはできず、他の方法でスキーマを得る必要があった。

「アプリの実際の動き」を忠実に再現するスキーマデータを得るには、proxyツールを挟んで実際に流れるリクエスト/レスポンスのデータから生成するアプローチがいちばん手っ取り早いだろうと考えた。

イメージは以下

proxyツールとしてはmitmproxyを使い、以下のような流れでスキーマを生成

  • 1 ~ 5
    • mitmproxyのPython APIフックで流れてくるリクエスト/レスポンス情報をElasticsearchにdumpするmitmproxy-elasticagentを介して、アプリに必要となるAPIスキーマのrawデータを収集する
  • 6 ~ 8
    • Elasticsearchに保存されたデータをつかって、OpenAPI Specification(v3)準拠なyamlを生成する

デモ

  • Elasticsearch起動
    • $ elasticsearch
  • ~/.mitmproxy/config.ymlにmitmproxy-elasticagent addonスクリプトを追加
    scripts:
      - /path/to/mitmproxy-elasticagent/jsondump.py
    es_dest_url: http://127.0.0.1:9200/test-api/_doc
    es_target_host: api.example.com
    
  • $ git clone https://github.com/hrfmmr/oasbuilder
    • oasbuilder/.envをいじる
      ELASTICSEARCH_HOST=http://localhost:9200
      ELASTICSEARCH_INDEX=test-api
      OAS_VERSION=0.1.0
      OAS_TITLE=test api
      OAS_DESCRIPTION=test api
      OAS_SERVER_URLS=https://api.example.com
      
    • make run && make previewでdumpされたJSONをOAS yamlにビルド後、プラウザでRedocドキュメント表示

実行風景

  • なにやってるの
    • 右ペイン
      • $ watch -n2 "curl -XGET -H 'Content-Type: application/json' 'http://127.0.0.1:9200/demo-api/_doc/_search?pretty=true' -d '{\"aggs\": {\"req_paths\": {\"terms\": {\"field\": \"request.path.keyword\", \"size\": 10000}}}, \"_source\": [\"request.path\"] }' | jq '.aggregations.req_paths.buckets[].key'" を実行しているのは、アプリ操作に連動してAPIリクエスト/レスポンの元データをproxy addon経由でElasticsearchに保存されてくる様子を、APIの endpointをpath単位でwatchコマンドで監視しているもの
    • 左ペイン
      • oasbuilderを実行し、APIスキーマをOASとして出力 -> ブラウザでRedocドキュメント表示