Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyのAPIラッパーを設定する3つの方法

RubyでAPIラッパーを作るとき、必ず「設定(コンフィギュレーション)」という課題に直面します。ユーザー名とシークレットキーが必要な場合もあれば、ホスト名だけで済むケースもあるでしょう。

設定のやり方にはいくつかのアプローチがあります。では、どれを選ぶべきなのでしょうか?

手軽だが落とし穴もある「グローバル設定」

まず考えたいのは、サービスをアプリ内のどこからでもすぐに呼び出せる状態にしておくことです。そうしないと、APIを1行使うために3行もの設定コードを書く羽目になってしまいます。

定数やクラス属性を使えば、設定をグローバルにできます。

config/initializers/product_api.rb
ProductApi.root = "https://staging-host.example.com/"
ProductApi.user = "justin"
ProductApi.secret = "mysecret123"
app/controllers/products_controller.rb
def show
  @product = ProductApi.find(params[:id])
end

多くのgemがこのパターンを採用しています。書くのも簡単で、使うのも簡単です。しかし、大きな問題を2つ抱えています。

  • ProductApiは1つしか存在できない。

    2人の異なるユーザーとしてProduct APIを使い分けたい場合や、1つのアプリから複数のサーバーへ接続したい場合には、この方式では対応できません。

  • グローバルな状態は、意図せず書き換えられてしまう。

    あるスレッドやアプリの一部がProductApi.userを変更すると、ProductApiを使っている他のすべてのコードが壊れます。しかも、この種のバグは原因を特定するのが非常に困難です。

つまり、クラスレベルの変数には落とし穴があるのです。では、クラスそのものではなく、インスタンスを設定するのはどうでしょうか?

#initializeでインスタンスごとに設定する

インスタンスを使えば、必要になったタイミングでAPIラッパーを生成・設定できます。

app/controllers/products_controller.rb
def show
  product_api = ProductApi.new(
    root: "https://staging-host.example.com/",
    user: "justin",
    secret: "mysecret123")
  @product = product_api.find(params[:id])
end

これで、使うたびに異なる設定値を渡せるようになりました。他のメソッドやスレッドが同じインスタンスを共有しないため、知らないうちに状態が変わる心配もありません。

一見すると良さそうです。しかし、まだ十分に便利とは言えません。APIを使うたびに、毎回設定しなければならないからです。

実際のところ、ほとんどの場面ではAPIの細かい設定に関心などなく、まともなデフォルト値で手軽に使えれば十分なはずです。ところがインスタンス方式では、APIを利用するアプリのあらゆる箇所が、設定方法を知っていなければなりません。

しかし、「グローバルアクセスの手軽さ」と「必要なときだけ上書きできる柔軟性」を両立させる方法があります。

そして興味深いことに、このパターンはOS XやiOSの開発で頻繁に登場するのです。

良いデフォルトと柔軟性を両立させるには?

各インスタンスを個別に設定できるうえ、「特にこだわりがないときに使えるグローバルなデフォルトインスタンス」も用意されていたら、どうでしょうか?

iOSやMac OSのSDKを見渡すと、“defaultSomething”や“sharedWhatever”といったパターンが至る所で使われています。

[[NSURLSession sharedSession] downloadTaskWithURL:@"https://www.google.com"];

[[NSFileManager defaultManager] removeItemAtPath:...];

さらに、デフォルトの挙動だけでは足りない場合は、これらのクラスのインスタンスを個別に取得することもできます。

NSURLSession *session = [NSURLSession sessionWithConfiguration:...];

NSFileManager fileManager = [[NSFileManager alloc] init];

Rubyでも、default_apiというクラスメソッドを用意すれば、同じ仕組みを実現できます。

app/controllers/products_controller.rb
def show
  @product = ProductApi.default_product_api.find(params[:id])
end

...

def show_special
  special_product_api = ProductApi.new(
    root: "https://special-product-host.example.com/"
    user: "justin"
    secret: "mysecret123")
  @special_product = special_product_api.find(params[:id])
end

実装イメージは次のようになります。

class ProductApi
  def initialize(root:, user:, secret:)
    @root, @user, @secret = root, user, secret
  end

  def self.default_api
    @default_api ||= new(
      root: ENV['PRODUCT_API_ROOT'],
      user: ENV['PRODUCT_API_USER'],
      secret: ENV['PRODUCT_API_SECRET'])
  end

  def find(product_id)
    ...
  end
end

ここではdefault_apiの中で環境変数を使いましたが、設定ファイルを読み込む形でも構いません。また、||=をスレッドローカルやリクエストローカルなストレージへの保存に置き換えれば、マルチスレッド環境でもより安全になります。

まずはこの形から始めるのが良い出発点となるでしょう。

筆者が見てきた多くのgem(Twitter gemなど)では、必要になるたびにAPIオブジェクトを設定・生成するスタイルが採用されています。それ自体は妥当な解決策です(ただし、結局グローバル変数に代入してしまうコードをよく見かけます)。

そこからもう一歩進んで、事前設定済みのデフォルトオブジェクトも併せて提供すれば、ライブラリの利用者はぐっと快適になります。

  1. Macの画面をロックする9つの方法【ショートカット・ホットコーナーなど】

    席を離れるほんの数分の間でもデータを安全に守りたいなら、Macの画面をロックすることは非常に重要です。画面をロックすると、起動中のアプリケーションを終了させたり中断したりすることなく、Macをスリープ状態にできます。再度アクセスする際には、通常ログインパスワードの入力が必要になります。この記事では、Macの画面をロックするあらゆる方法をご紹介します。その前に、まだパスワード保護を有効にしていない方は、まずMacのロック解除時にパスワードを要求する設定を行っておきましょう。目次:1. Macにパスワード保護を設定する2. Macの蓋を閉じて画面をロックする3. キーボードショートカットで画面をロ

  2. Chromeを高速化する10の方法|今すぐ試せる設定とテクニック

    Chromeは軽快で使いやすいブラウザですが、使い続けているうちに動作が遅くなることがあります。原因の多くは、蓄積されたキャッシュファイルやインストールしすぎた拡張機能です。しかも、それらの多くは実は必要のないものだったりします。キャッシュの削除、ブラウザのアップデート、隠れた設定項目の活用など、PC上のChromeを高速化する方法はいくつもあります。これらを見直すだけで、Chromeのパフォーマンスは大きく改善します。本記事では、今日からすぐ実践できる10の対策を順番に解説します。1. Chromeを最新バージョンにアップデートするまず試したいのが、Chromeを最新版に更新することです。新