サーバーレスの勉強と実益を兼ねて、自分のQiitaの記事を自動でサマリして簡単なポートフォリオ代わりに使える記事を出してみようと思いました。
成果物
こちらの記事です。
Qiita Contributions Portfolio (Auto Generated)
自分の投稿記事について、使用したタグと Like数が多いものを表示します。
要件
- 自分が投稿したすべての記事を集計して、その集計結果を報告する記事を自動生成したい
- 自動生成する記事は、定期的に集計結果の更新を行い、その結果が自動的に反映されるようにしたい
構成図
全体的な構成は以下の図の通りです。
記事の作成/更新の機能と、記事の投稿内容となる変数の部分を計算するジョブの2つの部分からなります。
事前に Jinja2 をベースにした記事のテンプレートを作成しておきます。記事のタイトルやタグ、テンプレート変数を動的に DynamoDB から取得して本文をレンダリングし、投稿します。 Qiita への投稿部分は Step Functions で実装しています。
テンプレート変数部分は定期的に再計算して記事更新したいので、テンプレート変数の値を求めるための lambda を別で動かしています。集計処理と記事投稿は密結合したくない要素なので、自動投稿との間に SQS を挟んでいます。
SQS のコンシューマ側にあたる lambda でテンプレート変数を DynamoDB に永続化し、続いて Step Functions を起動します。
DynamoDB では 記事ID をハッシュキーとして記事情報を保持します。次のような情報を持っています。
- タイトル、タグ
- テンプレート変数(本文のレンダリングに使用)
- レンダリング時に使用すべきテンプレートの名前
- Qiita の記事公開状態のステート(公開済みである場合は、Qiita側が持っている記事IDも保持)
Step Functions の中身は次のような内容になっています。
このステートマシンは入力として記事ID(QiitaのItem IDではなく、このシステム内部でのみ使用する管理用変数)を受け取ります。
記事ID を指定して、 DynamoDB から投稿内容等の情報を取得します。見つからなければ記事投稿の情報が存在しないということなので、その時点で Fail させます。
次に、公開状態のステートを評価します。公開済みステートの場合は Qiita API を呼び出し、その記事が実際に存在しているかどうかを確認します。
その後、公開状態のステートを評価し、記事を新規作成すべきか、更新すべきかを判定します。新規作成の場合は最後に公開状態のステートを更新して終了です。
設計ポイント
記事更新に必要な構成要素はほぼ集計側から分離している点が設計上のメリットとなります。集計側としては結果を dict にしてキューイングするだけでよく、後続に存在する DynamoDB や Step Functions などの詳細は知る必要がありません。
今回は1つの記事だけ自動生成していますが、別の記事を同じ仕組みに乗せることも簡単です。任意の記事IDを決めてあげて、
- 記事のテンプレート (Jinja2) を作成
- DynamoDB に記事の投稿情報を Put しておく
- タイトル、タグ
- 作成したテンプレートの名前
- テンプレート変数の値を計算するための仕組み (今回の例ではlambda を使用) を開発する
- 計算したテンプレート変数は SQS にキューイングする
Qiita の投稿部分に Step Functions を使用したことで、中で行うロジックの見通しが良くなっています。雑に lambda を書いていると1本の function に多くの仕事をさせてしまいがちですが、Step Functions であれば各ステップの実装だけを綺麗にやれば後は繋ぎ込みを Serverless Framework のリソース定義で用意するだけです。
function 1本あたりでやっていることがシンプルなので、自然とコードは本質的なロジックのみをそれぞれ関数化しただけの、すっきりした構造になります。 また、Step Functions では分岐の制御構造を No Code で実装できるので、(今回の例では微々たる量ではあるものの)コードの記述量もより少なく済みます。
改善点
いくつかありますが、目につくのは次のポイントです。
- Jinja2 テンプレートがソースコードに直接含まれている
- S3 に置けるようにしたい
- 初投稿を行うためには、予め DynamoDB に記事投稿の情報を Put しておく必要がある
- 単純に面倒くさい
- 他の人に横展開しづらい構成になっている
- 私が作成したい記事の集計ロジックと jinja2 テンプレートが投稿システムと同じソースに含まれてしまっている
他の人にも使ってもらおうと考えたとき、私が使っている Jinja2 のテンプレートがソースに含まれている状態はあまりよろしくない感じがします。また、テンプレートは Static Contents なので、置き場はできれば外部化したいです。
Jinja2 + S3 なら誰かが拡張機能を作ってそうな気はするので、既存パッケージを使って実現可能ならサクッとやりたいです。ローカルか S3 か、データソースを選択できるようになってるとよりよいのではないか、という気もします。
ソース
モジュールの切り方や命名にセンスがないので改善したいなという思いはありますが、ひとまず公開してみました。
sfn のタスク名などもいい加減なのでいずれ直したいですが、ご査収ください。
aws 関係は libs/aws/ 以下に寄せる、 libs/ 直下にはユースケース的な抽象インタフェースにして裏の仕組み (AWS) を意識させない名前を提供するように変更する、などの改善余地があると考えています。