新年なのでAWSのリソースだけで1年の目標管理マイクロサービスを作ったお話

久保田です。

2017年が始まりましたね。。。
早いもので今年の4月で3年目、今年はますます頑張りたいなと思います。

僕は毎年、年始に意識高く目標を立てているのですが、
だいたい3月くらいには忘れてしまっています。笑

なので今年は忘れないよう、目標管理をしてくれるマイクロサービス的なシステムを作ったので、書きたいと思います。
ぜひ真似してください。

今回はAWSのサービスのみで構築しました。構成は以下のような感じです。

f:id:AdwaysEngineerBlog:20170113150018p:plain

そういえばAWS、コンソール画面変わってましたね。これで伝わるのかな。。。

CloudWatchのスケジュール機能でLambdaの関数を動かし、3ヶ月に一度目標のメールを自分に送ります。
送られてくるメールは以下の画像のようなイメージです。

f:id:AdwaysEngineerBlog:20170113150108p:plain

そしてメールには目標とそれぞれの目標に対応したURLがあり、そのURLをクリックするとAPI GateWayにリクエストが送信され、Lambdaが動き、S3にある目標データが入っているjsonファイルを書き換えます。さらに目標が全て達成されたら、CloudWatchのスケジュールをdisableにします。

では早速作っていきます。

S3に目標データを作る

まずはS3にデータを置いておきます。
今年の目標を意識高く掲げておきましょう。

  • goal.json
{
  "1": "腹筋を割る",
  "2": "引っ越す"
}

このファイルをS3のどこかにuploadしておきます。

Lambda関数を作っておく

先にLambdaの関数を作っておきます。
先に作っておかないとCloudWatchのスケジュールが登録できないためです。

今回は、 S3から目標ファイルを読み込みメールを送る関数と、
API GatewayにリクエストがきたらS3の目標ファイルを更新する関数
の2つが必要となります。

適当に名前をつけてください。
僕は メールを送る関数を 「sendNotify」
更新する関数を 「clear」
としました。

実装はまた後で行います。

CloudWatchにスケジュールを登録する

次にCloudWatchにスケジュール登録します。

左ペインからイベントの下にある ルール を選び、以下のCron式を登録します。

0 10 1 3,6,9,12 ? *

そしてこのスケジュールに紐付けるLambda関数を選択します。

f:id:AdwaysEngineerBlog:20170113150131p:plain

API Gatewayでエンドポイントの作成

API Gatewayを使ってエンドポイントを作成します。
このエンドポイントにリクエストがきたらS3の目標ファイルを更新します。

今回は目標が達成されたら対象のデータを消すので、DELETEメソッドで作ります。
そして消す目標のIDをパスパラメータで受け取るようにします。

なのでリソースとメソッドは以下の画像のようになります。
(※目標更新の方のLambda関数を紐付けておいてください。)
(※ GETがある理由は後でわかります。)

f:id:AdwaysEngineerBlog:20170113151940p:plain

そしてマッピングテンプレートを設定し、パスパラメータをLambdaが受け取れるようにします。

f:id:AdwaysEngineerBlog:20170113150250p:plain

そしてDELETEなので、ステータスコードは204を返すようにしておきます。

メソッドレスポンスと統合レスポンスを編集します。

f:id:AdwaysEngineerBlog:20170113150231p:plain

f:id:AdwaysEngineerBlog:20170113150214p:plain

そしてデプロイしておきます。
デプロイしたら得られるエンドポイントをメモっておいてください。

これでDELETEメソッドは完成です。

次にGETメソッドの設定をしていきます。
なぜかというと、メールからURLをクリックした時に発行されるリクエストのメソッドはGETだからです。
なので、このGETを通って上で作ったDELETEメソッドに流します。

このようにしておきます。

f:id:AdwaysEngineerBlog:20170113151801p:plain

エンドポイントURLのところに上でメモしておいたエンドポイントを入れてください。

SNSの設定

Lambdaからメールを送る必要があるので、SNSにトピックを作ります。
ここも名前はなんでもいいです。僕は「goalNotify」としました。

購読の設定まで完了させておきます。

Lambda関数の実装

ここまでで型が完成したので、中身のLambda関数を作っていきます。
僕はPythonを使いました。

まずはS3から目標ファイルを読み込みメールを送る関数です。

import boto3
import json
import collections

BUCKET = 'バケット名'
KEY    = 'ファイル名'

def lambda_handler(event, context):
    # S3からファイルの読み込み、jsonからdict型のオブジェクトの生成
    s3 = boto3.client('s3')
    obj = s3.get_object(Bucket=BUCKET, Key=KEY)
    decoder = json.JSONDecoder(object_pairs_hook=collections.OrderedDict)
    jsonObj = decoder.decode(obj['Body'].read())
    

    # メール本文作成
    message = ""
    for k, v in jsonObj.iteritems():
        message += v + "\n" + " if you clear this goal, click under link. \n ここにエンドポイント"  + k + "\n\n"
    
    # SNSからトピックのarnを取得  
    sns = boto3.resource('sns')
    topic = sns.create_topic(Name="トピック名").arn
    # メール送信
    sns.Topic(topic).publish(
        Subject="goal of 2017.",
        Message=message,
    )


    return

次にAPI GatewayにリクエストがきたらS3の目標ファイルを更新する関数です。

import boto3
import json
import collections

BUCKET = 'バケット名'
KEY    = 'ファイル名'

def lambda_handler(event, context):
    # S3からファイルの読み込み、jsonからdict型のオブジェクトの生成
    s3 = boto3.client('s3')
    obj = s3.get_object(Bucket=BUCKET, Key=KEY)
    decoder = json.JSONDecoder(object_pairs_hook=collections.OrderedDict)
    jsonObj = decoder.decode(obj['Body'].read())
    
    # 対応するgoalIdがあったら消して更新
    if event['goalId'] in jsonObj:
        del jsonObj[event['goalId']]
        jsonStr = json.dumps(jsonObj)
        s3.put_object(Bucket=BUCKET, Key=KEY, Body=jsonStr)
        
        # もし全て完了したら、スケジュールを止める。
        if len(jsonObj) == 0:
            cloudwatch = boto3.client('events')
            cloudwatch.disable_rule(
            Name='sendNotify'
        )

        
    return

これで完成です。 試したい方はS3から目標ファイルを読み込みメールを送る関数を動かしてみてください。

さて、あとは3ヶ月後1つでも押せるように日々努力していくだけ、、、ですね!

今回は以上です。今年もよろしくお願いいたします。