2019-08-10
AWS Lambdaではてなブログの記事をQiitaに自動転載した
.
コード
first commit k-tokitoh/sync_entries@b05ed9e GitHub
動機
- なんか個人開発したいな。
- 記事の転載を実現するには両方のサービスの API を適当に叩けばできそうだな。
- 定期実行させるには AWS Lambda にアップロードして適当に設定すればよさそうだな。
- やる内容がいまの自分のレベル感からしてちょうどよさそうだな(難し過ぎず遊びでできそうだし、簡単過ぎず学びもありそう)。
- API も触ったことないから、はじめてのものに触れてたのしそうだな。
- 基本的に好き勝手に書き散らかしたいのでブログに書くことにしているが、多少まとまった記事については Qiita にも投稿したら人の目につきそうでうれしいな。
とりあえずの仕様
- 定期的にはてなから Qiita に記事を同期する。
- (Qiita からはてなへの記事の同期は行わない。)
- 「はてなから Qiita に記事を同期する」とは
- はてなの
SyncQiita
というカテゴリをつけた公開済み記事について - Qiita の自分のアカウントに同名のタイトルの記事がなければ
- Qiita に同様のタイトル、本文、タグをもった記事を投稿(公開)する
- ただしはてなでつけたカテゴリのうち
SyncQiita
を除いたものを Qiita のタグとして付与する
- ただしはてなでつけたカテゴリのうち
- (とりあえずタイトルが一致する記事の存否により投稿するか否かを決め、更新は考慮しない。)
- はてなの
学んだこと
net/http
はじめてさわった。便利だなあ…。
https 通信
Net::HTTP.start(uri.host, use_ssl: true)
Basic 認証
req = Net::HTTP::Get.new(uri) req.basic_auth(ENV[‘HATENA_ID’], ENV[‘HATENA_API_KEY’])
アクセストークンをつかった認証
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV['QIITA_ACCESS_TOKEN']}"
- get リクエストにパラメータを設定する
uri = URI.parse('https://qiita.com/api/v2/authenticated_user/items')
params = {page: 1, per_page: 20}
uri.query = URI.encode_www_form(params)
- post リク json を送る
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = {
"key1": "value1",
"key2": "value2",
}.to_json
- keep-alive
- http1.0 だと http レスポンスを返すたびに tcp 接続を保ったまま何回も http 通信できるらしい。
- これを keep-alive というらしい。
- Net::HTTP#start で渡したブロック内で複数のリクエストを投げると、それらが keep-alive で実行されるらしい。
xml
xml を扱うための標準ライブラリ rexml を初めてつかった。
REXML::Element
が 1 つの xml 要素の集合を表現する。REXML::Elements#[]
で xml 要素を取り出せる。REXML::Elements#each
で該当する全ての xml 要素に対して処理を回せる。
AWS Lambda
マネジメントコンソールで直書き or zip ファイルのアップロードによりスクリプトを設定できる。
実行するメソッド(lambda)を
ファイル名.メソッド名
という形で 1 つだけ指定する。環境変数もつかえる。
ログもいい感じに出力してくれてハッピー。
その他技術面
- ループでは 1 回 1 回ローカル変数のスコープは破棄される。
3.times do
p var # => 3 回とも`undefined local variable or method `var'`
var = 999
end
zip ファイルの作成
zip -r 作成する zip ファイル名 圧縮対象とするファイルを抽出するためのパターン
具体的には以下。
zip -r sync_entries ./*
Dir#[*pattern]
でパターンマッチした文字列の配列が返ってくる。dotenv
require 'dotenv'
だと gem の読みこみだけ。require 'dotenv/load'
だと gem の読み込み+ルートディレクトリの.env
のロードまでしてくれる。
設計
API 接続は Hatena クラスに隠蔽するなどの工夫ですっきりするなあ、と実感できた。
拡張性と可読性について
- 書いてると、「こういうことがしたくなるかも、それに対応できるようにするにはどうしたらいいだろう」っていうのがいっぱいでてくる。
- でも例えば「引数を変えるだけで『今後したくなるかもなこと』が実現できる」ような実装はつくらなくていい。使わないオプションは使えるようにしなくていいのだ。
- 「現状で満たすべきことを実現する最低限のコードを」「可読性高く書く」ことだけに集中すればよく、それ以外のことをすべきではない。
- つまり、可読性さえ意識すれば、拡張(可能)性は自然とついてくる。
- 必要なのは拡張(可能)性のみであって、まだ使わない機能ならば実際に拡張すべきではない。
- 可読性高く書くとは、「重複がなく」「変数名が適切で」「クラスやメソッドの責務分担や粒度が適切で」あること。
- そして「そこで何をしているのかを理解するための適切なコメントがあること」。
- このテーゼに関して以下で 2 つの具体例をみる。
例 1: 情報を変数にもたせるか、コメントで残すか
- はてなから取得した記事を要素とする配列の変数名を例に考える。
- 「記事である」という情報は必要なので、変数名はまず entries をベースにしよう。
- はてなの記事である」という情報も変数にもたせた方がよさそうだ。
- hatena_entries にしよう。
- ちなみにこの配列は下書き記事を含まない予定である。
- しかしもしかすると、今後下書きを含めたい場面が生じるかもしれない。
- では今回の変数に格納された記事たちが下書きを除くものであるという情報を変数に入れるべきだろうか?
- つまり、変数名を hatena_entries_excluding_drafts にすべきだろうか。
- いや、これは 2 つの理由で必要ない。
- 現在「下書きを含む場合」と「下書きを含まない場合」の片方しかないので、両者を区別する必要がない
- 変数名が冗長である
- こういう理由があるときは、変数名ではなくてコメントに情報を残すのがよさそうだ。
例 2: メソッドの切り出し
- あるメソッド内でのバリデーションについて、「このバリデーションをかけない場合が今後発生するかもしれない」と思う。
- しかしその理由から、バリデーションを別メソッドに切り出すのは、いいことじゃない。
- なぜなら、それはまだ必要じゃないから。
- 以下の理由により別メソッドに切り出すことは是認される。
- 「現状のメソッドが巨大なので、より小さい単位に分割した方が読みやすい」
- 「そのバリデーションは明らかに独立したことを行っているので、別メソッドに切り出した方が読みやすい」
- 「そのバリデーションは複数のメソッド内でつかっているので通化すべきだ」
再び、拡張性と可読性について
- 「現状で満たすべきことを実現する最低限のコードを」「可読性高く書く」ことだけに集中すればよく、それ以外のことをすべきではない。
- そのように書きさえすれば、 拡張が必要になったとき、「どこで何をしているかが突き止めやすいので」「どこを修正・追記すればよいのかがわかりやすくて」「修正・追記すべき箇所が少ない」、そのコードをいじればいいのだ。 それだけだ。
開発方針
アジャイル的な考え方を実践した。
あれこれやりたくなるけど、「まずは最低限これができたら公開/実行していいよな」というところを見極めて、まずそれをやる。
とりあえず不完全でも雑でもバグがあっても、
- 「ユーザーにとんでもない迷惑をかけて信用を損なう」とか
- 「中途半端なリリースをしたことで後で修復作業など余計なコストが大きくかかる」という場合を除いては、
とりあえず不完全でも雑でもバグがあっても、リリースしちまうのがいいことだ!
会社のプロダクトでも趣味の個人開発でも、「ユーザーに迷惑をかけて信用を損なう」ことをどれだけ重く受け止めるかが違うだけで、根本的な考え方は同じではないか。
という訳で改善したい箇所はいっぱいあるので気が向いたとき継続的にいじっていきたい。