yu nkt’s blog

nkty blog

I'm an enterprise software and system architecture. This site dedicates sharing knowledge and know-how about system architecture with me and readers.

Swaggerを試し、自動生成されたコードを見てみる

イントロ

会社でSwaggerがブームっぽくなっているので、使ってみました。 気になっていた機能は、codeGenです。 これのおかげで、どれほどAPIサーバーの開発が楽になるのかを体感してみたいと思います。

なお、この記事では自分がしたことの履歴のみを記載し、「Swaggerとは」という内容は他のサイトなどに任せるとします。

SwaggerEditorの起動

まずは、Swagger Editorをダウンロードします。

$ git clone https://github.com/swagger-api/swagger-editor.git
Cloning into 'swagger-editor'...
remote: Counting objects: 33660, done.
remote: Compressing objects: 100% (35/35), done.
remote: Total 33660 (delta 16), reused 28 (delta 11), pack-reused 33613
Receiving objects: 100% (33660/33660), 198.93 MiB | 4.95 MiB/s, done.
Resolving deltas: 100% (19003/19003), done.
$ cd swagger-editor
$ explorer .

あとは、index.htmlをブラウザで開くだけです。 なにこれ、簡単すぎ。

Swagger-Editorの画面

Swagger-Editor起動時には、サンプルのAPIドキュメントが書かれています。 Swagger Petstoreというサービスのようです。

f:id:yunkt:20180815125607p:plain

サンプルを見て、いくつか、気付くところがあります。

  • 鍵マークがついているAPIとついていないAPIがある
  • GET /pet/findByTagsに訂正線が付けられている
  • Modelを登録して、Responseとかに使える
  • Try it outボタンの意味

鍵マーク

直感的には、これの意味が分かりませんでしたが、各APIのスコープ(APIをたたける人の制限)を示すものだったようです。

鍵マークが付いているものは、tokenによる認証が必須のエンドポイントだそうです。 鍵が外れたマークになっていますが、鍵がかかっているマークとかは特にないようです。

後で、token認証を、モックアップサーバーで実際に試してみましょう。 その内容は、別の記事で紹介したいと思います。

訂正線

Deprecated (非推奨)のAPIということだそうです。

Model

Responseデータのデータモデルを登録しておけば、API開発時にそのモデルを指定するだけで、Response内容が設計できるようです。 これだけで、だいぶ設計が楽になりそうです。

f:id:yunkt:20180815130312p:plain

Try it outボタン

設計したAPIを試すためのボタンです。

APIの受け口になるWebサーバーを立ち上げていないと、このボタンは使えません。 今は、ブラウザでindex,htmlを開いているだけなので使えないようです。

しかし、次に記載するモックアップサーバーを起動したら、そちらでTry it outができます。

モックアップサーバー

立ち上げ

モックアップサーバーを立ち上げるためには、まずCodeGen機能でコードを取得しましょう。 上のタブのGenerate Serverをクリックし、自分が慣れている言語・フレームワークを選択します。 自分は、nodejs-serverを選択しました。

Zipで資材がダウンロード出来るので、それを解凍します。

解凍された中にREADMEが入っていました。 npm startを実行せよ、とのことです。 当然ここは、上で選択した言語・フレームワークごとに適切な指示が書かれており、それに従えば良いものと思われます。

APIドキュメントページ

実行後、http://localhost:8080が、API Webサーバーのホスト:ポートになっているようです。 http://localhost:8080/docs/が、APIドキュメントページです。 このページを、Swagger UIと呼ぶそうです。 API仕様をユーザーに見せるためには、これをちょこちょこっと修正するだけで良さそうです。 これは、自力でAPI開発するのが馬鹿馬鹿しくなる位、幸せになれそう。

docsのほうで、Try it outボタンがあります。これをクリックすると、試しにAPIがたたけるようです。 何もパラーメータが必要ない、GET /store/inventoryを試してみましょう。 すると、打ち込んだリクエスト、レスポンス、レスポンスコード、レスポンスヘッダーが出力されます。 モックアップサーバーとして、十分です。

モックアップサーバーのコード

CodeGenでどんなコードが生成されていたのか、気になりますよね。見てみましょう。

package.json

まずは、package.jsonを見てみましょう。

先ほどのnpm startで何が行われていたか、一応チェックしたところ、node index.jsだけでした。 あっさりしたものです。

あと、依存しているライブラリはこの通りです。

  "dependencies": {
    "connect": "^3.2.0",
    "js-yaml": "^3.3.0",
    "swagger-tools": "0.10.1"
  }

