AWS Lambdaではてなブログの記事をQiitaに自動転載した

コード

first commit · k-tokitoh/sync_entries@b05ed9e · GitHub

動機

  • なんか個人開発したいな。
  • 記事の転載を実現するには両方のサービスのAPIを適当に叩けばできそうだな。
  • 定期実行させるにはスクリプトAWS Lambdaにアップロードして適当に設定すればよさそうだな。
  • やる内容がいまの自分のレベル感からしてちょうどよさそうだな(難し過ぎず遊びでできそうだし、簡単過ぎず学びもありそう)。
  • AWS Lambdaもはてな/QiitaのAPIも触ったことないから、はじめてのものに触れてたのしそうだな。
  • 基本的に好き勝手に書き散らかしたいのでブログに書くことにしているが、多少まとまった記事についてはQiitaにも投稿したら人の目につきそうでうれしいな。

とりあえずの仕様

  • 定期的にはてなからQiitaに記事を同期する。
    • (Qiitaからはてなへの記事の同期は行わない。)
  • はてなからQiitaに記事を同期する」とは
    • はてなSyncQiitaというカテゴリをつけた公開済み記事について
    • Qiitaの自分のアカウントに同名のタイトルの記事がなければ
    • Qiitaに同様のタイトル、本文、タグをもった記事を投稿(公開)する
      • ただしはてなでつけたカテゴリのうちSyncQiitaを除いたものをQiitaのタグとして付与する
    • (とりあえずタイトルが一致する記事の存否により投稿するか否かを決め、更新は考慮しない。)

学んだこと

net/http

  • はじめてさわった。便利だなあ…。

  • https通信

Net::HTTP.start(uri.host, use_ssl: true)
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)
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接続が切れるが、http1.1だとtcp接続を保ったまま何回もhttp通信できるらしい。
    • これをkeep-aliveというらしい。
    • Net::HTTP#startで渡したブロック内で複数のリクエストを投げると、それらがkeep-aliveで実行されるらしい。

xml

  • xmlを扱うための標準ライブラリrexmlを初めてつかった。

  • REXML::Elementが1つのxml要素を、REXML::Elementsxml要素の集合を表現する。

  • REXML::Elements#[]XPathを指定して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をベースにしよう。
    • はてなから取得した記事とQiitaから取得した記事を対照して扱う場面が発生しそうなので、「Qiitaではなくはてなの記事である」という情報も変数にもたせた方がよさそうだ。
    • hatena_entriesにしよう。
    • ちなみにこの配列は下書き記事を含まない予定である。
    • しかしもしかすると、今後下書きを含めたい場面が生じるかもしれない。
    • では今回の変数に格納された記事たちが下書きを除くものであるという情報を変数に入れるべきだろうか?
    • つまり、変数名をhatena_entries_excluding_draftsにすべきだろうか。
    • いや、これは2つの理由で必要ない。
      • 現在「下書きを含む場合」と「下書きを含まない場合」の片方しかないので、両者を区別する必要がない
      • 変数名が冗長である
    • こういう理由があるときは、変数名ではなくてコメントに情報を残すのがよさそうだ。
  • 例2: メソッドの切り出し

    • あるメソッド内でのバリデーションについて、「このバリデーションをかけない場合が今後発生するかもしれない」と思う。
    • しかしその理由から、バリデーションを別メソッドに切り出すのは、いいことじゃない。
    • なぜなら、それはまだ必要じゃないから。
    • 以下の理由により別メソッドに切り出すことは是認される。
      • 「現状のメソッドが巨大なので、より小さい単位に分割した方が読みやすい」
      • 「そのバリデーションは明らかに独立したことを行っているので、別メソッドに切り出した方が読みやすい」
      • 「そのバリデーションは複数のメソッド内でつかっているのでDRY原則に従って共通化すべきだ」
  • 再び、拡張性と可読性について

    • 「現状で満たすべきことを実現する最低限のコードを」「可読性高く書く」ことだけに集中すればよく、それ以外のことをすべきではない。
    • そのように書きさえすれば、拡張が必要になったとき、「どこで何をしているかが突き止めやすいので」「どこを修正・追記すればよいのかがわかりやすくて」「修正・追記すべき箇所が少ない」、そのコードをいじればいいのだ。それだけだ。

開発方針

  • アジャイル的な考え方を実践した。
  • あれこれやりたくなるけど、「まずは最低限これができたら公開/実行していいよな」というところを見極めて、まずそれをやる。

  • とりあえず不完全でも雑でもバグがあっても、

    • 「ユーザーにとんでもない迷惑をかけて信用を損なう」とか
    • 「中途半端なリリースをしたことで後で修復作業など余計なコストが大きくかかる」という場合を除いては、
  • とりあえず不完全でも雑でもバグがあっても、リリースしちまうのがいいことだ!

  • 会社のプロダクトでも趣味の個人開発でも、「ユーザーに迷惑をかけて信用を損なう」ことをどれだけ重く受け止めるかが違うだけで、根本的な考え方は同じではないか。

  • という訳で改善したい箇所はいっぱいあるので気が向いたとき継続的にいじっていきたい。