この記事は Sansan アドベントカレンダー ネタ枠のHaiToが去年から引き続きお送りしています。
Introduction
去年の http://qiita.com/HaiTo/items/af0836723ba1ec45de3e の Introduction
で
fogが唐突にExcon::Errorを返してくるファッキンな部分とかと書いても
こんなことを書いていて、まぁつまりこの記事はこの話です。
Fog?
https://github.com/fog/fog
fog is the Ruby cloud services library, top to bottom:
Fog
はクラウドサービスをがばっとラップしていい感じに共通化されている状態で扱える便利ライブラリ1です。
AWSやAzureはもちろん、Sakura cloud やIBMのBluemixとかいろいろななクラウドサービスに対して殆ど共通のインターフェースを提供してくれ、いい感じに扱えます。
大切なことなので二度
例えば使い方として、
# 出典: http://fog.io/
storage = Fog::Storage.new({
:provider => 'AWS',
:aws_access_key_id => ACCESS_KEY_ID,
:aws_secret_access_key => SECRET_ACCESS_KEY
})
storage.get(file_name)
# これでS3からObjectが取れる
ね。簡単でしょ? AWS-SDKが2012年頃はこなれていなかったのかは歴史的経緯の海の飲まれ不明ですが、Sansanの名刺データ化システムの中の画像をとったりする部分はこのFogをさらに僕達がラップして使っています。
何が良くないの?
冒頭でも書きましたが、予期せぬエラーが発生します。
storage.get(file_name)
# raise Excon::Error::InternalServerError
# message: Expected(200) <=> Actual(500 InternalServerError)
Excon
のエラーがraiseされてきます。
Excon とは https://github.com/excon/excon こちらで、まぁつまりRubyのHTTPクライアントの一つです。
つまり、クラウドサービスをいい感じにラップしてくれるライブラリですが、エラーはそのコンテクストでラップしてくれるわけではなく、HTTPクライアントのエラーをそのまま raise
する仕組みになっています。
で、それで何が困るの?
たとえば以下のようなコードになるわけです。
def storage
@storage ||= Fog::Storage.new({
:provider => 'AWS',
:aws_access_key_id => ACCESS_KEY_ID,
:aws_secret_access_key => SECRET_ACCESS_KEY
})
end
def image_temporary_url(id, expire: 30.seconds.since.to_i)
storage.get_object_https_url('awesome-bucket-name', id, expire)
rescue Excon::Error::InternalServerError => e
retry # 実際には無限Retryになるので良くないのでretriable等を使う。
end
突如として出現する Excon
の文字列! 特に Excon
を直接使っていないと、このExcon
が一体何者なのかを考える所から始まります。2
そして、バックトレースを漁っていくとFogの先にその実体が居ることが分かるわけです。
後からコードをさわった人も、「このExconってなんだろう? Gemfileにも書かれてないしなぁ……」となるわけです。
Gemfile.lockを見たり、@storage
を見てFogの中を見て……と考古学的な時間が掛かります。
じゃあどうしたいの?
素直にFog::Error
みたいな例外をraiseしてください お願いします何でもはしません。
つまり、以下のようなコードになります。
def storage
@storage ||= Fog::Storage.new({
:provider => 'AWS',
:aws_access_key_id => ACCESS_KEY_ID,
:aws_secret_access_key => SECRET_ACCESS_KEY
})
end
def image_temporary_url(id, expire: 30.seconds.since.to_i)
storage.get_object_https_url('awesome-bucket-name', id, expire)
rescue Fog::Error::InternalServerError
retry
end
こうするだけで、「あぁFogのエラーなんだな」という事が一瞬で理解出来るようになりますね。
深追いする必要もなく、Fogを使うのを辞めたら素直にこのロジックも消す(あるいはAWS::Error
みたいなのに置き換わる)ことが出来ます。
良い世界ですね。
じゃあコントリビューションしろよ
Fogの場合はライブラリがでかく、また、ライブラリの特性上影響範囲が広く読みきれない(読めば良いんですが)事が目に見えてます。
でも実は取り掛かったら簡単なのかも。
あと英語が苦手なので、ちょっとこれだけのやりとりを頑張るモチベーションも無いです。
Fog捨てたいし
結論
何かライブラリを作ったりした時に、別のGemを使って例外を発生する箇所もあると思いますが、
そのライブラリのコンテクストに応じた例外に置き換えて、改めてraiseするようにする と、皆ハッピーになるかなぁと思います。
今年はモンハンではなくGジェネを楽しんでます