もういがみ合わない。Thunder Client と FastAPIで10分で作るスタブとAPI仕様書

## 共有地の悲劇

昔で言うところのクラサバ開発、今で言うところの外部API連携開発についてです。

クライアント開発とサーバ（外部API開発）は、制作内容が大きく異なり必要な専門知識や得意分野が異なるため、別の会社で行われることが多いと思います。

ここで、クライアント側は**サーバー側へ問い合わせてその応答を受けて処理を継続する**ことが多いと思いますが、サーバー側が存在しなければ開発ができない、だと、困ってしまいます。

そこでサーバーの応答をエミュレートするスタブが必要になります。

![クラサバ.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/b070d9ee-6713-ccc8-ec88-aa11998f7187.png)


参照：[wiki: スタブ](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%96) 
> スタブ（stub）とは、コンピュータプログラムのモジュールをテストする際、そのモジュールが呼び出す下位モジュールの代わりに用いる代用品のこと[1]。下位モジュールが未完成でも代わりにスタブを用いることでテストが可能になる。逆に上位モジュールの代わりに用いる代用品をドライバ（ソフトウェアの場合）またはコントローラ（ハードウェアの場合）と呼ぶ。ただし、仮のモジュールではなく正規のモジュールについてもドライバまたはコントローラと呼ばれることがあるので、区別するために「テストドライバ」や「サンプルドライバ」などと呼ぶことも多い。

しかし、**納品物として見ると必要のないもので**、なぜか見積もりに含められず、スタブなしで無理やり開発して、いきなり本物に繋げて疎通確認してそこから頑張るといったケースが散見されます。

また、外部連携のAPIを開発している側は、カジュアルにAPIの仕様を変えたくなることがあります。その様な時にAPIの仕様書の更新や連携が漏れると、クライアント側が動かなくなりますが、それに気づくのは大分時間が経ってからとなります。

この様な問題に長く悩まされてきましたが、もしも、**スタブの作成がとても簡単で、API仕様書が自動で生成されたら**どうでしょうか？

Python の FastAPI を用いるとおよそこの辺りの問題を解消できそうに思えましたので、導入方法などを紹介してみたいと思います。

## 構成

VS Code に Python の開発環境と、API試験用の Thunder Client をインストールし、Python の FastAPI でスタブコードを作成して確認するという構成となります。

![構成.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/9e2f29fc-3c85-80ce-dde9-5bf0a464f2a5.png)

## 準備

環境に以下をインストールしてください。

