お役立ち Twitter Bot を作りながら学ぶ AWS ドリル

~第 3 回 リファクタリング & 曜日ごとのツイート 編

2022-03-02
How to be a Developer

Author : 金澤 圭

ソリューションアーキテクト(SA)の金澤 (@ketancho) です。3 月になりましたが、皆さまいかがお過ごしですか ? 私は毎年 3 月に (駆け込みで) 健康診断・人間ドックを受けることが多いのですが、社会人になってから単調増加し続ける某数字と向き合わなければならないので、落ち込んでいることが多いです。おそらくこの記事が公開されるであろう日が健康診断当日なので、優しく声をかけていただければと思います。一番嬉しいお声は「AWS ドリルやりましたよ !」です。何卒よろしくお願いいたします🙏

さて、 前回の AWS ドリル #2 も多くの方にご覧いただくことができ、大変ありがたい限りです。前回に引き続き、実際に Twitter Bot を実装されている方も多く見受けられ、著者冥利に尽きるなーと思っています。今回も楽しい記事を丁寧に書ければと思っておりますので、皆さまも引き続き手を動かしていただき、#AWSウェブマガジン ハッシュタグをつけてフィードバックいただけるとありがたいです。

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 おはよう Bot 編
  • 第 2 回 昔書いた記事の宣伝 Bot 編
  • 第 3 回 リファクタリング & 曜日ごとのツイート 編
  • 第 4 回 新章突入 ! 気になるワード検索 & 通知 Bot 編
  • 第 5 回 皆さまの代わりに英語でツイートしておくよ Bot 編
  • 第 6 回 AWS Step Functions を使って Well-being Bot を作ろう ! (前編)
  • 第 7 回 AWS Step Functions を使って Well-being Bot を作ろう ! (後編)
  • 第 8 回 - 総集編 & 夏休みの自由研究のすすめ
  • 第 9 回 - IaC 入門しながら作るリマインダー Bot (前編)

1. この記事で作る Bot の機能と、とりあげる AWS サービス・機能

さて、今回も Twitter Bot を作りながら手に馴染んだ AWS サービス・機能を増やしていただければと考えています。今回のドリル #3 では、これまで作成してきたおはよう Bot のリファクタリングを行います。後ほど現時点でのソースコードを再掲しますが、lambda_handler メソッド内のコード量が増えてきたのと、Twitter API の Key / Token 情報をハードコーディングしたままなので、

  • 「Twitter API を呼び出してツイートする」部分をメソッドに切り出す
  • API Key / Token を「安全な格納先」から取得し、初期化処理を別メソッドに切り出す

という対応を進め、Lambda 関数の見通しをよくしたいと思います。「安全な格納先」と書きましたが、今回は AWS Systems Manager の Parameter Store という機能を利用します。Parameter Store は、パスワードをはじめとした機密データや、設定情報を安全に保存する機能です。

たまにソースコードに埋め込まれた機密度の高い文字列を誤って公開してしまった.. というインシデントを耳にすることがあると思います。この Parameter Store を利用することで、Lambda 関数に機密データをハードコーディングせずに済み、漏洩する文字列そのものをなくすことができます。

また、複数のプログラム・環境から同じ設定情報を参照・利用したい場面でも、この Parameter Store の利点が活きてきます。各所に文字列をハードコーディングしてしまうと、その文字列を変更したいときの作業が大変ですし、変更が漏れてしまうと障害に繋がるかもしれません。Parameter Store を利用することで設定情報を一元管理できるので、変更したいときはこの 1 箇所だけを更新すればよいのです。

このような背景から、実際のプロダクト開発でも Parameter Store を利用するシーンが多くあるので、今回のドリルで取り上げたいと考えました。ぜひこのドリルを通して、素振りをしていただければと思います。 なお、AWS には AWS Secrets Manager というシークレット情報を保護するサービスが別にあるのですが、こちらについてはこの記事の最後に落ち穂拾いとして紹介いたします。


2.「Twitter API を呼び出してツイートする」する部分をメソッド化する

それではここから今回のドリルを始めていきます。まずはこれまでの復習も兼ねて、現在の Lambda 関数のソースコードを見ていきましょう。前回は Amazon DynamoDB から「その日のメッセージ」を取得し、朝のツイートにそれを追加するという改修を行いました。

