Edited at

AWS SDKのラッパーを作るとき用の便利concern

More than 3 years have passed since last update.

追記:EC2上でSDKを利用する場合は、IAMロールを利用しましょう。

RubyでAWS SDK(aws/aws-sdk-ruby)を使うときに、

そのままアプリケーションのコードに組み込んで使う人はそんなにいないと思う。

SDKのコードを直接組み込むと、ユニットテストしづらくなるし設定用のコードが各所に散らばることになる。

なので、簡単なラッパー&クライアントのファクトリクラスを書くと良い感じに手軽にコードも書けるし

テストも書けるという望ましい状態になるかと思います。

今回は、SDK使う際に自分が書いた簡単なコードを紹介します。

注:AWS SDKはもうすぐv2がリリースされそうな感じですがここではv1のコードを元に記事を書いています。

まず、便利concern。これをファクトリクラスにincludeして使う。

そうすると、config/initializers/aws.rbで使う用の.configureメソッドと、

設定済みクライアントの取得ができる.clientメソッドが生えてくる。

実装が若干おまじないっぽい。


lib/my_app/aws/configurable.rb

module MyApp::AWS::Configurable

extend ActiveSupport::Concern

def client
self.class.client
end

included do
class << self
# cache client object until re-configuring
def client
@client ||= service_class.new(configuration.to_h)
end

def configure(&block)
# remove client cache at first
clear_client_cache!
options = Options.new(AWS::Core::Configuration.accepted_options)
yield options
@aws_core_configuration = AWS::Core::Configuration.new(aws_options.to_h)
end

def configuration
stub_configuration || @aws_core_configuration
end

def stub_configuration
# override at your spec_helper
end

def clear_client_cache!
@client = nil
end

protected
def service_class
matched = self.to_s.match(/\AMyApp\:\:([a-zA-Z_\:]+)/).captures[0]
(matched + '::Client').constantize
end
end
end

class Options < BasicObject
def initialize(accepted_options)
@accepted_options = accepted_options
@config = {}
end

def to_h
@config
end

def method_missing(method, *args)
return super unless setter?(method.to_s)

name = extract_name(method.to_s)
return super unless accept?(name)

@config[name] = args[0]
end

def setter?(method)
method =~ /\A\w*=\z/
end

def extract_name(method)
method.match(/\A(\w*)=\z/).captures[0].to_sym
end

def accept?(name)
@accepted_options.include?(name)
end
end
end


ファクトリクラスの定義


lib/my_app/aws/sns.rb

module MyApp

module AWS
class SNS
include MyApp::AWS::Configurable

# 以下に必要なコードがあれば書いてく
end
end
end


初期化処理。よくあるアクセスキーの設定がアプリケーションのコードから分離できる


config/initializers/aws.rb

MyApp::AWS::SNS.configure do |conf|

conf.access_key_id = Rails.application.secrets.aws_access_key_id
conf.secret_access_key = Rails.application.secrets.aws_secret_access_key
conf.region = Rails.application.secrets.aws_region_sns
end

ファクトリクラスの使い方。ファクトリクラスのclientメソッドから

初期化済みのクライアントのインスタンスが取得できるので、設定処理が散らかることがない。

これを元にラッパークラスを書いていく。


lib/my_app/hoge.rb

class MyApp::Hoge

def create(xxx)
sns = MyApp::AWS::SNS.client
sns.create_platform_endpoint(xxx)
end
end

テストを書くときは、AWS::Core::Configurationの設定にstub_requestsをtrueで渡してやる。

すると、requestがstubのresponseを返すようになる。

しかしこのままだと、空のAWS::Core::Responseが返ってくるのみ。


spec/support/aws.rb

module MyApp

module AWS

class SNS
def self.stub_configuration
AWS::Core::Configuration.new(
access_key_id: 'ACCESS_KEY_ID',
secret_access_key: 'SECRET_ACCESS_KEY',
stub_requests: true,
)
end
end

end
end


このようにstub_forでレスポンスの値を書くと実際のレスポンスを好きな値にすることができる。

ただし、SDKだけではモック化には対応してない。

参考: Stubbing AWS Responses - AWS Developer Blog - Ruby


spec/lib/my_app/xxx.rb

before do

sns = MyApp::AWS::SNS.client
resp = sns.stub_for(:create_platform_endpoint)
resp.data[:endpoint_arn] = 'arn:aws....'
end

after do
MyApp::AWS::SNS.clear_client_cache!
end

it 'xxxx xxx xxx' do
resp = MyApp::Hoge.create(xxx)
expect(resp[:endpoint_arn]).to eq('arn:aws....')
end


こんな感じでサクッとテストも書けるようになりました。便利。