Webサーバーフレームワークには、connectを使っているようです。 connectについては、以下の記事が分かりやすそうです。

qiita.com

swagger-toolsは、まだ謎です。 他のコードを見ていけば、何のために存在しているか、つかめることでしょう。

ディレクトリ構造

次に、ディレクトリ構造を見てみます。

$ tree -I node_modules .
.
├── api
│   └── swagger.yaml
├── controllers
│   ├── Pet.js
│   ├── Store.js
│   └── User.js
├── index.js
├── package.json
├── package-lock.json
├── README.md
├── service
│   ├── PetService.js
│   ├── StoreService.js
│   └── UserService.js
└── utils
    └── writer.js

4 directories, 12 files

それぞれのディレクトリについて、見ていきましょう。

apiディレクト

apiディレクトリには、Swagger Editorで設計したyamlファイルが入っています。

最初は、なぜ設計に用いたファイルが、ここにも置かれているのか疑問でしたが、どうやらmiddlewareの作成に利用しているようです。

また、ディレクトリ構造や、各ファイルを軽く見ると、ルーティングを定義しているコードがない事にも気付きます。 index.js内で、後述するコントローラーの関数と、apiディレクトリ内のyamlファイルをパラメータとして、ルーティングを設定する関数middleware.swaggerRouterを呼ぶことで、ルーティングを設定しているようです。

これらのmiddlewareの作成などに、swagger-toolsが大きな役割を担っているようです。 では、他のディレクトリを見に行く前に、swagger-toolsを見に行きましょう。

swagger-tools

node_modules内のswagger-toolsの中身を見てみましょう。 全てのファイルを列挙すると、多すぎるので、2階層に限定しています。

$ tree swagger-tools -I node_modules -L 2
swagger-tools
├── bin
│   └── swagger-tools
├── index.js
├── lib
│   ├── helpers.js
│   ├── specs.js
│   └── validators.js
├── LICENSE
├── middleware
│   ├── helpers.js
│   ├── swagger-metadata.js
│   ├── swagger-router.js
│   ├── swagger-security.js
│   ├── swagger-ui
│   ├── swagger-ui.js
│   └── swagger-validator.js
├── package.json
├── README.md
└── schemas
    ├── 1.2
    ├── 2.0
    └── json-schema-draft-04.json

7 directories, 15 files

index.jsで、module.exportsされているinitializeMiddlewareは、後述しますが、自動生成されたコードのindex.jsでも利用されています。

swagger-tools内のindex.jsでは、specsmodule.exportされています。 specsは、./lib/specs.jsをrequireしているだけですが、specs.jsの中では、Specification関数があり、Swagger仕様オブジェクト、というものが作られます。 これも重要な気がします。

var initializeMiddleware = function initializeMiddleware (rlOrSO, resources, callback)

第一引数、The Resource Listing or the Swagger Objectの略だそうです。 ここに、自動生成された方のAPI仕様ファイル、./api/swagger.yamlyamlとして読み込んだオブジェクトが投入されます。

ルーティングは、自動生成されたindex.jsの中で、後述するコントローラーや、Swagger UIのパスなどをパラメータとして、middlewareのルーティングを設定する関数swaggerRouterに渡してやれば良いようです。 これは、index.jsの33行目で行われています。

  // Route validated requests to appropriate controller
  app.use(middleware.swaggerRouter(options));

controllersディレクト

controllersディレクトリは、MVCのC、コントローラーですね。 届いたリクエストに応じたイベントハンドラで、モデルを操作します。

コントローラーのコードが自動生成出来るって、すごくないですか? どのようにして自動生成出来ているのか見るために、少しコードを見てみましょう。

var utils = require('../utils/writer.js');
var Pet = require('../service/PetService');

module.exports.addPet = function addPet (req, res, next) {
  var body = req.swagger.params['body'].value;
  Pet.addPet(body)
    .then(function (response) {
      utils.writeJson(res, response);
    })
    .catch(function (response) {
      utils.writeJson(res, response);
    });
};

module.exports.deletePet = function deletePet (req, res, next) {
  var petId = req.swagger.params['petId'].value;
  var api_key = req.swagger.params['api_key'].value;
  Pet.deletePet(petId,api_key)
    .then(function (response) {
      utils.writeJson(res, response);
    })
    .catch(function (response) {
      utils.writeJson(res, response);
    });
};