import json
import os
from requests_oauthlib import OAuth1Session
from datetime import datetime, timedelta, timezone

import boto3
 
dynamodb = boto3.resource('dynamodb')
twitter_bot_message_table = dynamodb.Table('TwitterBotDailyMessage')

JST = timezone(timedelta(hours=+9), 'JST')

# API Key, Access Token (本来、ハードコーディングするのは望ましくありません。連載の中で修正していきます。)
consumer_key = '*Input Your API Key*'
client_secret = '*Input Your API Key Secret*'
access_token = '*Input Your Access Token*'
access_token_secret = '*Input Your Access Token Secret*'

oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)

def lambda_handler(event, context):
    now = datetime.now(JST).strftime("%Y年%-m月%-d日")
    text = 'おはようございます!今日は ' + now + ' です。' # 変更

    date = datetime.now(JST).strftime('%Y%m%d')
    response = twitter_bot_message_table.get_item(Key={'Date': date})
    if('Item' in response):
        text = text + response['Item']['Message']

    payload = {'text': text}
    response = oauth.post(
        "https://api.twitter.com/2/tweets",
        json=payload,
    )
    if response.status_code != 201:
        raise Exception(
            "[Error] {} {}".format(response.status_code, response.text)
        )

現在、lambda_handler メソッドの中ですべての処理を行っており、コードが長くなってきました。そこで、下記の text を受け取ってツイートするコードブロックを tweet メソッドとして切り出そうと思います。

    payload = {'text': text}
    response = oauth.post(
        "https://api.twitter.com/2/tweets",
        json=payload,
    )
    if response.status_code != 201:
        raise Exception(
            "[Error] {} {}".format(response.status_code, response.text)
        )        

ツイート処理をメソッドに切り出した版のソースコードがこちらです。ほとんど変わらないように見えますが、今後はツイートする文字列を作るところまでを lambda_handler メソッドで行い、ツイート処理自体は tweet メソッドに任せることができます。このようにメソッドごとに役割分担しておくことで、ソースコード全体の見通しもよくなり、今後の機能追加がしやすくなると思います。

import json
import os
from requests_oauthlib import OAuth1Session
from datetime import datetime, timedelta, timezone

import boto3

dynamodb = boto3.resource('dynamodb')
twitter_bot_message_table = dynamodb.Table('TwitterBotDailyMessage')

JST = timezone(timedelta(hours=+9), 'JST')

# API Key, Access Token (本来、ハードコーディングするのは望ましくありません。連載の中で修正していきます。)
consumer_key = '*Input Your API Key*'
client_secret = '*Input Your API Key Secret*'
access_token = '*Input Your Access Token*'
access_token_secret = '*Input Your Access Token Secret*'

oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)

def lambda_handler(event, context): 
    now = datetime.now(JST).strftime("%Y年%-m月%-d日")
    text = 'おはようございます!今日は ' + now + ' です。'
    
    date = datetime.now(JST).strftime('%Y%m%d')
    response = twitter_bot_message_table.get_item(Key={'Date': date})
    if('Item' in response):
        text = text + response['Item']['Message']
    
    tweet(text)
        
def tweet(text):
    payload = {'text': text}
    response = oauth.post(
        "https://api.twitter.com/2/tweets",
        json=payload,
    )
    if response.status_code != 201:
        raise Exception(
            "[Error] {} {}".format(response.status_code, response.text)
        )

ここまで修正が終わりましたら、Lambda 関数のデプロイを行い、テスト実行しておきましょう。一気に大きな修正をするのではなく、細かく刻む方が早めに不具合や実装ミスを見つけられるのでおすすめです。

今回はツイート部分を切り出しただけなので、ツイート内容は変わりません。そのため、作業当日に既に Bot が動いているという方は、AWS ドリル #1 でも紹介した「同じメッセージは投稿できない」エラーが返ってくるかもしれません。その場合は、その日のツイートを消す、あるいはメッセージの内容を少し変えるなどして、テストを行うようにしてください。


3. Parameter Store を利用して API Key / Token をハードコーディングしないようにする

