nodejs
SDK
swagger
OpenAPI-Specification
bitflyer
HameeDay 6

OpenAPI Spec 3.0の勉強ついでにBitFlyer Lightning APIのラッパーモジュールを作りました

要約

  1. OpenAPI SpecはRESTなAPIの構造を提供します  
  2. 仕様のみならず、開発周辺ツールがとても充実しています
  3. 実際にBitFlyerのLightning APIのspecを書いてnode.jsで簡易なモジュールを作ってみました  
  4. OpenAPI Specの仕様はまだ完璧ではないしツラみもありますが、ぜひ使ってみてください  

OpenAPI Specとは

The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for REST APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic. When properly defined via OpenAPI, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interface descriptions have done for lower-level programming, the OpenAPI Specification removes guesswork in calling a service.

引用: OpenAPI-Specification

つまり、人間も機械も読みやすく、なるべく手のかからないAPIの標準的なインターフェースを作りましょうと言うことです。

実際に書いてみると、APIへの理解はより容易に、実装も簡易になるなと感じています。

OpenAPI Specの説明はとても良い記事を見つけましたので、ぜひこちらをご参照ください。

同じ説明になるので、ここでは省略させていただきます。

なお、OpenAPIとSwaggerの違いですが、OpenAPI(OpenAPI Spec)が仕様で、SwaggerがOpenAPI Specの作成、それを用いた開発の補助となる周辺ツール、サービス群です。

OpenAPI Specの策定・推進をしているコミュニティがOpenAPI initiativeです。

参考: openAPI about
参考: swagger about

現在ver3.0がリリースされています

OpenAPI Specは特定のオブジェクトをネストしていく形で表現されます。
以下の図が親のオブジェクトです。

引用: openAPI initiative
引用: openAPI initiative  

OpenAPI Spec 3.0の構成について  
2.0から3.0へのバージョンアップで、要素がまとまった事が分かると思います。
私の感想としては、2から3への変更で理解しやすく&書きやすくなりました。  

(細かい話は省きますが)これらの仕様に沿って、json、もしくはyamlで定義していきます。  

例えば、サーバー情報のオブジェクト(servers)はこのようになります。

servers:
  - url: https://api.bitflyer.{domain}/{version}
    description: The main prod server
    variables:
      domain:
        enum:
          - jp
          - com
        default: jp
      version:
        enum:
          - v1
        default: v1

serversを親にurlやdescriptionを定義します。各オブジェクトは仕様に応じてネストします。

基本的な書き方はどのオブジェクトでも同じです。

仕様については、GithubWEBサイトにとても細かいドキュメントが整備されているのでご参照ください。

OpenAPI Specに従うと何が嬉しいのか

  1. 低い学習コストでRESTな設計ができる
  2. 容易に高品質なドキュメントを生成することができる
  3. コードの実装や、ラッパーモジュールにも活用できる

仕様として構造が決められているので、自然とRESTな作りになります。  

APIを作成するからには、仕様書・ドキュメントが必須になりますが、OpenAPI specをベースにしていれば、swaggerで簡単にhtmlに吐き出すことが可能です。

swagger toolsがやっているように、specを仕様に従ってソースコード内で読み込めば、実装にも活用できます。

(swaggerのツール郡については、こちらの記事をご参照ください。とてもわかり易かったです)

引用: Swagger editor
引用: Swagger editor  

また、spec自体がyamlであるため、git管理が可能であり、共同の編集や共有に向いています。

publicに公開するAPIでないとしても、各ステークホルダーとの合意形成にとても役立つと思います。

まさしく、人間も機械も読める仕様書なわけです。Document as code!

BitFlyerLightning APIのOpenAPI Specを書いて、nodeのラッパーモジュールを作ってみました

今回は個人的な趣味でBitFlyerさんのAPIドキュメントをOpenAPI Specに書き起こさせていただきました。(※絶賛書き途中です)  
OpenAPI spec 3.0の勉強と暗号通貨のトレードbotついでです。

