Web APIを開発しよう -設計からAWSへのデプロイまで-
【更新】2020年3月14日:古い情報をアップデート、Web APIの設計についても追記
本記事は私がWeb APIを開発する経緯を残したものです
Web API: The Good Partsを大いに参考に実際に開発を進める体でいくつかの開発ツールの使い方を解説しました
最終的にはAmazon API Gateway, AWS Lambdaにデプロイし、実際に利用可能なレベルまで(運用可能とは言っていない)
目次
動作環境
- Python 3.8
- pipenv 2018.11.26
- chalice 1.13.0
Web APIとは
物の本から引用します
Web API とは「HTTP プロトコルを利用してネットワーク越しに呼び出す API」です。
Web API: The Good Parts
(中略)
簡単に言えばある URI にアクセスすることで、サーバ側の情報を書き換えたり、サーバ側に置かれた情報を取得できたりすることができるウェブシステムで、プログラムからアクセスしてそのデータを機械的に利用するためのものです。
Web APIを理解する上でCRUD, RESTという概念を理解していると良いので紹介します
CRUDとは
CRUDとは、永続的なデータを取り扱うソフトウェアに要求される4つの基本機能である、データの作成(Create)、読み出し(Read)、更新(Update)、削除(Delete)の頭文字を繋げた語。
CRUD(Create/Read/Update/Delete)とは – IT用語辞典 e-Words
Web APIとは「URI にアクセスすることで、サーバ側の情報を書き換えたり、サーバ側に置かれた情報を取得できたりすることができるウェブシステム」のことでした
Web APIではURIとHTTPメソッドの組み合わせで対象と操作内容(CRUD)を表します
HTTPメソッドとCRUDの対応は以下の通りです
- GET: 読み出し
- POST: 作成
- PUT/PATCH: 更新
- DELETE: 削除
RESTとは
Web APIの規格の1つです
REpresentational State Transferの略で以下の6つの基本原則を持ちます
- Uniform interface
- Client-server
- Stateless
- Cacheable
- Layered system
- Code on demand (optional)
RESTfulと表現するには上記の原則を満たす必要があります
開発ツール
今回の開発に利用するツールを紹介します
いずれもミスなく、素早く開発を進める上で大いに役立ってくれます
Swaggerとは
SwaggerはRESTfulなウェブサービスを設計、構築、文書化、利用するためのツールで支えられるOSSフレームワークです
「The Best APIs are Built with Swagger Tools」とまで言われています(自称)
Swagger is an open-source software framework backed by a large ecosystem of tools that helps developers design, build, document, and consume RESTful web services.
Swagger (software) – Wikipedia
Swagger toolsにはUI, Editor, Codegen, Validatorなどがあります
これらのツールを統合したSwaggerHubではOpenAPI仕様でAPIを記述し、そこからインタラクティブなドキュメントやサーバサイドのコードを生成することができます
Chaliceとは
ChaliceはPythonでサーバレスアプリを記述するためのマイクロフレームワークです
AWSへのデプロイを簡単に実行できます
Chalice is a microframework for writing serverless apps in python. It allows you to quickly create and deploy applications that use AWS Lambda.
aws/chalice: Python Serverless Microframework for AWS
以降、実際にAPIの設計、実装を行います
お題としてWeb API: The Good PartsのSNSアプリのWeb APIの例を取りあげます
SwaggerHubでAPI設計
今回作成するAPIは公開しているのでぜひご覧ください
SwaggerHubにログイン
APIの設計や文書化を行うにはSwaggerの様々なツールを使うのが効率的です
それらツールを統合したSwaggerHubのアカウントを作成しましょう
SwaggerHubにログインしたらCreate New APIから新規作成します
OpenAPI Versionは3.0.0を選択し、TemplateはNone、他は適当に入力してCREATE API
Auto Mock APIをONにしておくと、モックのAPIサーバーを立ててくれます
エディターとドキュメントが並んで表示されます
サーバ情報
まずはサーバ情報を記述しますinfo
とpaths
の間に以下のように記述します
servers:
- url: https://api.exmaple.com/v1
urlは複数定義可能なので、開発サーバと本番サーバを分けることができます
機能の洗い出し
では実際にAPIの設計を行います
その前にクライアントアプリケーション側の画面と遷移を考えましょう
このSNSアプリに必要な機能を洗い出すとこうなります
- ユーザ登録
- ログイン
- 自分の情報の取得
- 自分の情報の更新
- ユーザ情報の取得
- ユーザの検索
- 友達の追加
- 友達の削除
- 友達の一覧の取得
- 友達の検索
- メッセージの投稿
- 友達のメッセージの一覧の取得
- 特定の友達のメッセージの取得
- メッセージの編集
- メッセージの削除
- 友達の近況の一覧
- 特定のユーザの近況の一覧
- 近況の投稿
- 近況の編集
- 近況の削除
ここで操作対象となるデータはユーザ情報、近況情報、友達関係を表すソーシャルグラフの3つです
まずはユーザ情報に関するAPIの設計を行います
上述の必要機能からユーザ情報に関するものだけをまとめると以下です
- ユーザ登録
- 自分の情報の取得
- 自分の情報の更新
- ユーザ情報の取得
- ユーザの検索
エンドポイントの設計
これらの機能に必要なエンドポイントを設計します
(「自分の情報の取得」も「ユーザ情報の取得」もIDを指定したユーザ情報を取得するという点で「特定のユーザの情報の取得」に統合できます)
目的 | エンドポイント | メソッド |
---|---|---|
ユーザ一覧取得 | https://api.exmaple.com/v1/users | GET |
ユーザの新規登録 | https://api.exmaple.com/v1/users | POST |
特定のユーザの情報の取得 | https://api.exmaple.com/v1/users/{userId} | GET |
ユーザの情報の更新 | https://api.exmaple.com/v1/users/{userId} | PUT/PATCH |
ユーザの情報の削除 | https://api.exmaple.com/v1/users/{userId} | DELETE |
こうして2つのエンドポイントとHTTPメソッドを組み合わせることで5つの機能を実現できます
エンドポイントの定義
実際にSwaggerHub上でそれぞれのエンドポイントを定義します
まずは「ユーザ一覧取得」からです/users/
にGETメソッドでレクエストすれば全てのユーザ情報が返ってくるという意味です
レスポンスはとりあえず200だけ記述しておき(ないとエラーが出る)、後ほど中身を定義します
paths:
/users/:
get:
summary: Returns a list of users.
responses:
'200':
description: OK
同様に「ユーザの新規登録」を定義します
paths:
/users/:
get:
summary: Returns a list of users.
responses:
'200':
description: OK
post:
summary: Add a new user.
responses:
'200':
description: OK
エディタにAPIの定義を記述するとリアルタイムにドキュメントが更新されます
残りはIDを指定する3つの機能です
パラメータの渡し方はいくつかありますが、ここではパスに含める方法を取りあげます
パラメータを波括弧でくくり、パスに含めます
parameters
キーに名前や必須か否か、データ型などを定義できます
従って「特定のユーザの情報の取得」は以下のように記述します
paths:
/users/{userId}:
get:
summary: Returns a user by ID.
parameters:
- name: userId
in: path
required: true
description: ID of user to return
schema:
type: integer
format: int64
responses:
'200':
description: OK
同様に「ユーザの情報の更新」、「ユーザの情報の削除」をPUT、DELETEで定義します
(PUTは入力された情報で既存のユーザ情報を完全に置き換えます。一部のみを更新する場合はPATCHを使います)
paths:
/users/{userId}:
get:
summary: Returns a user by ID.
parameters:
- name: userId
in: path
required: true
description: ID of user to return
schema:
type: integer
format: int64
responses:
'200':
description: OK
put:
summary: Update an existing user
parameters:
- name: userId
in: path
required: true
description: ID of user to update
schema:
type: integer
format: int64
responses:
'200':
description: OK
delete:
summary: Delete a user
parameters:
- name: userId
in: path
required: true
description: ID of user to delete
schema:
type: integer
format: int64
responses:
'200':
description: OK
以上でひとまず受け皿だけは完成しました
引き続きリクエストボディ(ユーザ作成の時に何を渡すのか)とレスポンス(ユーザ取得の時に何が返ってくるのか)を定義します
リクエストボディの定義
新規ユーザ登録のエンドポイントにrequestBody
を追加します
以下のようにrequestBody
内のschema
でデータ型を定義します
paths:
/users/:
post:
summary: ...
requestBody:
required: ...
content:
application/json:
schema:
type: object
properties:
name:
type: string
example: Alice
ドキュメントはこう反映されますexample
を記載しておくと親切かと思います
レスポンスの定義
次はレスポンスの定義です
requestBody
同様responses
内にcontent
を定義します
paths:
/users/{userId}:
get:
summary: ...
parameters:
...
responses:
'200':
description: ...
content:
application/json:
schema:
type: object
properties:
id:
type: integer
format: int64
example: 1
name:
type: string
example: Alice
データ型の定義
と、上記のようにresponses
やrequestBody
に一々データ型を書くのは面倒ですし何より事故の元になります
components/schemas
セクションにデータ型を予め定義することで、これを参照することができます
先ほど同様exampleを定義できます
components:
schemas:
User:
properties:
id:
type: integer
example: 1
name:
type: string
example: Alice
required:
- id
- name
ドキュメントに新たにSchemas
という区分が追加されます
今定義したスキーマを利用するにはschema
セクションにて$ref
でパスを指定します
paths:
/users/:
get:
...
post:
summary: ...
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
...
以上でユーザ情報に関する部分のAPI設計が完了しました
Swagger Codegenでデプロイ
SwaggerHubの右上ExportのServer Stubからお好みのサーバを選択
ここではflaskを使います
zipファイルがダウンロードされるので、展開します
こんな感じのファイル構成
$ ls -a
. .gitignore .travis.yml git_push.sh swagger_server
.. .swagger-codegen Dockerfile requirements.txt test-requirements.txt
.dockerignore .swagger-codegen-ignore README.md setup.py tox.ini
ライブラリをインストールして、swagger_serverをモジュールとして実行すればローカルサーバが立ち上がります
$ pip install -r requirements.txt
$ python -m swagger_server
...
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
...
エンドポイントにアクセスできます
Codegenが自動生成してくれるのはあくまでルーティング部分のみ
def users_get(): # noqa: E501
"""Returns a list of users.
# noqa: E501
:rtype: List[User]
"""
return 'do some magic!'
ロジックは書かれていないので自分で記述する必要があります
Dockerfileも自動生成されており、コンテナとして起動することもできます
AWSにホスト
APIをAWSにホストする際に
私はAPIサーバをAPI Gatewayで運用したいので以下のChaliceを使います
Python仮想環境構築
pipenvを利用してPythonの仮想環境を構築します
$ pipenv install --python 3.8
$ pipenv shell
以降の操作は断りがない限り仮想環境内です
Chaliceインストール
$ pipenv install chalice
Chalice動作確認
新規プロジェクトを立ち上げると同名のディレクトリが作成されます
$ chalice new-project test
testディレクトリ内にいくつかのファイルが作成されます
その中のapp.pyがAPIの肝となる部分です
デフォルトではトップページ(’/’)にアクセスしてきたら{'hello': 'world'}
を返すよう設定されてます
$ less test/app.py
from chalice import Chalice
app = Chalice(app_name='test')
@app.route('/')
def index():
return {'hello': 'world'}
早速ローカルで動作確認します
$ cd test
$ chalice local
Serving on http://127.0.0.1:8000
ブラウザでアクセスすると{'hello': 'world'}
が返ってきました
ではapp.pyに以下を追記してみます/users/{_id}
へのアクセスに対してidに応じたユーザ情報を返せるようにしましょう
USERS = {
'1': {
'name': 'Alice',
'age': 20,
},
'2': {
'name': 'Bob',
'age': 25,
},
}
@app.route('/users/{_id}')
def get_user(_id):
return USERS[_id]
ブラウザでhttp://127.0.0.1:8000/users/1
にアクセスしましょう
想定どおりid 1のユーザ情報が表示されました
これで(超)基本的なChaliceによるAPIサーバの立ち上げ方がわかりました
Amazon API Gateway, AWS Lambdaにデプロイ
ローカルで動かすことができたので、実際にウェブ経由でアクセスできるようにデプロイしましょう
(工事中)