続いて、下記の API Key / Token をハードコーディングしている箇所のリファクタリングを進めます。コメントとしても書いていますが、このような機密度の高い情報をプログラムに含めるのはよくありません。

# API Key, Access Token (本来、ハードコーディングするのは望ましくありません。連載の中で修正していきます。)
consumer_key = '*Input Your API Key*'
client_secret = '*Input Your API Key Secret*'
access_token = '*Input Your Access Token*'
access_token_secret = '*Input Your Access Token Secret*'

oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)

そこで、AWS Systems Manager の Parameter Store を使い、これらの API Key / Token 情報をパラメータ化します。そして、そのパラメータを Parameter Store から取得する形に Lambda 関数のソースコードを変更することで、Lambda 関数自体には API Key / Token 情報が含まれない形に修正していきましょう。

3-1. Parameter Store のパラメータを作成する

まずはパラメータを作成するところから始めましょう。AWS Systems Manager の Parameter Store の画面 に遷移し、「パラメータの作成」ボタンをクリックしてください。

まず、パラメータに名前をつけます。今回は JSON 形式で Twitter に関する  4つのパラメータを格納するので、/credentials/twitter と命名します。また、SecureString として格納したいので、タイプを 安全な文字列 としてください。

クリックすると拡大します

続いて、値を格納します。今回はこのような JSON 形式で値を追加してください。(各項目は皆さまの API Key / Token 情報に置き換えてください。)

{
  "consumer_key": "*Input Your API Key*",
  "client_secret": "*Input Your API Key Secret*",
  "access_token": "*Input Your Access Token*",
  "access_token_secret": "*Input Your Access Token Secret*"
}

こちらの JSON を Parameter Store の値に入力いただき、右下の「パラメータを作成」をクリックしてください。

ここまででパラメータの作成が完了です。

クリックすると拡大します

3-2. IAM ロールに IAM ポリシーを追加する

続いて、Lambda 関数からパラメータを参照・・・していきたいところなのですが、前回の記事でも触れた通り、Lambda 関数から他の AWS サービス・機能を利用する場合は、その Lambda 関数に紐づく IAM ロールに適切な権限がついている必要があります。今回は先に IAM ロールを修正し、Parameter Store のパラメータを参照できるようにしておきます。

Lambda 関数 twitter-bot-hello の上部までスクロールし、タブを「設定」に切り替え、左側に表示されるメニューから「アクセス権限」を選択してください。すると、実行ロールが表示されるので、ロール名のリンクをクリックしてください。

クリックすると拡大します

IAM ロールの設定画面が前回の記事からアップデートされていますが、設定の流れは変わりません。「アクセス許可を追加」から「ポリシーをアタッチ」を選択してください。

クリックすると拡大します

IAM ポリシーは利用者が作成することができ、本来であれば必要最低限の権限に絞るべきなのですが、今回は標準で用意されている IAM ポリシーを使います。上部の検索窓に「SSMRead」と入力し、エンターを押してください。

クリックすると拡大します

すると IAM ロールがひとつに絞られるはずなので、「AmazonSSMReadOnlyAccess」の左側にチェックを入れ、「ポリシーをアタッチ」してください。

クリックすると拡大します

以上で IAM ロールに今回のドリルで必要な権限を追加できました。

3-3. Lambda 関数 twitter-bot-hello のソースコードを修正し、API Key / Token 情報を Parameter Store から取得する形にする

それではいよいよ Lambda 関数 twitter-bot-hello をアップデートします。今回も AWS SDK for Python のドキュメント を確認しながら実装を進めます。Parameter Store は AWS Systems Manager の機能の一部なので、今回は こちらの Systems Manager に関する部分 が該当箇所になります。

最初に実装方針を示し、最後にソースコードの完成版を載せたいと思います。もし「そろそろ AWS ドリルも慣れてきたし、力試ししたいな」という方は、実装方針だけ確認し、ご自身で実装を進め、答え合わせとして私のソースコードを確認してみてもいいかもしれません。(答え合わせと書きましたが、あくまで私の例は一例なので、必ずしも同じにしていただく必要はございません。)

まず、ドキュメントの冒頭にあるように、

import boto3

client = boto3.client('ssm')

