バックエンドも AWS も分からず Chalice を触った体験記【Python/React】
はじめに
これまでフロントエンドばかりやってきたので、バックエンドのことは全然知りません。REST API…?MVC…?なんとなく聞いたことはあるけど…そんなレベルです。
今回 AWS Chalice と React を用いて簡単な Todo-app の開発を行ったので、体験記として残します。
技術スタック
フロントエンド
- React + Vite
バックエンド
- Python
- AWS Chalice
- PostgreSQL
リポジトリ
本記事では一部コードを例示する箇所がありますが、全てこのリポジトリから抜粋したものです。全コードをご覧になりたい方は上記を参照ください。
やったこと
- PostgreSQL で DB を作成する
- Cognito でユーザーを管理する
- SES でメールを送信する
- S3 に画像をアップロード+ Cognito のユーザー情報を変更+不要な画像は適宜削除する
順を追って説明します。
1. PostgreSQL で DB を作成する
PostgreSQL で todo
DB を作成し、ライブラリを用いて Chalice 側から操作できるようにしました。
結局のところこれが一番難しかった気がします。バックエンド側の README にも書きましたが、インストールしたライブラリの半分くらいは DB のためでした。それほどやることが多く、難解でした…。
また、以下 3 つのデスクトップアプリをインストールしました。社用 PC には入れていたものもある(使っていたとは言っていない)のですが、私用 PC で使うのは初めてでした 👼 ちゃんと一通り触りました
- PostgreSQL
UI 上でテーブルを選択するだけでターミナルで SQL を起動できる - TablePlus
DB の中身を確認する - Postman
ローカルサーバーに向かって各種リクエストを送り、動作を確認する
2. Cognito でユーザーを管理する
Cognito に関しては長くなってしまったので別記事にまとめました。
サインイン、サインアップ、サインアウト、ユーザー削除及びユーザー属性の取得と更新について記述しています。
また、 todo
DB に user_id
カラムを追加して、Cognito のユーザー ID を入れるようにしました。
フロント側では、 Todo リストを取得する API を叩く関数の引数に Cognito から取得したユーザー ID を入れることで、 todo
DB から必要なデータのみを抽出して返すようにしました。
3. SES でメールを送信する
タスクが完了されると、フロントから送った user_id
をもとに Cognito でユーザー検索をして email
を取得し、タスクが完了されたことを知らせるメールが送信されるようにしました。
- todo_service.update_todo:タスクを更新する
- user_service.get_user_attribute:ユーザーを検索して
email
を取得する("cognito-idp", admin_get_user) - email_send_service.send_email:ユーザーにメールを送信する("ses", send_email)
Cognito でユーザー検索が一番しんどかったです。こちらのコードで落ち着きましたが、かなり遠回りをしました。
4. S3 に画像をアップロード+ Cognito のユーザー情報を変更+不要な画像は適宜削除する
プロフィールアイコンを S3 にアップロードし、登録し、必要に応じて S3 のデータを削除するようにしました。
- フロントエンドで選択した画像を file オブジェクトとしてバックエンドに送信
- file_service.upload_s3_file_image:S3 に保存("s3", upload_fileobj)
- user_service.get_user_attribute:
picture
属性を呼び出し、中身があれば(一度でもプロフィールアイコンアイコンを登録していれば)その値を保管("cognito-idp", get_user_attributes) - user_service.update_user_attribute:
picture
属性を更新("cognito-idp", admin_update_user_attributes) - file_service.delete_s3_file:3 に値があれば、該当するファイルを S3 から削除("s3", delete_object)
- 完了したとし、フロントエンドに 200 を返す
- フロントエンドで
revalidate
を行い、描画を更新
知ったこと
~/.aws/credentials
の居場所や中身の役割
$ aws configure
以上を用いて key, region, output を設定したはいいものの、ファイルがどこに設置されたのか見当たらず。先頭の ~
を見落としていてずっとプロジェクトのディレクトリ内で探していました…。 ~
だいじ。(これなんと呼ぶんだろう)
ここで設定した二種類の key で IAM ユーザーにアクセスし、 boto3 で用いるみたいです。
接続したい AWS のサービス毎に IAM ユーザーを作成+ポリシーをアタッチし、権限を付与する
セキュリティの観点から、サービス毎にアクセスキーを発行するユーザーを分散させることが推奨されていますね。今回はデモ開発だったのでここまでしなくともよかったかもしれませんが、学習のために倣いました。
- IAM ユーザーを作成する
- 作成した IAM ユーザーのアクセスキーおよびシークレットアクセスキーを発行する
- ポリシーを作成し、サービス毎に権限を与える(コード挿入する)
ここで権限を与えたものに限り、 chalice 上で呼び出すことができるようです(後述1)。いったんすべて*
でやってしまいました。 - ポリシーを IAM ユーザーにアタッチする
ポリシーがアタッチされた IAM ユーザーに限り、2 で作成したキーを Boto3 (後述2)で設定することで、 AWS のサービスにアクセス可能になります。
サービス毎に違いはあるのでしょうが、だいたいはこんな流れだと理解しました。
boto3
ライブラリを用いる
Chalice から AWS に接続するには、 以下のように用いるサービス毎に key とともに定義しておき、呼び出しやすくしました。 .aws/credential
に保管するより楽だと気づくのにかなり時間がかかってしまいました。
Boto3 を用いた AWS のサービスに接続する関数
環境変数として保管してあるのは、 IAM で発行した アクセスキーとシークレットアクセスキーです。前述の通り、サービス毎にユーザーを作成したので、全て値が異なります。
import boto3
s3 = boto3.client(
"s3",
aws_access_key_id=os.environ["AWS_S3_ACCESS_KEY"],
aws_secret_access_key=os.environ["AWS_S3_SECRET_ACCESS_KEY"],
region_name=os.environ["AWS_PROJECT_REGION"]
)
ses = boto3.client(
"ses",
aws_access_key_id=os.environ["AWS_SES_ACCESS_KEY"],
aws_secret_access_key=os.environ["AWS_SES_SECRET_ACCESS_KEY"],
region_name=os.environ["AWS_PROJECT_REGION"]
)
cognito = boto3.client(
"cognito-idp",
aws_access_key_id=os.environ["AWS_COGNITO_ACCESS_KEY"],
aws_secret_access_key=os.environ["AWS_COGNITO_SECRET_ACCESS_KEY"],
region_name=os.environ["AWS_PROJECT_REGION"]
)
# ...
作っておいた関数を使って実際に操作を行う
AWS 上で IAM ユーザーにポリシーがアタッチされていると、chalice 上で以下のように関数を呼び出せます。先ほどは *
で権限をまとめて与えてしまいましたが、 upload_fileobj
や send_email
等、個別で与えることもできるようです。より厳重に管理できますね。
s3.upload_fileobj(
...
)
ses.send_email(
...
)
cognito.admin_get_user(
...
)
# ...
こけたこと
Action
と Resource
は配列で指定する
ポリシーの 文字列だった Action
や Resource
を配列に修正すると動いたことが何度かあったので、そういうものなんだろうなとふんわり理解しました。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action":
[
"*"
],
"Resource":
[
"*"
]
}
]
}
file
ではなく fileobj
を用いる
S3 で画像ファイルをアップロードする時は これも詳しい理由は調べていないのですが、こっちだと動きました。
# エラーが出てしまう
s3.upload_file(
...
)
# こっちだとうごく
s3.upload_fileobj(
...
)
ExtraArgs
を指定する
S3 に置いた画像 URL は開くと勝手にダウンロードされてしまうので、アップロード時に Boto3 からアクセスする際に ExtraArgs
を指定することで、初めて URL から Web 上で画像を読み込めるようです。そっちがデフォルトではないんだ…
s3.upload_fileobj(
...,
ExtraArgs={"ContentType": "image/jpeg"}
)
やり残したこと
デプロイすること
試行錯誤しましたがもう諦めかけています。インフラってなんでこんなにややこしいんだ
感想
「バックエンド も DB も AWS もガチでマジでなんも分からん」から、「2%くらいは理解した」まで進化しました。ゼロを脱するのは大変ですが、何事もやってみるものですね。
たぶんこれからもフロントで食っていきます。
Discussion