リポジトリはこちらです。masayannuu/node-bitflyer

インストール 

npm install node-bitflyer

普通にリクエストする時

const { RestClient, RealtimeClient } = require('node-bitflyer')

const client = new RestClient('api_key', 'api_secret')
client.fetch('GET', '/me/getpermissions').then((res) => { console.log(res)} )

//with query
client.fetch('GET', '/board?product_code=ETH_BTC').then((res) => { console.log(res)} )

//with body
client.fetch('POST', '/me/sendchildorder',
{ "product_code": "BTC_JPY",
  "child_order_type": "LIMIT",
  "side": "BUY",
  "price": 30000,
  "size": 0.1,
  "minute_to_expire": 10000,
  "time_in_force": "GTC"}
  ).then((res) => { console.log(res)} )

swagger.yamlから引っ張ってきて実行する時

メソッドを生やして、エンドポイントの情報知らなくても叩けるようにラッピングしました。

const { RestClient, RealtimeClient } = require('node-bitflyer')

const client = new RestClient('api_key', 'api_secret')
client.getMarkets().then((res) => { console.log(res) })
// client.fetch('GET', '/markets')と同じ

OpenAPI Specの構造体に合わせて諸々呼び出せるので、使い回しが容易です。
OpenAPI Specの定義書はapis/v1/swagger.yamlです。

※定義した値、ほぼ使ってないじゃん、という点は目をつぶっていただければと思います。笑  
リクエストやレスポンスもModel的に定義してあるので、QueryBuilderの生成等も比較的容易にできます。  

今回私は、swagger.yamlに定義したpathごとに持つoperationIdという値を元にメソッドを生やすということやってみました。

class RestClient {
  /**
   *
   * @param  {[String]} key
   * @param  {[String]} secret
   */
  constructor (key, secret) {
    this._key = key || ''
    this._secret = secret || ''
    this._permission = ''
    //swagger.yamlを読み込んでオブジェクトにします
    this._swagger = new Swagger()
    this.createMethod()
  }

  /**
   * Create method from swagger spec
   */
  createMethod () {
    this._swagger.paths.forEach((path) => {
      const path_object = this._swagger.getPathObject(path)
      Object.keys(path_object).forEach((method) => {
        this[path_object[method]['operationId']] = this.createRequest(path, method)
      })
    })
  }

  /**
   * Create function for api request
   * @param  {[String]} path
   * @param  {[String]} method
   * @return {[Object]}
   */
  createRequest (path, method) {
    return async (params='') => {
      if (await this.isPermmitted(path)) {
        return await this.fetch(method.toUpperCase(), path, params)
      } else {
        return new ConnectionError('You don`t have permmision', 'unauthorized access', 401)
      }
    }
  }

OpenAPI Specの定義があると、さくっとできました。

エンドポイントが増えた時も勝手に追従します。

そして、繰り返しになりますが、OpenAPI Specはドキュメントの生成も可能です。

つまり、仕様の変更に合わせて、依存関係にある各所を修正するコストがぐっと安くなります。 

最後に

ちなみに、野良で既存のドキュメントをゴリゴリ書き起こすのはなかなか大変です。笑

完璧なものに仕上げるのであれば、異常系(各エラーレスポンス)も定義が必要ですし。

まだまだバージョンアップされていくものなので、引き続き情報を追っていきたいと思います。

既存のAPIからSpecを書き起こす際には、テストからSpecを生成できるSwagger Inspectorや、ドックコメントから生成できるSwagger codegenのようなツールもあるので、ぜひ自分に合ったものを探してみてください!  

APIの開発者のコストを下げるだけでなく、その周辺ツールの開発もより容易になるので、今よりもっと広まりさらに便利な標準になってくれるといいな、と思っています。(たくさん使って、積極的にcontributeしていきます!)

ご一読ありがとうございました!!