という形で AWS Systems Manager のクライアントを作成します。import boto3 の部分は既に前回書いているので、クライアントを作る部分だけ追加すればよいでしょう。client という変数名はわかりにくいので、私は ssm_client と修正しようと思います。

ここで改めて今回修正したいソースコードを確認します。

# API Key, Access Token (本来、ハードコーディングするのは望ましくありません。連載の中で修正していきます。)
consumer_key = '*Input Your API Key*'
client_secret = '*Input Your API Key Secret*'
access_token = '*Input Your Access Token*'
access_token_secret = '*Input Your Access Token Secret*'

oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)

Key / Token 文字列を4箇所でハードコーディングしているので、ここを Parameter Store から文字列を取得し、代入する形に変更します。ここでは Parameter Store の get_parameter メソッド を使っていきましょう。

ドキュメントの該当箇所を見ると Name と WithDecryption という引数を利用できるようです。Name には、先ほど作成したパラメータの名前を入れます。そして、今回作成したパラメータは SecureString として作成したので、メソッドの WithDecryption を True にする必要があります。

response = client.get_parameter(
    Name='string',
    WithDecryption=True|False
)

また、Parameter Store を使う形に置き換えるだけでもいいのですが、私はこの部分を初期処理をする init メソッドとして切り出そうと思います。oauth 変数は、tweet メソッドでも利用するので、グローバル変数として残す必要がある点に注意が必要です。

このような実装方針で変更した私のコード例をお見せしたいと思います。

import json
import os
from requests_oauthlib import OAuth1Session
from datetime import datetime, timedelta, timezone

import boto3

dynamodb = boto3.resource('dynamodb')
twitter_bot_message_table = dynamodb.Table('TwitterBotDailyMessage')

ssm_client = boto3.client('ssm')

JST = timezone(timedelta(hours=+9), 'JST')

oauth = None

def lambda_handler(event, context): 
    init()
    
    now = datetime.now(JST).strftime("%Y年%-m月%-d日")
    text = 'おはようございます!今日は ' + now + ' です。'
    
    date = datetime.now(JST).strftime('%Y%m%d')
    response = twitter_bot_message_table.get_item(Key={'Date': date})
    if('Item' in response):
        text = text + response['Item']['Message']
    
    tweet(text)
    
def init():
    response = ssm_client.get_parameter(
        Name='/credentials/twitter',
        WithDecryption=True
    )
    twitter_parameters = json.loads(response['Parameter']['Value'])

    consumer_key = twitter_parameters["consumer_key"]
    client_secret = twitter_parameters["client_secret"]
    access_token = twitter_parameters["access_token"]
    access_token_secret = twitter_parameters["access_token_secret"]
    
    global oauth
    oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)
        
def tweet(text):
    payload = {'text': text}
    response = oauth.post(
        "https://api.twitter.com/2/tweets",
        json=payload,
    )
    if response.status_code != 201:
        raise Exception(
            "[Error] {} {}".format(response.status_code, response.text)
        )

こちらで再度テスト実行をしてみましょう。Bot は変わらず同じ動きをしていますでしょうか ? 以上でリファクタリング完了です🎉


4. 曜日ごとのメッセージを Parameter Store に保存し、毎日違うツイートをするように変更する

さて、ここまででリファクタリングが無事に終わり、ソースコードの見通しもよくなったのですが、「リファクタリング」なので当然ですが、Bot は 前回のドリル #2 と同じ動きのままのはずです。この連載を始めるときに、「毎月何かしら Bot の動きが変わるようにしたい ! その方が楽しいはずだ !」と考えていたので、このままでは私の目標が達成されません。

そこで、もう少しお付き合いいただき、「Parameter Store を利用し、曜日ごとに違う文言をツイートする」という追加実装をしていこうと思います。月曜日にはこの挨拶、火曜日にはこの挨拶、というイメージです。

前回の記事では、DynamoDB に日付ごとの項目を作成し、毎日異なる文言をツイートしましたが、それに加えて Parameter Store に格納した曜日ごとのメッセージも間に入れていただく形にしてみます。

クリックすると拡大します

