はじめに
本稿は、
SvelteKit + FastAPI + vercel + heroku でやる気があれば誰でも簡単フルスタックエンジニア (前半戦)
の続きです。
前稿でやったこと
- svelteKitで、フロントエンドを実装した
- 実装したフロントのソースを、vercelにあげてネットに公開した。
後半戦では、サーバーの実装に触れていきます。
(そして、毛色がわかったら、それをフロントに繋いてみよう !)
準備
サンプルをダウンロード
こちらをクローンしてください!
もしくは、上級者の方は、
tiangoloさん (fastapi作者のヒゲ親父) が公開している、Full-Stack用のプロジェクトテンプレートを使った方が、いいかもしれません!
この記事では、サンプルを元に説明します。
ダウンロードしたら、まず、README.md
の指示に従って、ローカルの環境構築を行なってください!。
(サーバはまだ実行しなくていいです。$ uvicorn ....
の部分)
herokuに登録
前半戦での説明通り、herokuにサーバの実装をデプロイします。
ここで行うことは3つで、
- herokuに登録
- herokuで「アプリケーション」を作成
- herokuで、PostgreSQLのデータベースを無料で貸与
です。
↑から、アカウントを作成 & ログインからの、
◆ Create new appを押して、
適当なアプリケーション名とサーバの置き場所を指定して、[Create app]
これで、herokuの母体の準備はOK。
次に、Resource > Add-onの検索窓から、「Heroku Postgres」を探す。 > 決定
Hobbyプランが無料です。
登録すると、Heroku Postresのコンソールに行けるようになるので、
コンソール移動 > Setting > Database Credentials > View credential で、
DBへの接続情報をGETできます。ここのURI
を、サンプルソースコードの、/config/db_config.py
の、「ここにURI入れて!」部分に入れてください。
注意として、URIの先頭はpostgres: ですが、今回使うソースでは、postgresq;:と書き換えてください。
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine1 = create_engine(
"postgresql://...残りはそのまんま..."
)
pgadminのインストールと利用
DBの中身を覗くことは実は結構難儀します。そこで、pgadminを使って、可視化します。
この辺を参考にしつつ、pgadmin4
をインストール
Serverを右クリック > Create > Server
すると、設定画面が出てくるので、先ほどHeroku Postgresで出てきたCredentialの情報を入れていきます。
- Generalタブ
- Nameに、好きな名前を設定(pgadmin4上での、表示名になる)
- Connectionタブ
- Host name/address に、「Host」の記載を入力(ほとんどの場合、ec2-から始まるやつ)
- Portに、「Port」の記載を入力 (ほとんどの場合5432)
- Maintenance databaseに、「Database」の記載を入力
- Usernameに、 「User」の記載を入力
- Passwordに、「Password」の記載を入力。そしてセキュリティ意識高い系でもない僕たちのような人は、特別な理由がない限り、
こんなクソ長いパスワード忘れるので下のチェックマークをONにして、入力を省略できるようにしとこう
Saveを押すと、左メニューバーに作ったDB接続が表示されるので、プルダウンしていきます。
{🐘 作った名前} > Databases > {Maintenance databaseの名前までスクロール} > Schemas > Tables > 何も表示されないが正解
ちなみに、Databases移行に死ぬほどDBが並んでいるのは、Herokuが一つの接続に、複数のDBを登録して、その1つ1つをユーザに対して間借りする形でやってるからだと思われます。課金したら変わるのかな?
ここで、DB関連で、注意事項をいくつか。
- 低料金プランでは、最大接続数が決まっています。Hobbyはたった20接続しか確保されません。
- 「間借り」しているので、ちょっと重たいです。
- 登録できるレコードは、Hobbyで上限10000レコードです。上限を超えると、メール通知ののちに、1週間程度の猶予期間が始まり、そのあとは、消えるかなんらかの対処がなされます。
次に、用意しましたサンプルデータを突っ込みます。
./createtable.pyを実行すれば、あなたの用意したHeroku Postgres DBに勝手にサンプルデータを流し込みます。少量なので帯域とかは気にしなくて大丈夫。
python createtable.py
or
python3 createtable.py
そうすると、DBにデータを入れられるので、
早速、pgadmin4で確認してみましょう。
Table > どれかのテーブル(3つ入っているはず) > 右クリック > View/Edit Data > All Rowsで、
ユーザらしきものが2つ出れば成功です。
最後に、README.md
にも記載しております、起動シーケンスを実行して、サーバをあなたのPCで立ててみましょう。
uvicorn main:app --reload
を実行して、
INFO: Will watch for changes in these directories:
...
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [27454] using watchgod
INFO: Started server process [27456]
INFO: Waiting for application startup.
INFO: Application startup complete.
Application startup complete
が出たら、事前準備はこれにて終了です!
Fast API実装
お待たせしました。インフラ整備は、さっきのでほとんど終わりです。
いよいよサーバの実装です。
流し込んだデータを見てもらえればわかる通り、サンプルでは、Twitterっぽいシステムを構築しようとしています。
FastAPIの魅力はなんといっても、その早さ (実装のシンプルさ) と速さ (処理速度的な) です。
ヒゲ作者の方が、「Flaskを意識して作った」と言うとおり、コードが直感的でわかりやすいです。
では、それらの機能を、実装とともにみていきます。
サンプルを見れば、初心者の方も実装のイメージがつくと思いますので、頑張ってみていきましょう。
リクエスト/レスポンスの実装
リクエスト/レスポンス は、いわばAPIの窓口です。
窓口をしっかりしないと、APIというのはどんどん使いづらくなっていきます。
その点、FastAPIはかなり楽にリクエストの窓口を定義できます。
/api/twitter_modoki/__init__.py
の、このコードをみていきましょう。
twitterModokiRouter = APIRouter() #一旦ここは気にしない
# .(メソッド名) ('{パス}', response_model={レスポンスの形式})
@twitterModokiRouter.get('/tweet/list', response_model=List[TweetResponseModel])
def 全てのツイートを取得するAPI( # APIの関数名が、自動生成ドキュメントのAPIの説明文になります。
user_id: UUID = None, # クエリパラメータ。 型チェック(もしくは変換)・軽いバリデーションもしてくれる
offset: int = 0, # クエリパラメータ, デフォルト値を設定すると自動的にOptionalになる。
limit: int = 100, # クエリパラメータ
session: Session= Depends(get_session) # APIの開始時にget_sessionが呼び出され、終了時にはget_sessionのfinallyを実行する。
):
try:
.......
このコードで、以下の機能が実装されました
- http://****/tweet/list に窓口開設
- 窓口では
user_id
、limit
、offset
の3つのパラメータを任意で受け付けるし、型チェックや、軽いバリデーションなども行う。(user_idはUUID形式出ないとエラーを吐く。limit, offsetは、数値型になりうるならパラメータから勝手に数値型に変形する。) - 3つのパラメータは、任意である。(= *** と書くと、その値がデフォルトになり、パラメータがOptional・任意になる)
- 窓口は、レスポンスの形式をTweetResponseModelの配列で返すように約束。
- get_session関数(DBの接続を確保する関数、自作)を、レスポンスを返すまで取り回せるようにする。この例では、「APIが終わったら、DBの接続をきる」という実装がこれだけで済む。(詳しくは、get_sessionの実装を参照)
どうでしょうか。
これらの実装が、数行で済んでいることがまず驚きである。
では、サーバを起動して、
uvicorn main:app --reload
APIを呼んでみよう。
curl -X GET http://127.0.0.1:8000/twitter-modoki/tweet/list?user_id=12341234-1234-1234-1234-123412341234&offset=1&limit=10
or
(↑これは、Talend API Tester. 便利だよ.)
すると、こんなのが帰ってくる。
パラメータを指定しなければ、
curl -X GET http://127.0.0.1:8000/twitter-modoki/tweet/list
こんなふうにリストが帰ってくる。
これらは、データを「取得」するAPI、たいていHTTPメソッドは「GET」で定義される。
では、「POST」の実装はどうだろう。
# api/twitter-modoki/__init__.py
@twitterModokiRouter.post('/tweet')
def ツイートを1つ登録するAPI(
body: TweetRequestBody,
# POSTメソッドの場合、このようにbodyに格納される予定のjsonオブジェクトの定義と結びつけると、
# 勝手に必要な情報を構造体として抽出してくれる
session: Session= Depends(get_session)
):
try:
-----
# schema/request.py
class TweetRequestBody(BaseModel):
text: str = Field(....
user_id: UUID = Field(...
reply_tweet_id: Optional[UUID] = Field(....
GETと同じく、引数部分に何やらものを書くと、POSTメソッドで頻繁に使われる「BODYパラメータ」を取得できる。
POSTメソッドは、以下のように、BODYに構造体を定義してリクエストを送出する。
通信経路を通る際は、BODYはただの文字列と化すので、サーバ側はその文字列を構造体に直すのに難儀するはずだったが、この実装は、TweetRequestBody
の定義にそう構造をBODYから勝手に作り出し、body
に代入する。あれまー
サンプルでは、GET, POSTメソッドを使ったパターンを紹介したが、基本的にリクエスト/レスポンスのすべての実装は、これだけで賄える。
ドキュメント生成
fastapiの真なる強みとして、ドキュメントの自動生成機能があります。
やるべきことをやっていれば、ドキュメントの生成自体は非常に簡単です。
/docs
をつける。これだけ。
生成されたドキュメントの、/twitter-modoki/tweet/listの記述がこちら。
さっきの、リクエスト/レスポンスの実装をやっていれば、実務レベルにも耐えうる立派なドキュメントが完成します。これで残業しなくて済むね!
これには、見た目以上にたくさんのメリットがあります。
色々言い換えながら説明するなら、こんな感じでしょうか↓
- ドキュメントをわざわざ別で作らなくていい。
- サーバーの実装ができれば、即座にドキュメントが生成される
- フロントの実装者は、サーバと同じドメインにアクセスすればドキュメントが手に入る
- ドキュメントと実装にズレが生じることがない!!!!! (神)
デフォだと、swagger形式のドキュメントが生成されますが、
redocが好きな人は、/docs
じゃなくて、 /redoc
とすればそうなります。スゴイネー
また、ドキュメント自動生成機能は、もちろんOFFにすることも可能です。
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None) #オフにする
## ルーティング
apiのルーティングが簡単な方が、実装にも幅が出るし、バージョニングも楽ちんです。
FastAPIの、APIRouter
、include_router()
は、本当に楽ちんです。
from fastapi import FastAPI
from api.twitter_modoki.v1 import twitterModokiRouter as v1
from api.twitter_modoki.v2 import twitterModokiRouter as v2
app = FastAPI()
app.include_router(v1, prefix="/twitter-modoki", tags=['TwitterModoki'])
app.include_router(v2, prefix="/twitter-modoki/v2", tags=['TwitterModoki2'])
ちなみにmain.pyは、このサーバー実装のスタートポイントです。app
というのが全ての母体で、appに紐付ける形で、routerというのをどんどん繋げていきます。
prefix
の定義もできるので、APIをお手軽にグルーピングしたい場合は、
このサンプルみたく、バージョンでAPIを分けたい時にも役立ちます。
tags
は、自動生成ドキュメントのグループ名になります。
Dependenciesによる依存性注入
では、APIのv2バージョンで、v1よりセキュリティを強化したり、リクエストをしやすくしたりしましょう。依存性注入(DI)で。
DIってなに?って人も、多分実装を見れば言いたいことがわかります。
さて、v2では、簡単なセキュリティ要件に対応したようです。
- フロントは、HTTPヘッダーに、user_idと、認証キー(authorization)が必ず入れる。
- user_idがDBに存在するか、存在チェックをしないといけなくなった
- authorizationが違うなら、APIにアクセスできなくなった。
これらを、DIで実装しよう。
実は、ここまでにDIっぽい実装が一つ登場しています。
session: Session = Depends(get_session)
此奴です。この、Dependsというfastapiのモジュールが不可思議奇奇怪怪な存在で、DIの魔法に簡単に誘ってくれます。
@twitterModokiRouter.get(
'/tweet/list',
response_model=List[TweetResponseModel],
#これにより、2つの関数がAPI処理実行前に必ず行われるようになった。
dependencies=[Depends(required_header), Depends(required_authorization)]
)
def 全てのツイートを取得するAPI(
#user_idはHTTP Headerからとる。
# A = Header(...)で、ヘッダーからカラムAを勝手に探して取る。
user_id: UUID = Header(...),
offset: int = 0,
limit: int = 100,
session: Session= Depends(get_session)
):
try:
.......
次に、dependencies
に紐付けた2つの関数の実装をみてみよう。
from fastapi import Header, HTTPException
from sqlalchemy.orm.session import Session
from model.UserModel import UserModel
from config.db_config import SessionLocal
from uuid import UUID
def required_authorization(
authorization: str = Header(...)
):
if authorization == 'fast-api-token-barebare':
pass
else:
#キーが違うなら、リクエストをRejectする。
raise HTTPException(status_code=432, detail="Access invalid")
def required_header(
user_id: UUID = Header(...)
):
session: Session = SessionLocal()
try:
#get()によって、該当するidをもつユーザが0つ、あるいは2つ以上取れた時、エラーを吐く。
session.query(UserModel).get(user_id)
except:
#ので、リクエストをRejectできる。
raise HTTPException(status_code=432, detail="Access invalid")
finally:
session.close()
こんな感じ、単純に、要件にそうチェックを走らせている。
おそらく上級者の方は、「Header(...)ってこんなとこまで来ても中身引っ張れるんだ。。。」 と驚愕かもしれないが、できてしまうものはできてしまうのである。
これらが通れば、API関数の↓の部分
# A = Header(...)で、ヘッダーからカラムAを勝手に探して取る。
user_id: UUID = Header(...),
でHeaderからuser_idをとってもOKという感じである。
Headerに必要情報を記載して送信。これで、v2の要件達成である。
そして、この構文なら、
dependencies=[Depends(required_header), Depends(required_authorization)]
この記述を他のAPI関数にペチペチコピペするだけで、認証をかけたいAPIに同じ処理を実装することができるのです。
補足説明
初心者向けに、このサンプルが構成についての説明欄を設けました。
- /api
- 窓口です。窓口の説明や機能は、おそらく今までに紹介した機能で十分だと思います。
- /service
- /apiから、1:1対応で呼び出します。いわばAPIのメイン処理部分。
- /repositoryと組み合わせて、DBから欲しい情報を整理して、レスポンスの型に沿ったデータを作り出します。
- /model
- サーバでいうModelとは、データベースに入ってるテーブルの定義の写し鏡です。
- SQLAlchemyというORMマッパ(写し鏡をやってくれるライブラリ)を使っているので、ORMマッパのルール通りにModelを実装しています。
- /schema
- リクエスト、レスポンスの型を定義しています。/apiでも頻繁に使っているので、おなじみかと。
- /repository
- /DBにやらせたい操作を、汎用性持たせつつ書いてます。
- /serviceから共通パーツを切り分けた・と捉えてもいいでしょう。
- /config
- 各種設定。
やってみよう追加課題
- フォロー・フォロワーを実装しよう
- /tweet/listが、フォロワーの内容しか出てこないように実装しよう
- Twitter Modoki アプリのフロントを実装して、結合してみよう。
- OAuth2を使って、ログインの実装をしよう
## デプロイ
さて、最後の仕事です。実装をデプロイしましょう。
フロントエンドの時よろしく、自分のリポジトリにソースをあげたら、herokuのDeployメニューから、
あとはよしなにDeployの設定を行います。
とりあえずAutomatic deployの設定をして、
初回だけは、Manual deployを実行。
Procfile
(heroku用のコマンドソース)に、デプロイに必要な定義をしておいたので、あとはデプロイを待つだけ。
終わったら、
Setting > ちょっとスクロール >Domains に、ドメインが貼られてます。
http://localhost:8000/
の代わりです!!!!!!
やったぜ!!!!!
最後に
いかがでしたでしょうか!
sveltekit + fastapi 、2つのフレームワークの強力さを知らせることはもちろん、昨今のデプロイサービスは恐ろしいほど簡単に世界に配信をかけられてしまうので戦々恐々です。
====================
2021年、エンジニアを志す若者にとって、すごくいい時代です。
膨大なソースコード資産、俗にいう「巨人」の「肩の上」に成り立つツールで、
こんなにも快適で、簡単で、実現できる物事の幅も広い開発体験ができるのですから。
そして何より、簡単なフレームワークというのは、HTTP/TCPや、ブラウザや、その他ベースの基幹技術を理解する上での補助剤でもあります。
なので、誰に罵倒されようと、簡単で、楽で、面白くて、いいのです。
遠慮なく巨人の肩の上で、やりたいことやっちまいましょう。
最後に、ありがとうおまえたち!