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」です。
(中略)
簡単に言えばある URI にアクセスすることで、サーバ側の情報を書き換えたり、サーバ側に置かれた情報を取得できたりすることができるウェブシステムで、プログラムからアクセスしてそのデータを機械的に利用するためのものです。

Web API: The Good Parts

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つの基本原則を持ちます

  1. Uniform interface
  2. Client-server
  3. Stateless
  4. Cacheable
  5. Layered system
  6. 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のアカウントを作成しましょう

https://swagger.io/tools/

SwaggerHubにログインしたらCreate New APIから新規作成します

OpenAPI Versionは3.0.0を選択し、TemplateはNone、他は適当に入力してCREATE API
Auto Mock APIをONにしておくと、モックのAPIサーバーを立ててくれます

エディターとドキュメントが並んで表示されます

サーバ情報

まずはサーバ情報を記述します
infopathsの間に以下のように記述します

servers: 
  - url: https://api.exmaple.com/v1

urlは複数定義可能なので、開発サーバと本番サーバを分けることができます

機能の洗い出し

では実際にAPIの設計を行います
その前にクライアントアプリケーション側の画面と遷移を考えましょう

Web API: The Good Parts

このSNSアプリに必要な機能を洗い出すとこうなります

  • ユーザ登録
  • ログイン
  • 自分の情報の取得
  • 自分の情報の更新
  • ユーザ情報の取得
  • ユーザの検索
  • 友達の追加
  • 友達の削除
  • 友達の一覧の取得
  • 友達の検索
  • メッセージの投稿
  • 友達のメッセージの一覧の取得
  • 特定の友達のメッセージの取得
  • メッセージの編集
  • メッセージの削除
  • 友達の近況の一覧
  • 特定のユーザの近況の一覧
  • 近況の投稿
  • 近況の編集
  • 近況の削除

ここで操作対象となるデータはユーザ情報、近況情報、友達関係を表すソーシャルグラフの3つです
まずはユーザ情報に関するAPIの設計を行います

上述の必要機能からユーザ情報に関するものだけをまとめると以下です

  • ユーザ登録
  • 自分の情報の取得
  • 自分の情報の更新
  • ユーザ情報の取得
  • ユーザの検索

エンドポイントの設計

これらの機能に必要なエンドポイントを設計します
(「自分の情報の取得」も「ユーザ情報の取得」もIDを指定したユーザ情報を取得するという点で「特定のユーザの情報の取得」に統合できます)

目的エンドポイントメソッド
ユーザ一覧取得https://api.exmaple.com/v1/usersGET
ユーザの新規登録https://api.exmaple.com/v1/usersPOST
特定のユーザの情報の取得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

データ型の定義

と、上記のようにresponsesrequestBodyに一々データ型を書くのは面倒ですし何より事故の元になります

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にデプロイ

ローカルで動かすことができたので、実際にウェブ経由でアクセスできるようにデプロイしましょう

(工事中)

参考

コメントを残す