この実装をする上で必要な知識はここまでの記事で網羅できているので、この最後のパートは皆さまの腕試しにしていただこうと考えています。ドキュメントを参照しながら各自実装を進めていただければと思います。曜日ごとのメッセージ JSON のサンプルだけ先にお渡ししますので、これを皆さまならではな文言に変更し、Parameter Store に /messages/twitter というパラメータ名で格納してみてください。この情報は機密データではないので、通常の文字列としてパラメータを作成してみてください。

{
  "Monday": "週のはじまり月曜日!無理せずいきましょう!",
  "Tuesday": "まだまだ前半戦の火曜日!無理せずいきましょう!",
  "Wednesday": "週の折返し水曜日!疲れが出る頃なので無理せずいきましょう!",
  "Thursday": "あともう少しでお休み木曜日!無理せずいきましょう!",
  "Friday": "あと1日です、金曜日!華金なので無理せずいきましょう!",
  "Saturday": "土曜日!最高ですね!",
  "Sunday": "日曜日!最高ですね!"
}

それでは少し時間を取って、開発を進めてみてください ! 💪

いかがでしょうか ? うまく実装できましたでしょうか。あくまで例となりますが、最後に私のソースコードを共有したいと思います。(また lambda_handler メソッドがボリューミーになってしまいましたので、「ツイート文言を作る」部分もメソッドに切り出してもいいかもしれません。)

import json
import os
from requests_oauthlib import OAuth1Session
from datetime import datetime, timedelta, timezone

import boto3

dynamodb = boto3.resource('dynamodb')
twitter_bot_message_table = dynamodb.Table('TwitterBotDailyMessage')

ssm_client = boto3.client('ssm')

JST = timezone(timedelta(hours=+9), 'JST')

oauth = None

def lambda_handler(event, context): 
    init()
    
    now = datetime.now(JST).strftime("%Y年%-m月%-d日")
    text = 'おはようございます!今日は ' + now + ' です。'
    
    weekday = datetime.now(JST).strftime("%A")
    response = ssm_client.get_parameter(
        Name='/messages/twitter'
    )
    twitter_messages = json.loads(response['Parameter']['Value'])
    text = text + twitter_messages[weekday] + '\n\n'

    date = datetime.now(JST).strftime('%Y%m%d')
    response = twitter_bot_message_table.get_item(Key={'Date': date})
    if('Item' in response):
        text = text + response['Item']['Message']
    
    tweet(text)
    
def init():
    response = ssm_client.get_parameter(
        Name='/credentials/twitter',
        WithDecryption=True
    )
    twitter_parameters = json.loads(response['Parameter']['Value'])

    consumer_key = twitter_parameters["consumer_key"]
    client_secret = twitter_parameters["client_secret"]
    access_token = twitter_parameters["access_token"]
    access_token_secret = twitter_parameters["access_token_secret"]
    
    global oauth
    oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)
        
def tweet(text):
    payload = {'text': text}
    response = oauth.post(
        "https://api.twitter.com/2/tweets",
        json=payload,
    )
    if response.status_code != 201:
        raise Exception(
            "[Error] {} {}".format(response.status_code, response.text)
        )

以上で今回のドリルは終了です。お疲れさまでした ! 🎉

5. 落ち穂拾い & 参考情報

5-1. Parameter Store と AWS Secrets Manager

今回は機密データの格納先として Parameter Store を利用しましたが、AWS には AWS Secrets Manager というデータベースの認証情報や API キーなどの機密データを格納する別のサービスがあります。AWS Setrets Manager には、シークレットをローテーションさせるという Parameter Store にはない機能もあり、必要に応じて使い分けていただけます。また、AWS Setrets Manager から Parameter Store のシークレットを参照する といった連携も可能です。

今回の実装については、Parameter Store / AWS Secrets Manager どちらでも実現可能なのですが、今回の利用の仕方であれば Parameter Store の方が安価なので、という理由で選定しています。AWS Secrets Manager も 30 日の無料トライアルがあるので、あわせてお試しいただいてもいいかもしれません。この 2 つのサービス・機能の使い分けについては こちらのよくある質問 にまとまっているので、実際のプロジェクトでご利用いただく際には参考にしてみてください。

