https://www.seplus.jp/dokushuzemi/blog/2020/07/make_webapi_with_python_fastapi.html
今回参加したコースは Python /FastAPI でつくる WebAPI 入門 です。
Python といえば、基本情報技術者試験でプログラミング言語の選択肢に追加され、高校の情報Ⅱでも採用されるなど、C や Java ではなく Python でプログラミングデビューする方がドンドン増え、さらにメジャーな存在になっていますね。
その Python がよく使われる分野としては、AI/機械学習や統計が有名ですが、もちろんクラウド系のシステムや Web アプリケーションでも Python が活躍しています。
そこでは、サービスの機能をインターネット上で公開してほかのアプリケーションから呼び出して使ったり、モバイル端末や様々なデバイスのリクエスト/レスポンスに使ったり、複数のシステムを組み合わせたりするのに、 WebAPI という仕組みが使われます。
このコースでは、 WebAPI とは? から、よい WebAPI の設計指針まで学び、実際に Python と FastAPI で WebAPI で高速実装しました。これが 3 時間で実装できるのですから、本当に便利な世の中になりました。
では、コース内容をレポートします!
もくじ [hide]
1 社 / 1 部門 / 1 チーム 単位
月額 28,000 円~で社内のだれでも受講し放題に
コース情報
前提知識 | 基本的な Python プログラミングと Web アプリケーション開発の基礎知識がある方 |
---|---|
受講目標 | Python と FastAPI を使用した Web API の設計・実装がわかる |
講師紹介
このコースで登壇されたのは 米山 学さん です。
JavaはもちろんPython/PHPなどスクリプト言語、Vue/ReactなどJSだってなんだってテックが大好き。原点をおさえた実践演習で人気
過去には、「 1 日で習得する Python 入門 」「 C# や Java 開発者のための Python 入門」、「Python で ディープラーニング 入門」など、 Python の入門から応用までのコースで登壇されています。
WebAPI とは?
まずは WebAPI の概要が説明されました。
- WebAPI とは?
- Web テクノロジーを活用したシステム間連携のためのAPI
- 通信プロトコルとして HTTP を、リソースの識別として URI を使用
- 起源は 1970 年代の RPC( Remote Procedure Call )
- 最新の RPC は Google の gRPC
- 最新のマイクロサービスやサーバレスでも WebAPI が重要
- Web テクノロジーを活用したシステム間連携のためのAPI
- Web と WebAPI の違い
- 通常の Web の利用者は「人」
- HTML コンテンツを返す
- WebAPI の利用者は人でなく「プログラム」
- 返ってくるのは JSON や XML など
- 通常の Web の利用者は「人」
- WebAPI を実現する技術
- 現在では RESTful API がデファクト
- 最近では GraphQL, gRPC も徐々に普及してきている
WebAPI は、様々な企業やサービスが連携することで API Economy を生み出しています。
- API List 100+ に有用なものが公開されている
- Google Maps、Amazon、Yahoo!、Twitter などが公開
- WebAPI を外部に提供して新たなサービスを作ってもらう
- さらにそのサービスの API が使われていく
- IFTTT / Zapier など API を連携させるツールで RPA を行う例も増加
- 自社サービスを活用してもらうことで、さらに普及し発展する
なお、米山さんは、説明しながら Zoom で共有した資料に、赤で下線を引いたり囲んだり文字を書き込んだりしていきました。
動きのない図ではわからない、流れなどがとてもわかりやすくなりました。
RESTful アーキテクチャとは?
WebAPI を実現する技術としては、 RESTful Architecture が広く使われています。
- REST = REpresentative State Transfer の略称
- REpresentative (表現可能な) State (状態) Transfer (転送)
- Roy Fielding が 2000 年に論文発表
- システム間接続のセオリー集のようなもの
- 設計と実装の単純化と標準化を謳っている
- この REST を満たしたアーキテクチャを RESTful Architecture という
- ROA(Resource Oriented Architecture)を満たすことが必要十分条件
ROA とは?
- Resource
- 情報やデータのこと
- URI
- 名前 (URN) と 場所 (URL) を表す
- Endpoint (エンドポイント) という
- Representation
- データフォーマットのこと
- JSON などで表現されたもの
- Link
- 他の Resource との関連
CRUD と HTTPリクエスト
では、RESTful アーキテクチャで WebAPI で出来ることをどのように表現しているのでしょうか。
- CRUD ( Create / Read / Update / Delete の略) の 4 つで出来ることを分類
- HTTP リクエストで送受信する
- 動詞(操作= HTTPリクエスト・メソッド)+ 目的語となる名詞(リソース = URI )
GET/items/123
とあれば「 123 のアイテムを表示せよ」
CRUD処理(操作) | HTTPリクエスト・メソッド | 例 |
---|---|---|
CREATE(新規作成) | POST | POST /items ・・・ 新しいアイテムを作成する |
READ(取得) | GET | GET /items ・・・ アイテムの一覧を取得する |
GET /items/123 ・・・ 123番のアイテムを取得する | ||
GET /items/123/name ・・・ 123番のアイテムの名前を取得する | ||
UPDATE(更新) | PUT | PUT /items/123 ・・・ 123番のアイテムを更新する |
DELETE(削除) | DELETE | DELETE /items/123 ・・・ 123番のアイテムを削除する |
この表を見ると、ノリがわかりますね。
ブラウザにある開発者ツールを使って、 [ネットワーク] タブを見ると、出てくるのでお馴染みかも知れません。
WebAPI の設計ポイント
では、この RESTful アーキテクチャを使って、どのように WebAPI を設計(表現)すればよいのでしょうか。
リソースと URI の設計のポイント
まずは、WebAPI ユーザからのリクエストの設計です。
- ユーザに対して何を(何のリソース)提供するのか
- 許可する処理を決める
- 「誰でも」なのか「特定なのか」
- 「読むだけ」なのか「読み書き可能なのか」
- 読み書きであれば CRUD のうち、どれが OK なのか
- データフォーマットを決める
- 型情報ももちろん含まれる
- ログインなど CRUD で表現しきれないものは login など文字列を入れても良い
- 良い URI とは
- 名前から「どのような機能なのか?」「何を提供するのか?」がわかるように
- シンプルで短い
- .php など実装がわかるものは NG
- 命名ルールが統一されている
- キャメルケース、スネークケースなど
- これが不統一だと使いにくい
- 階層は深くなりすぎないように(だいたい 4 ~ 5 )
- バージョン番号がわかる
/api/v2/・・・
- クエリストリングを使ってフィルタリングやソートなどのオプションを表す
GET /tickets?sort=-priority # チケットのリストを priority の降順で取得する
- 名前から「どのような機能なのか?」「何を提供するのか?」がわかるように
当たり前ですが、ユーザとなる開発者にとって「使い方がわかりやすい」ということがポイントですね。
レスポンスの設計ポイント
続いて、リクエストに対するレスポンスはどのように設計すればよいのでしょうか。
- JSON が主流
- XML から 2012 年ごろに JSON が逆転 (Google トレンドから)
- 人間にもわかりやすく、軽量
- もともとは JS でオブジェクトを表すために開発されたもの
{ key:value }
で表現される
- ヘッダ / ボディでリソースを表現する
{ "productId": 1, "productName": "A green door", }
- どんな構造にするか、標準があるので参考にしよう
- json:api など
- レスポンスの中身
- GET はわかりやすい
- 要求されたデータを返すだけ
- POST / PUT / DELETE が難しい
- 空の JSON データを返すことが多い
- 変更されたデータを返す、というのも一つの手
- HTTP ステータスコードを使うのがベター
- 403
- 201 は作成成功のときのステータスコード
- クライアントが受け取ったデータを処理しやすいように
- 1 回で終わる
- 階層(ネスト)が深すぎるとツラい
- データの名前がわかりやすいように
- 名前は複数形で
- 使いやすいドキュメントを用意しよう
- JSON Schema にある例
{ "productId": 1, "productName": "A green door", "price": 12.50, "tags": [ "home", "green" ] }
- Swagger (Open API) がよく使われるようになっている
- JSON Schema にある例
- GET はわかりやすい
開発者にとって「使いやすさ」がポイントですね。
エラーとセキュリティの設計ポイント
- HTTPレスポンスの返送
- 適切なエラー・ステータス・コードを返す
- HTTP で規定されているコード:4xx(クライアント側)、5xx(サーバ側)
- レスポンス・ボディに適切なエラー・メッセージを設定
- HTMLを返送しないように注意(ユーザは JSON を期待している)
- 適切なエラー・ステータス・コードを返す
- エラー発生時にユーザが知りたい必要最低限の情報を付与
- 発生したエラーの種類/エラー・コード
- エラーの発生原因
- エラーの解決方法(詳細情報へのリンク)
- 例:
"errors"
の配列の中に、コード、メッセージ、参照 URL
セキュリティ設計も考える必要があります。
- 常に SSL 通信による暗号化を行う
- API キーを用いた認証機能とアクセス権限に基づくサービスの提供
- リクエスト情報(使用履歴)のトレース
- クォータ(呼び出し回数制限)の設定
Python / FastAPI で WebAPI サーバを書こう
ここから、 Python で WebAPI サーバを実装する実習となります。
まず、 Python とその代表的な WebAPI に特化したフレームワークが説明されました。
最近は Web アプリケーションフレームワークではなく、 API サーバを書くことに特化したフレームワークがあるのですね。知らなかった。。
たしかに、モバイルなど各種デバイスの登場で、バックエンドは API までが境界線になりつつありますよね。
- Flask
- FastAPI
- Django-REST-Framework
- Eve
- Hug
もともと、Web アプリケーションフレームワークでもある Flask がよく使われていたのですが、ここ数年は FastAPI が人気とのことです。
FastAPI とは?
- Flask に代わる人気のマイクロ・フレームワーク
- 高速
- モダンな機能
- Swagger や GraphQL に対応
- WebSocket にも対応
- Uvicorn (非同期処理対応) サーバで動く
動かしてみよう
今回は Visual Studio Code( VSCode )で演習します。
まずは Uvicorn などのインストールです。
> pip install fastapi uvicorn
続いて、おなじみ Hello, World してみましょう。
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/') # URI / にGETリクエストが来たときの処理
def get_hello():
return {'message': 'Hello from FastAPI Server!'}
if __name__ == '__main__': #コンストラクタ
uvicorn.run(app)
なかなかにわかりやすいですね。
では、コマンド実行してプログラムを動かしてみましょう!
> uvicorn hello:app --reload
ディレクトリをわけて開発
Web アプリケーションフレームワーク同様に、ディレクトリをきって役割を分割していきます。
Sample
|- routers
| |- user.py (エンドポイントのデータと処理を記述)
| :
|
|- app.py (サーバの boot など)
:
では、この構成に従って、 user.py を書きます。
- routers/user.py
from fastapi import APIRouter
router = APIRouter()
user_list = ['John', 'Bill', 'Eric']
@router.get('/users') # index の処理
def get_users():
return {'users': user_list}
続いて、サーバやプログラムの boot などを行う app.py を書きます。
- app.py
import uvicorn
from fastapi import FastAPI
from routers import user
app = FastAPI()
app.include_router(user.router)
if __name__ == '__main__':
uvicorn.run(app)
動かしてみましょう!
> uvicorn app:app --reload
CRUD 処理を書いてみましょう
では、CRUD の 4 つの処理を書いていきます。
- GET
- 個人ごとにデータを出すもの
- routers/user.py
from fastapi import APIRouter
router = APIRouter()
user_list = ['John', 'Bill', 'Eric']
@router.get('/users')
def get_users():
return {'users': user_list}
@router.get('/users/{index}') # この処理を追加
def get_user(index: int):
return {'user': user_list[index]}
無事に表示されました。
づついて、 POST (登録) の処理です。
- POST
- ユーザを登録する
- GET と違ってリクエストが投げにくい
- VScode の拡張を入れましょう
- REST Client がいい感じです
- user.rest というファイルを作って、リクエストが投げられる
CTRL+ALT+R
で実行できます
- routers/user.py
@router.post('/users')
def create_user(json: Dict):
user = json.get('user')
user_list.append(user)
return {}
では、 user.rest にリクエストを書いて実行してみます。
なお、POST はエンドポイントの下にリクエスト内容を JSON で書きます
POST http://localhost:8000/users
{"user" : "TEST"}
###
GET http://localhost:8000/users
無事に登録されました!
続いて、PUT(インデックス指定によるユーザーの更新)と DELETE (ユーザ削除) です。
@router.put('/users/{index}', )
def update_user(index: int, json: Dict):
user = json.get('user')
user_list[index] = user
return {}
@router.delete('/users/{index}')
def delete_user(index: int):
user_list.pop(index)
return {}
これも同様に実行してみます。
無事に動作しましたね。
FastAPI で型定義と検証
FastAPI では型定義と妥当性検証の機能があり、 Python のタイプヒンティングと組み合わせるだけで簡単に実装できます。これはFlaskでは提供されていない機能です。
その例として、新しいリソースを追加してみます。
- routers/book.py
from fastapi import APIRouter
# 型を使った定義やバリデーションを行う pydantic をインポート
from pydantic import BaseModel
from typing import List
router = APIRouter()
class Book(BaseModel): # クラスで型を定義
isbn: str
title: str
authors: List[str]
price: int
@router.post('/books')
def create_book(book: Book): # Book クラスに追加する create メソッド
return {
'isbn': book.isbn,
'title': book.title,
'authors': book.authors,
'price': book.price
}
- app.py
import uvicorn
from fastapi import FastAPI
from routers import user, book
app = FastAPI()
app.include_router(user.router)
app.include_router(book.router)
if __name__ == '__main__':
uvicorn.run(app)
- book.rest
POST http://localhost:8000/books
{
"isbn" : "978-1718501126",
"title" : "Learn Python",
"authors" : [
"Tim Arnold",
"Justin Seitz"
],
"price" : 5200
}
実行してみると、これも無事に登録できました。
Swagger を使ってみましょう
FastAPI にもともと含まれている機能で、 API のドキュメントが自動作成されるとのこと。本当ですか、マジですか(泣)
- 実装から UI が出てくる
- ドメインの URL に /docs で出てくる
- めっさ便利
- redoc
- ドメインの URL に /redoc で出てくる
- より見やすい!
キャー!ステキ!!
API のドキュメントを書いたことがあるのですが、実装との diff を埋めるのに泣きたく (ry
なんと、最近は Swagger で定義すると、コード生成までやってくれるって言うんですから、夢か。。
データベースからレスポンスを作ってみよう
今度はデータベースを使って、レスポンスを返してみましょう。
- RDBMS 以外にも NoSQL も使われる
- 今回は MongoDB を使ってみましょう
- MongoDB はドキュメント( JSON )を扱う
- mysql と cli は同じっぽい
では、実際 MongoDB にデータを投入していきます。
> show databases;
admin 0.000GB
config 0.000GB
local 0.000GB
> use test # db に無くても作れる。test データベースを作成
switched to db test
> db.users.insertOne({"_id": 1, "name": "John"})
{ "acknowledged" : true, "insertedId" : 1 }
> db.users.insertOne({"_id": 2, "name": "Eric"})
{ "acknowledged" : true, "insertedId" : 2 }
> db.users.insertOne({"_id": 3, "name": "Bill"})
{ "acknowledged" : true, "insertedId" : 3 }
> db.users.find()
{ "_id" : 1, "name" : "John" }
{ "_id" : 2, "name" : "Eric" }
{ "_id" : 3, "name" : "Bill" }
続いて、ドライバのインストールです。
> pip install pymongo
では、プログラムを書いていきましょう。
- user.py
from fastapi import APIRouter
from typing import Dict
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/') # MongoDB の接続
db = client.test
router = APIRouter()
# 前に書いた処理はコメントアウトされます
# user_list = ['John', 'Bill', 'Eric']
@router.get('/users/{index}')
def get_user(index: int):
# return {'user': user_list[index]}
return db.users.find_one({"_id": index})
@router.post('/users')
def create_user(json: Dict):
# user = json.get('user')
# user_list.append(user)
db.users.insert_one(json)
return {}
@router.put('/users/{index}', )
def update_user(index: int, json: Dict):
# user = json.get('user')
# user_list[index] = user
db.users.update_one({"_id": index}, {"$set": json})
return {}
@router.delete('/users/{index}')
def delete_user(index: int):
# user_list.pop(index)
db.users.delete_one({"_id": index})
return {}
ということで、また同じようにやってみると、、、
動きました~
これまでのところで、ほぼ 3 時間だったのですが、親切にこのあと実際に JavaScript で WebAPI を使うフロントエンドまでカバーしていた「おまけ」もざっと紹介してコースは修了しました。
まとめ
このコースでは、 WebAPI の基礎知識から、実際に Python / FastAPI で作って見るところまで 3 時間で研修しました。
資料は Web 形式で用意され、重要なキーワードにはリンクを貼り、あとから詳しく勉強するのにも便利なようになっていたことに加え、研修中は手元の Web ブラウザーからコピー&ペーストすれば動いて、オンライン研修でも WebAPI プログラミングを簡単に体験できました。
それにしても FastAPI は WebAPI に特化しているだけあって、ディレクトリで悩むこともなく、とても直感的にサッと書けるのが印象的でした(しかも API ドキュメントも自動生成)。
大量のリクエストをさばき、複雑なエッジケースまでカバーする大規模なアプリケーションを除けば、バックエンドはドンドン簡略化が進み、主戦場はフロントエンドに移っているのかも知れませんね。
0 コメント:
コメントを投稿