- [VS Code](https://code.visualstudio.com/download)
言わずとしれたコードエディター
- VS Code Extension
    - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
    Python開発用エクステンション。昔は環境作るのが大変でしたが、今はエクステンションで一発です。
    - [Thunder Client](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client)
    APIリクエストを投げられるエクステンション。リクエスト内容を json で export でき、共有可能。
[Postman](https://www.postman.com/)と似た使用感だが、VS Code から使えるので超便利です。
- [Python 3.9](https://www.python.org/downloads/windows/)
    - 最新版でもよいですが、依存モジュールが動作しないなどあるので…少し古いものにしています。

## スタブ開発

スタブ開発用に任意のフォルダを VS Code で開いて以下手順を実行してください。

### Python 環境の作成

VS Code でコマンドパレットで 環境の作成を選択し、Venv、Python 3.9.x を選択。
※Python のバージョンは任意です。

![000_Python環境の作成_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/01841ccd-09bd-859e-3f47-67610678bff0.png)
![000_Python環境の作成_01_Venv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/fff76fb3-5873-e237-5a63-7f9c62061738.png)
![000_Python環境の作成_02_version.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/a8842c3c-94af-7a51-dbfc-860a2ee6fe79.png)

開いているフォルダに、`.venv` というフォルダが生成されたら環境の作成は完了です。
上手く動作しない、Python のバージョンを切り替えたいなどの時は、`.venv` フォルダを削除して再度 Python 環境の作成を実行してください。

### Python ターミナルを作成

「Python 環境の作成」で作成した `.venv` を利用する VS Code のターミナルを作成します。
使い勝手は通常のターミナルと全く同じですが、`.venv` の設定が利用されているところだけが異なります。
以後、Python に関するコマンドはこのターミナルで実行することになります。

![010_ターミナルを作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/d1cfe252-86e9-f437-5b42-f21ccd20a42d.png)

### モジュールをインストール

Python ターミナルで以下実行します。
※パラメータが複雑なAPIを定義する場合は追加で対応するモジュールのインストールが必要になります。

```cmd
pip install fastapi
pip install uvicorn
```

### main.py を書く

`main.py` というファイルを作成し、以下のコードをコピペしてください。

```py
from fastapi import FastAPI, Query
from fastapi.openapi.docs import get_redoc_html

app = FastAPI()

@app.get("/test")
async def test(
    id: int = Query(..., description="ユーザーID", example="12345"),    # id
    name: str = Query(..., description="ユーザー名", example="Taro")   # 名前
):
    """
    関数コメントは、ReDoc に表示される。md 記法が利用可能。
    1行改行は、改行として認識されない。

    改行したい時は、<br>brタグを利用するか、2行改行する。
    """
    return { "message": f'Hello {id}:{name}!' }

@app.get("/redoc", include_in_schema=False)
async def redoc_html():
    return get_redoc_html(
        openapi_url="/openapi.json",
        title=app.title + " - ReDoc",
        redoc_js_url="/static/redoc.standalone.js",
    )
```

`id`,`name` を引数とし、`Hello {id}:{name}!` を返却する test という api を持ち、その API仕様書を出力するスタブサーバの全コードです。**とても短いですね。**

## スタブ実行

Python ターミナルで以下実行してください。

```cmd
# --reload : ファイル更新時に自動リロード
uvicorn main:app --reload
```

ブラウザで `http://localhost:8000/test?id=12345&name=Tanaka` にアクセスできたらスタブサーバの起動は完了です。

## API 仕様書

`http://localhost:8000/redoc` にアクセスしてください。
以下のようなページが表示されます。

**(図) ReDoc で出力されたAPI仕様書**
![100_redoc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/cd9b9873-fe17-15d8-87d2-118f83c286c5.png)

このレベルの仕様書を手書きしてメンテナンスするのはかなりコストがかかると思いますが、自動で生成されます。

## Thunder Client での疎通試験

Get メソッドはブラウザからも簡単に確認が可能でしたが、Post メソッドやファイル添付であったり、OAuth や Basic 認証等の試験はブラウザからは簡単にできません。

Thunder Client であればそれらの試験もVS Code上から簡単にできます。

**(図)左 : main.py、右 : Thunder Client、下 : Python ターミナル**
![110_thunder_client.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/6d459701-ccd9-b52c-d2f1-e67f006dfe08.png)

実行コマンドは export でき、「このパラメータでは動作した」「このパラメータでは動作しなかった」といったことも簡単に共有できます。

## おわりに

10分程度で、スタブサーバ、API仕様書、疎通確認結果を作成できることが分かりました。
Python や FastAPI がよく分からなくても、ChatGPT先生に聞きながらであれば、さくっと書けます。

外部連携、API仕様が共有地であることは変わらないですが、これらのファイルを受け渡しすることでより円滑な開発ができるかもしれません。

仕様書をいくら読んでも分からない事も、実際に動かしてみれる環境があれば動かして確認もできます。
このような、従来であればAPI仕様書の不備にヘイトが溜まったり問い合わせのコミュニケーションコストが発生したところを、未然に防げる仕組みになりうる、ということにはとても価値があると思います。

また、FastAPI は openapi の yaml も出力しています。それを用いてクライアントやサーバのコードを生成することもできますのでそれらも活用できるとよいかもしれません。

(参照)[Open API Generator : Generators List](https://openapi-generator.tech/docs/generators/)

## おまけ０

https://qiita.com/kaku3/items/75d98759609820595bc6

## おまけ１（新卒エンジニア向け手紙）

https://qiita.com/kaku3/items/2c8d4d783be7ce4fc9ea

https://qiita.com/kaku3/items/0bf1703cb8d6f84afbc5

## おまけ２（新卒エンジニア向け記事）

https://qiita.com/kaku3/items/fb949aa1a53f1f71c796

https://qiita.com/kaku3/items/aa2f81cf1e3974b8ad3a

https://qiita.com/kaku3/items/937354cc180c8bee823b

https://qiita.com/kaku3/items/b1c94328f273c750286b

## おまけ３（...は難しいシリーズ）

https://qiita.com/kaku3/items/f8411523cce000de750e

https://qiita.com/kaku3/items/5b11d2e1aace73c36340

## おまけ４（営業A短編シリーズ）

https://qiita.com/kaku3/items/03aae7b9e3c70c55f513

https://qiita.com/kaku3/items/3378ea55b1240d7360a1

## おまけ５（エンジニアのためのお仕事問題集）

2030年にIT人材が最大79万人不足するとのことで、学習用の資料をgitで**無料**公開してます(不定期更新)。
よろしければどうぞ。

[エンジニアのためのお仕事問題集](https://github.com/kaku3/workbooks)