「無料トライアル」と書きましたが、AWS には実際に機能をお試しいただくための 無料利用枠 が設定されているサービスが多くあります。これまでの記事でも利用してきた AWS Labmda や Amazon DynamoDB にもこの無料枠の設定があり、なるべくそれを活用できるようにしています。ご興味のある方はこの無料枠のページもせひご参照いただければと思います。

5-2. AWS Lambda のコールドスタート / ウォームスタート

続いて、AWS Lambda における「コールドスタート」と「ウォームスタート」について紹介したいと思います。これまでのドリルでは言及してこなかったのですが、AWS Lambda を使う上でこの考え方はぜひご理解いただきたいと思っており、AWS Lambda に皆さまが慣れてきたであろうこのタイミングで紹介したいと考えています。

(後述するのですが、ここまでのドリルの実装だと Lambda 関数が 1 日に 1 回しか動かないので、基本的には「コールドスタート」になるはずです。このあとの話がやや長めなので、お腹いっぱいになりそう・・・という方はまた必要なときに戻ってきていただき、この記事としては「Next Action におすすめのハンズオン」まで飛ばしていただいても構いません。)

まず、前回の AWS ドリル #2 のソースコードと今回の #3 のソースコードを、API Key / Access Token を使って OAuth セッションを作る部分に絞って再掲してみます。改めて違いを確認してみてください。

AWS #2 ドリルにおけるソースコードの最終形 (一部抜粋)

# 省略

# API Key, Access Token (本来、ハードコーディングするのは望ましくありません。連載の中で修正していきます。)
consumer_key = '*Input Your API Key*'
client_secret = '*Input Your API Key Secret*'
access_token = '*Input Your Access Token*'
access_token_secret = '*Input Your Access Token Secret*'

oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)

def lambda_handler(event, context): 
    now = datetime.now(JST).strftime("%Y年%-m月%-d日")
    text = 'おはようございます!今日は ' + now + ' です。'
    
# 省略

AWS #3 ドリルにおけるソースコードの最終形 (一部抜粋)

# 省略

oauth = None

def lambda_handler(event, context): 
    init()
    
# 省略 
    
def init():
    response = ssm_client.get_parameter(
        Name='/credentials/twitter',
        WithDecryption=True
    )
    twitter_parameters = json.loads(response['Parameter']['Value'])

    consumer_key = twitter_parameters["consumer_key"]
    client_secret = twitter_parameters["client_secret"]
    access_token = twitter_parameters["access_token"]
    access_token_secret = twitter_parameters["access_token_secret"]
    
    global oauth
    oauth = OAuth1Session(consumer_key, client_secret, access_token, access_token_secret)
   
# 省略 

ドリル #2 では、oauth の宣言、作成 (代入) を lambda_handler メソッドの外側で行っているのに対し、ドリル #3 では oauth の宣言のみを lambda_handler メソッドの外側で行い、OAuth セッションの作成 (代入) は (init メソッドを経由して) lambda_handler メソッドの内側で行っている、という違いがあります。

AWS Lambda は、Lambda 関数が呼び出されたときに、プログラムが動作する実行環境を作成します。Lambda 関数の処理が終わったあと、その実行環境はしばらく残り続け、同じ Lambda 関数が呼び出されたときに実行環境が再利用されることがあります。この実行環境が再利用されることを「ウォームスタート」といいます。逆に、ウォームスタートできる実行環境がないときは、プログラムが動作する実行環境を新たに作ることになるのですが、これを「コールドスタート」といいます。1つの実行環境は同時に1つのリクエストしか処理できないので、例えば並列に3つのリクエストがきたときに、再利用できる実行環境が2つあるときは、2つのリクエストはウォームスタート、残りの1つのリクエストはコールドスタートといった形になります。

この話をここでしたのは、ウォームスタートしたときに Lambda 関数の中で実行される部分とされない部分があるからです。詳細は AWS Lambda 実行環境に関するドキュメント に掲載されているのですが、このドキュメントから一部を抜粋したいと思います。

関数とすべての拡張機能が完了した後、Lambda は別の関数の呼び出しを想定して、実行環境をしばらく維持します。実際には、Lambda は実行環境をフリーズします。関数が再び呼び出されると、再利用のため、Lambda によって環境が解凍されます。実行環境の再利用には、次のような意味があります。

