概要
- AWSサービスを触って学びたい
- 毎週土曜日にメールで通知されるQiitaの「今週ストックした記事」をSlackに通知したい
使用するもの
サービス | 用途 |
---|---|
AWS Lambda | Qiita APIからストックした記事を取得してSlackに通知する用 |
Amazon DynamoDB | Qiita APIから取得した記事には「いつストックしたか」の情報がないため通知情報が重複してしまう。これを防ぐために前回通知した情報を保持しておく |
Amazon CloudWatch | Lambdaの定期実行用 |
AWS SAM(サーバレスアプリケーションモデル) | ローカルで構築したアプリケーションを各サービスにデプロイする用 |
Qiita API | ストックした情報を取得する用 |
全体図
アジェンダ
0. 事前準備
1. テンプレートを作成
2. SAMを用いてデプロイ
3. Slackに通知
4. Qiita APIを使用してストック情報をSlackに通知
5. DynamoDBを使用して通知履歴の管理
6. CloudWatchで任意の時間にLambdaを定期実行させるようにする
0. 事前準備
AWS
以下が未作成、未インストールの場合は適宜行います。
- AWSアカウントの作成
- AWS CLI のインストール
- AWS SAM CLI のインストール
Slack
通知させたいチャンネルにwebhook
アプリをインストールして動作確認しておきます。
参考: SlackのWebhook URL取得手順 - Qiita
実装
1. テンプレートを作成
samコマンドでテンプレートを作成します。
$ sam init -n qiita-stocks-notifier -r ruby2.5
オプション | 説明 |
---|---|
-n, --name TEXT | initコマンドで作成されるプロジェクトの名前。 |
-r, --runtime | ランタイムの指定。今回はrubyにしました。 |
参考: sam init - AWS サーバーレスアプリケーションモデル
コンソール内で聞かれる質問はデフォルトを選択しました。
コマンドが完了すると以下の構造のテンプレートがディレクトリ配下に作成されています。
$ tree
.
├── Gemfile
├── README.md
├── events
│ └── event.json
├── hello_world
│ ├── Gemfile
│ └── app.rb
├── template.yaml
└── tests
└── unit
└── test_handler.rb
hello_world/app.rb の中身はメッセージを出力するだけになっています。
2. SAMを用いてデプロイ
テンプレートが作成出来たのでLambda上にデプロイしてみます。
参考: チュートリアル: Hello World アプリケーションのデプロイ - AWS サーバーレスアプリケーションモデル
$ sam build
$ sam deploy --guided
# 質問に回答していきます。
デプロイが正常に完了するとコンソールに文字列が出力され流ことが確認出来ます。
{"message": "hello world"}
Lambdaの管理画面にデプロイしたアプリケーションが反映されていることを確認します。
3. Slackに通知
hello_world/Gemfileにslack-notifier
を追加してbundle install します。
stevenosloan/slack-notifier: A simple wrapper for posting to slack channels
gem "slack-notifier"
$ bundle
app.rb
を以下のようにslack-notifier
にHooksURLを渡して初期化し、テスト文言を送信するようにしてみます。
require "json"
require "slack-notifier"
def lambda_handler(event:, context:)
notifier = Slack::Notifier.new "https://hooks.slack.com/services/**/**" do
defaults channel: "#qiita-slacks-notification"
end
notifier.ping "yeah"
end
3.1 デプロイして動作確認
修正が完了したらビルド&デプロイして動作確認します。
$ sam build
$ sam deploy
Lambdaの管理画面にて「テストイベントの選択」 -> 「テストイベントの設定」を選択します。
作成したアプリケーションへのパラメータを選択する画面になりますが、今回はパラメータによって挙動を変えるものではないので
デフォルトのままにしておきます。
「テストイベントの設定」が完了したら「テスト」を選択し、Slackにメッセージが送信されることを確認します。
これでLambdaからSlackへの疎通確認は出来ました。
4. Qiita APIを使用してストック情報をSlackに通知
続いてQiita API
からユーザーのストックを取得して通知するようにします。
4.1 APIドキュメントの確認
ユーザーのストックした記事を取得するAPIはこちらにありました。
Qiita API v2ドキュメント - Qiita:Developer
外部APIとのやり取りを楽に行うために Faraday
gemを入れておきます。
参考: lostisland/faraday: Simple, but flexible HTTP client library, with support for multiple backends.
gem "faraday"
$ bundle
4.2 通知部分の実装
実装部分の概要は以下になります。
- Faraday を使用してAPIからストック情報を取得
- レスポンスを タイトル と URL 毎に配列にまとめてSlackに通知
require 'json'
require 'faraday'
require 'slack-notifier'
USER_ID = "{自分のユーザーID}".freeze
def lambda_handler(event:, context:)
# APIからストック情報を取得
response = Faraday.get("https://qiita/api/v2/user/#{USER_ID}/stocks", page: 1, per_page: 10)
# タイトルとURLを配列にまとめる
messages = JSON.parse(response.body).each_with_object([]) do |res, ary|
ary << [res["title"], res["url"]]
end
notifier = Slack::Notifier.new "https://hooks.slack.com/services/**/**" do
defaults channel: "#qiita-slacks-notification"
end
notifier.ping "先週Qiitaでストックした記事を流すよ"
messages.each do |message|
notifier.ping message[0]
notifier.ping message[1]
notifier.ping "- - - - - - - - - - - - - - - - - - - - "
end
end
修正が完了したら、ローカルでもアプリのテストが出来るsam local invoke
を使用して動作確認してみます。
sam local invoke --no-event
オプション | 説明 |
---|---|
--no-event | 空のイベントを使用して関数を呼び出します。パラメータを渡して確認する場合は別のオプションを使用します。 |
参考: sam local invoke - AWS サーバーレスアプリケーションモデル |
コマンドを実行するとSlackにストック情報が送信されていることを確認出来ます。
5. DynamoDBを使用して通知履歴の管理
通知部分は出来ましたがこのままですと毎回同じ情報が流れてくる可能性があるので、一度通知した記事は通知させないようにしたいです。
DynamoDB
を用いて通知情報の登録と、データベース内にある情報は通知させないようにします。
DynamoDBとのデータのやり取りは aws-record
gemを使用します。
aws/aws-sdk-ruby-record: Official repository for the aws-record gem, an abstraction for Amazon DynamoDB.
5.1 実装
template.yml
AWSTemplateFormatVersion: '2010-09-09'
...
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: ruby2.5
Policies:
+ # DynamoDBへCreate/Read/Update/Deleteする権限を付与
+ - DynamoDBCrudPolicy:
+ TableName: !Ref NotifiedMessageDDBTable
+ # 環境変数
+ Environment:
+ Variables:
+ DDB_TABLE: !Ref NotifiedMessageDDBTable
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
+ NotifiedMessageDDBTable:
+ Type: AWS::Serverless::SimpleTable
...
require 'aws-record'
require 'json'
require 'faraday'
require 'slack-notifier'
USER_ID = "{自分のユーザーID}".freeze
class NotifiedMessage
include Aws::Record
set_table_name ENV['DDB_TABLE']
string_attr :id, hash_key: true
string_attr :title
string_attr :url
end
def lambda_handler(event:, context:)
# 通知済みの情報を取得
already_notified_messages = []
NotifiedMessage.scan.each do |notified_message|
already_notified_messages << notified_message.url
end
# APIからストックした記事を取得し、未通知のもののみを配列にまとめる
response = Faraday.get("https://qiita.com/api/v2/users/#{USER_ID}/stocks", page: 1, per_page: 10)
messages = JSON.parse(response.body).each_with_object([]) do |res, ary|
ary << [res["title"], res["url"]] unless already_notified_messages.include? res["url"]
end
notifier = Slack::Notifier.new "https://hooks.slack.com/services/xx/xx" do
defaults channel: "#qiita-stocks-notification"
end
notifier.ping "今週ストックした記事はなかったよ" and return if messages.size.zero?
# Slackに通知とデータベースに保存
notifier.ping "先週Qiitaでストックした記事を流すよ"
messages.each do |message|
notifier.ping message[0]
notifier.ping message[1]
notifier.ping "- - - - - - - - - - - - - - - - - - - -"
notified_message = NotifiedMessage.new(id: SecureRandom.uuid, title: message["title"], url: message["url"])
notified_message.save!
end
end
これをデプロイしてテストすると、DynamoDBの管理画面にて追加したテーブルの確認、また最新のストック情報が10件挿入されていることが出来ます。
6. CloudWatchで任意の時間にLambdaを定期実行させるようにする
最後に、毎週いい感じの時間にSlackへの通知が来るようします。
6.1 スケジュールの設定
トリガーを追加
を選択し、検索から「CloudWatch Events/EventBridge」を選択すると実行に関するルールを設定出来るので以下のように設定しました。
スクショが漏れていましたが、スケジュール式の箇所は正しくはcron(0 12 ? * SAT *)
(毎週土曜12時)で設定しました。
参考: Rate または Cron を使用したスケジュール式 - AWS Lambda
CloudWatchからschedulerを確認すると、以下のように毎週土曜日12時に実行されることが確認できます。
まとめ
これで毎週Qiitaからのストック通知を受け取ることが出来ました。
しかしながら、このままですとDynamoDBの読み込み数が膨れ上がっていきお財布がピンチになるのでscan
にquery
を渡して取得する情報を絞り込む必要があります。
AWSのサービスは触ってみて始めて分かることも多いので、とても勉強になりました。
間違い等ありましたらご指摘頂けると幸いです。
参考記事
公式ドキュメント
- sam init - AWS サーバーレスアプリケーションモデル
- sam local invoke - AWS サーバーレスアプリケーションモデル
- Amazon DynamoDB の例 - AWS SDK for Ruby