はじめに
叡智に富んだ読者の皆様、御機嫌よう。
フルスタックエンジニアにメガ進化したいすぎちゃんです!
ぶっちゃけ、リアルすぎちゃんの懐事情は険しくなってきつつありますw(仕事しろ)
そんな爪に火を灯すような生活を送る中でWeb開発を試みると、
どうしてもコスパとタイパを気にせざるを得ません。
先月、AWSのAurora ServerlessをDBとしてポートフォリオを作ろうと試みたのですが、何もしなくても月に2500円もぶっ飛んじゃうので泣く泣く閉鎖することになりました (お金に余裕があればまた公開するかも)
さて、今回の記事ではコスパとタイパの両立ができるサーバレスアーキテクチャを元にアプリケーションの作り方をレクチャーしたいと思います!
「サーバレス」とは
サーバー周りの構築・運用を一切気にする必要がないサービス形態のこと。もちろん月々のサーバー代のことも気にしなくて良い。従量課金制が一般的で、料金体系は柔軟で使った分だけ支払えばOK。
今回は東大式スクリーニングテストを元に「酒豪判定アプリ」のハンズオンを発信していきます!
アプリケーションの全機能の作成過程を一つの記事にまとめると長くなっちゃうので、以下のように複数の記事に分けて解説していきます!
本ハンズオンの構成
- DynamoDBに置いている質問項目を画面に表示しよう!⇦here
- 結果を送って酒豪か下戸かを判定してもらおう!
- 酒豪度を他の人と比較しよう!
このシリーズでは、AWSのサービスを利用してサーバーレスなアプリケーションを構築する方法を学んでいきます。以下の手順で進めていく予定ですので、ぜひ参考にしてください!
使用技術
フロントエンド
- node: v20.18.0
- npm: 10.8.2
- vue.ts: @vue/cli 5.0.8
環境作成はこちらの記事を参考にしてください
インフラ
- DynamoDB:Key-Value型のデータベースのこと。RDSと違いスキーマの定義を予めしておく必要がない
- Lambda:Web上で関数を実行できるサービス
- Amazon API Gateway:REST API, WebSocket APIなどを簡単に実行&管理ができるサービス。それぞれのAPIに対してテストも可能
1. DynamoDBの設定
1-1. 基本設定
テーブル名はquestionnaire_items
, パーティションキーはq_num
(データ型は数値)に設定してください!
その他はデフォルトのままで構いません。
1-2. データの投入
「返された項目」の右上にある「項目を作成」をクリックしてください。
そうすると以下のような画面が表示されます。
以下がquestionnaire_items
テーブルに設定するべき属性です。
statement
:お酒を飲んだ時の症状
choice_1
:「いつも出る」の点数
choice_2
:「時々出る」の点数
choice_3
:「出ない」の点数
項目の登録を設問の数だけ繰り返します。
一つ目の項目の設定が終わればチェックボックスをクリックし、右上の「アクション」>「項目の複製」をタップしてください。
そうすると一から属性を定義する必要がなく、値を入力するだけで項目の設定が完了します。
2. Lambdaの設定
2-1. 基本設定
Lambdaの一覧画面の右上にあるオレンジ色の「関数を作成」をタップしてください。
関数の作成画面の最上部に表示されている3つのオプションの中から「設計図の使用」を選択して下さい。
「設計図の使用」を選択すると設計図名、関数名の設定欄が表示されます。
設計図名のドロップダウンメニューでは「Create a microservice that interacts with a DDB table」を選択してください。今回は比較的シンプルなpython3.10でLambdaのコードを書いていきます。
実行ロールのブロックでは「AWSポリシーテンプレートから新しいロールを作成」を選択してください!
ロール名はわかりやすいようにDynamoDbReadAccess
に設定して、ポリシーテンプレートは「シンプルなマイクロサービスのアクセス権限」を選択しましょう。
上記の手順を踏まないと、Lambdaを実行する際にDynamoへのアクセスが許可されず正しいコードを実行してもデータを取得できません。
2-2. 関数の作成
import boto3
import json
print('Loading function')
dynamo = boto3.client('dynamodb')
def respond(err, res=None):
return {
'statusCode': '400' if err else '200',
'body': err.message if err else json.dumps(res),
'headers': {
'Content-Type': 'application/json',
},
}
def lambda_handler(event, context):
try:
# DynamoDBクライアントを作成
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('questionnaire_items')
# 全てのレコードを取得
response = table.scan()
items = response.get('Items', [])
# 'q_num'で並べ替え(昇順)
sorted_items = sorted(items, key=lambda x: x['q_num'])
# もしデータが無い場合
if not items:
return {
'statusCode': 404,
'body': 'No data found'
}
# データを返す
return {
'statusCode': 200,
'body': sorted_items
}
except Exception as e:
# エラーが発生した場合はエラーログを出力
print(f'Error processing data: {e}')
return {
'statusCode': 500,
'body': f'Error processing data: {str(e)}'
}
上記のコードを用いればDynamoDBに追加した項目一覧を取得することができます!
boto3.resource('dynamodb'): DynamoDBクライアントを作成する
dynamodb.Table('questionnaire_items'): questionnaire_items
テーブルを指定
table.scan(): questionnaire_items
テーブルに登録されたデータを全て取得
table.scan()
を実務で使うのはあまりお勧めできません。データ数が多くなればなるほど料金が嵩んでしまうからです。
2-3. 疎通確認
2-2のコードをlambda_function.pyに貼り付けたら、コードが正常に動作するか確認しましょう!
青色に点滅する「Test」ボタンをタップしてください。(途中、ウィンドウが何かしら出てくると思いますが、今回は何も設定しなくても大丈夫です!)
Execution resultに上記の画像のようにstatusCodeが200のレスポンスが表示されていれば、DynamoDBからデータが取得できてます!
3. API Gatewayの設定
3-1. APIの定義
API Gatewayの設定画面に遷移すると、APIタイプの選択が求められます。
- HTTP API
- WebSocket API
- REST API
- REST API プライベート
今回はVPC外からのアクセスが必要であること、ゲームのようにリアルタイム性も必要ないためREST APIを使っていきたいと思います。
「REST API」とは?
REST API は、REST アーキテクチャの制約に従って、RESTful Web サービスとの対話を可能にする アプリケーション・プログラミング・インタフェース (API または Web API) です。REST (Representational State Transfer) は、コンピュータ・サイエンティストの Roy Fielding によって作成された API の構築方法を定義する仕様であり、REST 用に設計された REST API (または RESTful API) は軽量で高速であるため、IoT (モノのインターネット)、モバイル・アプリケーション開発、サーバーレス・コンピューティングなどの先進的なコンテキストに最適です。
引用:Red Hat
選択するとREST APIの作成画面に遷移します。
ラジオボタンでは新しいAPIを選択し、API名はquestionnaire_items
と設定してください。APIエンドポイントタイプはデフォルトのままで大丈夫です。
REST APIの作成に成功すれば、以下の画像のようなリソースの設定画面に遷移します。
3-2. Lambda関数との紐付け
次に3-1で作成したAPI定義とlambda関数の紐付けについて解説します。
「メソッド」ブロックの右上に位置する「メソッドを作成」をクリックしてください。
クリックするとメソッド作成画面が表示されます。
今回はアンケート項目の一覧を取得するのでメソッドタイプはGETに設定して下さい。
統合タイプはLambda関数に設定したのち、赤枠で囲っているドロップダウンメニューにて2で作成したLambda関数を選択してください!
紐付けに成功すれば、Lambdaのメソッド詳細画面が以下のように表示されるはずです
スロットリングの設定
「いたずらでF5ボタンを連打されたり、悪意をもった誰かにDos攻撃を受けて高額請求がきちゃった…」という痛ましいことにならないよう、スロットリングの設定も念のため行ってください!
「スロットリング」とは
システムやアプリケーションに対して過剰なリクエストや処理の負荷を制限する仕組みのことです。過剰な負荷がかかると、サーバーがダウンしたり、全体のパフォーマンスが低下したりする可能性があるため、それを防ぐために使われます。
添付してる画像とは異なりますが、レート数は1、バースト数を100に設定しておけば、運悪く誰かに攻撃を喰らって請求書をみた時に卒倒してしまう事態は防げるだろうと思います!安全管理大事!
実際に東京オリンピックの公式サイトは4.5億回の攻撃を受けたという報告がありました。
API Gatewayでは100万リクエストあたり3.5ドル発生する(2024年10月現在のレートに換算すると500円)ので、これの450倍分の料金が発生することを考えると恐ろしいですね…。
(ざっと計算してみましたが20万円請求されるみたいです…吐血)
APIのデプロイ
APIのデプロイに成功すれば、下記のようなステージ詳細画面にアクセスできます。
APIエンドポイントが発行されているので、それをコピーして試しにブラウザのアドレスバーにペーストしてみましょう!
https://<ユニークなAPI識別子>.execute-api.us-east-1.amazonaws.com/questionnaire_items
上手くいけばブラウザに以下のように表示されるはずです!
成功したお方はおめでとうございます!
これでサーバレスなマイクロアーキテクチャの構築に成功しました
フロントエンドへのデータ読み込み
vue.js
それでは
Questionnaire.vue
<template>
<div class="home" style="text-align:left; max-width:720px; margin:auto;">
<div class="inputGroup mb-6">
<div v-for="(qstItem, idx) in questionnaireItems" :key="idx" class="pb-4">
<div class="questionHeader pb-2" style="text-align: left">
{{qstItem.statement}}
</div>
<div class="inputAnswer row">
<label class="answer col-4 px-2" :for="idx+'_a'">
<input class="answer-option" type="radio" :name="'q-'+idx" value=1 :id="idx+'_a'" v-model="userAnswers[idx]">
<span class="answer-text">いつも出る</span>
</label>
<label class="answer col-4 px-2" :for="idx+'_b'">
<input class="answer-option" type="radio" :name="'q-'+idx" value=2 :id="idx+'_b'" v-model="userAnswers[idx]">
<span class="answer-text">時々出る</span>
</label>
<label class="answer col-4 px-2" :for="idx+'_c'">
<input class="answer-option" type="radio" :name="'q-'+idx" value=3 :id="idx+'_c'" v-model="userAnswers[idx]">
<span class="answer-text">出ない</span>
</label>
</div>
</div>
</div>
<button @click="submit">submit</button>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import axios from 'axios'
export default class Questionnaire extends Vue {
public questionnaireItems: [] = []
public userAnswers: [] = []
public getQuestionnaireItems () {
return axios.get('https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/questionnaire_items').then((res) => {
this.questionnaireItems = res.data.body
})
}
<!---省略--->
mounted () {
this.getQuestionnaireItems()
}
}
</script>
CORSエラーの解消
API Gatewayで特に何も設定していなければVPC外からの接続を拒否してしまい、ローカル端末からのリクエストができなくなってしまいます。
そこでCORSの有効化を行えば、VPC外に存在する端末からアクセスが可能になります!
APIのリソース画面に移動し、リソースの詳細の右側に位置する「CORSを有効化する」をタップしてください。
タップすると以下のような設定画面が表示されます。
ゲートウェイのレスポンスでは全てのチェックボックスにチェックを入れ、
Access-Control-Allow-Methodsでは「GET」に必ずチェックを入れてください。
その他の設定はデフォルトままにしておき画面下部の「保存」ボタンをタップします。
「保存」ボタンをタップすると新規でOPTIONSメソッドが追加されているのを確認できます。
その後、画面右上の「APIをデプロイ」を選択し同じステージに再度デプロイすれば、端末からのAPIへのアクセスが許可されるようになります。
再び読み込みをかけたところ無事にDynamoDBから取得したデータが画面に表示されました!
お疲れ様でした!
つぶやき
初めてTypeScriptを使ってみたけどEslintさんがドラマに出てくる姑みたいに細かい…
お小言は愛の鞭と受け取っておこう…(吐血)