・関数ハンドラーメソッドの外部で宣言されたオブジェクトは、初期化されたままとなり、関数が再度呼び出されると追加の最適化を提供します。例えば、Lambda 関数がデータベース接続を確立する場合、連続した呼び出しでは接続を再確立する代わりに元の接続が使用されます。新しい接続を作成する前に、接続が存在するかどうかを確認するロジックをコードに追加することをお勧めします。

つまり、ウォームスタートした場合は、ハンドラーメソッド (ドリルの lambda_handler メソッド) の外部に記載された処理は実行されない (オブジェクトは使い回される) ということです。例えば、下記のようなコードであれば、

# 処理 X

def lambda_handler(event, context):
    # 処理 Y

Lambda 関数が呼び出されるたびに処理 Y は (コールドスタート / ウォームスタート問わず) 実行されるのに対し、処理 X はコールドスタートのときしか実行されません。

さて、ここまでの AWS ドリルでは、以下の背景・方針で各処理をハンドラーメソッドの外側に書くか、内側に書くかを考えていました。

  • ライブラリのインポートや、AWS SDK の AWS リソースクライアントの作成、そして OAuth セッションの作成は、Lambda 関数が呼び出されるたびに実行する必要はないので、ハンドラーメソッドの外側に記述し、ウォームスタートのときは使い回されるようにする (ただし、Amazon EventBridge における実行間隔の設定が 1 日に 1 回になっているので、Lambda 関数がウォームスタートすることは基本的にはないはずです)。
  • 逆に、現在時間の取得や、DynamoDB テーブルから項目を取得する部分については、Lambda 関数の実行の都度行いたいので、ハンドラーメソッドの内側に記載する。

しかし、今回のドリル #3 では、OAuth セッションの作成をハンドラーメソッドの外から中に移しています。その理由は下記の通りです。

  • ドリル #2 までは、API Key / Token をハードコーディングしており、これらの情報が変わる場合は Lambda 関数もデプロイし直しになる (デプロイすると次回はコールドスタートになります) ため、ハンドラーメソッドの外側で定義し OAuth セッションを (使い回せるなら) 使いまわせるようにしたかった。
  • 今回のドリル #3 で API Key / Token のハードコーディングをやめ、Parameter Store に格納する形に変更しました。そのため、Lambda 関数の再デプロイなしに API Key / Token が変わる可能性が出てきます。つまり、Lambda 関数がウォームスタートした場合に、古い API Key / Token を使い続けてしまう可能性があるため、Parameter Store から API Key / Token を取得する処理をハンドラーメソッドの内側に移すことで、毎回 (ウォームスタートしたとしても) Parameter Store から最新の API Key / Token を取得する形にしたい。

基本的に API Key / Token は変わらないことが多いので、それを毎回 Parameter Store から取得するのはイマイチとも言えるかもしれません。特に、少しでも Lambda 関数の実行時間を短くしたい場合は、この取得処理はハンドラー外に持っていきたい気もします。もし、古い API Key / Token を使ってしまった場合は、Twitter API 呼び出し時にエラーが発生するので、その例外処理の中で Parameter Store から API Key / Token を取り直し、Twitter API の呼び出しをリトライする、という方法でエラーを回避することもできます。

今回の AWS ドリルでは、ソースコードをなるべくシンプルにしたいという想いがあるので、例外処理としての実装ではなく「処理をハンドラーの中に持ってくることで、毎回最新の API Key / Token を取得し、古い情報を使わないようにする」という方針としました。API Key / Token が更新されたときに例外処理するバージョンを作ってみるのも勉強になると思いますので、ご興味のある方はぜひ実装してみていただければと思います。

大切なのは、この AWS Lambda のコールドスタートとウォームスタートについて理解し、皆さまの実務で AWS Lambda を利用する際には、設計・開発の際にハンドラー内で実装した方がいいか、外で実装した方がいいかを考えていただくことだと思います。この記事としては、「設計・開発のときにここは考えた方がよさそう」くらいの情報として、頭の片隅に覚えておいていただければと思います。AWS Lambda のパフォーマンスについては、「Operating Lambda: パフォーマンスの最適化」という AWS ブログの記事も参考になるので、詳しく学ばれたいという方はぜひ参照していただければと思います。