module.exports.findPetsByStatus = function findPetsByStatus (req, res, next) {
  var status = req.swagger.params['status'].value;
  Pet.findPetsByStatus(status)
    .then(function (response) {
      utils.writeJson(res, response);
    })
    .catch(function (response) {
      utils.writeJson(res, response);
    });
};

writer.jsserviceディレクトリ内のコードに依存しています。

これらの関数の名前は、yamlファイル内の各エンドポイントのoperationIdに設定された値と同じです。 それぞれの関数内をよく見てみると、モデルの操作は行っていません。 モデルの操作は、コントローラーでは行わず、serviceディレクトリ内のコードに処理を任せているようです。 コントローラーでは、そのサービスへの適切な引数・返値に合わせた処理のみが記載されています。

では、serviceディレクトリを見ていきましょう。

serviceディレクト

先ほどと同じように、PetService.jsの中を少し見ていきましょう。

/**
 * Add a new pet to the store
 * 
 *
 * body Pet Pet object that needs to be added to the store
 * no response value expected for this operation
 **/
exports.addPet = function(body) {
  return new Promise(function(resolve, reject) {
    resolve();
  });
}


/**
 * Deletes a pet
 * 
 *
 * petId Long Pet id to delete
 * api_key String  (optional)
 * no response value expected for this operation
 **/
exports.deletePet = function(petId,api_key) {
  return new Promise(function(resolve, reject) {
    resolve();
  });
}


/**
 * Finds Pets by status
 * Multiple status values can be provided with comma separated strings
 *
 * status List Status values that need to be considered for filter
 * returns List
 **/
exports.findPetsByStatus = function(status) {
  return new Promise(function(resolve, reject) {
    var examples = {};
    examples['application/json'] = [ {
  "photoUrls" : [ "photoUrls", "photoUrls" ],
  "name" : "doggie",
  "id" : 0,
  "category" : {
    "name" : "name",
    "id" : 6
  },
  "tags" : [ {
    "name" : "name",
    "id" : 1
  }, {
    "name" : "name",
    "id" : 1
  } ],
  "status" : "available"
}, {
  "photoUrls" : [ "photoUrls", "photoUrls" ],
  "name" : "doggie",
  "id" : 0,
  "category" : {
    "name" : "name",
    "id" : 6
  },
  "tags" : [ {
    "name" : "name",
    "id" : 1
  }, {
    "name" : "name",
    "id" : 1
  } ],
  "status" : "available"
} ];
    if (Object.keys(examples).length > 0) {
      resolve(examples[Object.keys(examples)[0]]);
    } else {
      resolve();
    }
  });
}

ここに、具体的なモデルの操作を、記載できそうですね。 基本的にAPIリクエストを受け取った後は、非同期的に処理を行う事が前提になっているようです。 具体的なモデルの操作は、yamlに書かれていないので、ほぼ空欄同然になっています。

findPetsByStatus関数だけは、色々と書かれています。 これは、この関数が、Petモデルを返すため、Petモデルに定義されていたexampleがここに書かれているのだと思われます。 このexampleは、Swagger EditorのyamlまたはJSONで修正する事ができます。 findPetsByStatus関数を修正する際は、このPetモデルのexampleを見ながら修正できるので、ものすごくありがたいですね。

残り

utilsディレクトリとindex.jsの説明は、特にAPI設計開発において主要な所ではないので飛ばしました。 見れば何をしているか、すぐに分かりますし。

おわりに

確かに、Swagger Editorを使えば、API開発の簡単化+ミスの抑制に繋がると思います。 ただし気になるのは、既にある程度のAPIが公開されているアプリケーションに対して、後からSwaggerを使ってAPIの設計を管理したい、と思ったとき、どう使っていけば良いのか…。 これは、追々、ちゃんと使ってみてから、また見解を書きたいと思います。

自動生成されたコードに関して言うと、ルーティングがハードコーディングされていない設計は、拡張性が高まるので、素晴らしいと思います。 サービス・コントローラーをあらかじめ用意しておけば、yamlを書き換えるだけで、すぐにAPIの中を切り替えられます。 ユーザーからして、突然APIで提供される機能が変わったら大事なので、API提供の思想としてそれをして良いときは、見極めないと行けませんが。

あと、MVCではなく、コントローラーとモデルの間に、サービスが挟まっていますね。 このサービスの意義を、今後、もう少し深掘りしたいと思います。 恐らく、Springと同じサービスで、ここにビジネスロジックを書くのでしょう。