5-3. Next Action におすすめのハンズオン

「落ち穂拾い」としているのに長々と書いてしまいました。最後に Next Action におすすめな関連ハンズオンを紹介し、落ち穂拾い & 参考情報のコーナーを終えたいと思います。

今回のドリルでは、Parameter Store を利用し、セキュリティを意識したリファクタリングを行っていただきました。

セキュリティの面で AWS を利用する上で抑えておきたい考え方や、最初にご検討いただきたい設定をお伝えする「アカウント作成後すぐやるセキュリティ対策」ハンズオンHands-on for Beginners シリーズ として公開しています。今回のドリルを面白いと思っていただいた方は、ぜひ Next Action として、このセキュリティハンズオンもご視聴いただければ嬉しいです。

「アカウント作成後すぐやるセキュリティ対策」ハンズオンの申し込みはこちら »

また、今回は AWS Systems Manager の機能として Parameter Store のみをご利用いただきましたが、AWS Systems Manager には他にも多くの機能がございます。

動画形式のハンズオンとして「AWS Systems Managerを使ったサーバ管理はじめの一歩編」ハンズオンも先日公開されています。サーバーへのリモート接続や一括コマンド実行といった機能群を試すことができるコンテンツです。ご興味のある方はこちらのハンズオンもお試しいただき、AWS Systems Manager について知見を深めていただければと思います。

「AWS Systems Managerを使ったサーバ管理はじめの一歩編」ハンズオンの申込みはこちら »


6. まとめ

「お役立ち Twitter Bot を作りながら学ぶ AWS ドリル」連載の第 3 弾として、これまでのソースコードのリファクタリングを進めました。その中で、API Key や Token を AWS Systems Manager の Parameter Store に格納していただきました。あわせて、Parameter Store に曜日ごとのメッセージも格納し、曜日によって違うメッセージを投稿するという実装も行っていただきました。

記事の中でも触れましたが、Parameter Store は実務の中でよく使われるサービスですので、このドリルを通して利用の流れを経験していただき、いざお仕事で使う際には「私、Parameter Store 使ったことあるんで何でも聞いてくださいね (最高の笑顔)」と周りの方をリードしていただけると嬉しいです。

最後に改めてのお願いなのですが、皆さまのご感想・ご要望を #AWSウェブマガジン タグをつけて呟いていただけたら嬉しいです🙏 この記事に +α して、こんなものを作ってみました ! という共有も大歓迎です。今後の連載の参考にさせてもらいたいと思っておりますので、ぜひよろしくお願いしますー !

それでは次回の記事でまたお会いしましょう !

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 おはよう Bot 編
  • 第 2 回 昔書いた記事の宣伝 Bot 編
  • 第 3 回 リファクタリング & 曜日ごとのツイート 編
  • 第 4 回 新章突入 ! 気になるワード検索 & 通知 Bot 編
  • 第 5 回 皆さまの代わりに英語でツイートしておくよ Bot 編
  • 第 6 回 AWS Step Functions を使って Well-being Bot を作ろう ! (前編)
  • 第 7 回 AWS Step Functions を使って Well-being Bot を作ろう ! (後編)
  • 第 8 回 - 総集編 & 夏休みの自由研究のすすめ
  • 第 9 回 - IaC 入門しながら作るリマインダー Bot (前編)

builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

金澤 圭 @ketancho
アマゾン ウェブ サービス ジャパン合同会社
技術統括本部 ソリューションアーキテクト

サーバーレスが好きなソリューションアーキテクト。業種業界問わず、お客様のプロダクト開発をサポートさせていただいています。「AWS Hands-on for Beginners」というオンデマンドで視聴できるハンズオンも企画・推進しており、楽しく学べるコンテンツを日々考えています。好きなサービスは AWS Lambda、AWS Step Functions、Amazon Personalize で、好きな休日の過ごし方は娘ふたりと川の字になって昼寝👧👧と赤ん坊を抱っこしながらの散歩です👶

AWS のベストプラクティスを毎月無料でお試しいただけます

さらに最新記事・デベロッパー向けイベントを検索

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する