dry-containerとdry-auto_injectでDIコンテナを作る

dry-rbファミリーのdry-containerdry-auto_injectを使うと、POROの組み合わせでDIが実現できます。

DIについてハイパーざっくり理解を得るには次の記事を読めばよいです。

qiita.com

上の記事の中のDIコンテナを適用したコードをdry-containerとdry-auto_injectを使って書き直してみました。

require 'dry-auto_inject'
require 'logger'

class FileLogger
  def initialize(filename)
    @logger = Logger.new(filename)
  end
end

class TwitterManager
  def initialize(logger)
    @logger = logger
  end
end

class DatabaseUserAuthenticator; end

class SampleContainer
  extend Dry::Container::Mixin

  register 'file_logger' do
    FileLogger.new('example.log')
  end

  register 'twitter_manager' do
    TwitterManager.new(FileLogger.new('twitter.log'))
  end

  register 'database_authenticator' do
    DatabaseUserAuthenticator.new
  end
end

Import = Dry::AutoInject(SampleContainer)

class Sample
  include Import['file_logger', 'twitter_manager']
end

これで、SampleSampleContainer で登録されているインスタンスを注入できました。次のようにメソッド形式の呼び出しでインスタンスが取得できます。

sample = Sample.new
pp sample.file_logger
pp sample.twitter_manager
pp sample.database_authenticator

pp で中身を確認するとインスタンスが取得できていることがわかります。また、database_authenticatorSample に注入しなかったので取得できずエラーになります。

#<FileLogger:0x00007f84780cb3c8
 @logger=
  #<Logger:0x00007f84780cb378
   @default_formatter=
    #<Logger::Formatter:0x00007f84780cb328 @datetime_format=nil>,
   @formatter=nil,
   @level=0,
   @logdev=
    #<Logger::LogDevice:0x00007f84780cb2d8
     @dev=#<File:example.log>,
     @filename="example.log",
     @mon_count=0,
     @mon_mutex=#<Thread::Mutex:0x00007f84780cb1c0>,
     @mon_owner=nil,
     @shift_age=0,
     @shift_period_suffix="%Y%m%d",
     @shift_size=1048576>,
   @progname=nil>>
#<TwitterManager:0x00007f84780cab08
 @logger=
  #<FileLogger:0x00007f84780cadd8
   @logger=
    #<Logger:0x00007f84780cadb0
     @default_formatter=
      #<Logger::Formatter:0x00007f84780cad60 @datetime_format=nil>,
     @formatter=nil,
     @level=0,
     @logdev=
      #<Logger::LogDevice:0x00007f84780cad10
       @dev=#<File:twitter.log>,
       @filename="twitter.log",
       @mon_count=0,
       @mon_mutex=#<Thread::Mutex:0x00007f84780cacc0>,
       @mon_owner=nil,
       @shift_age=0,
       @shift_period_suffix="%Y%m%d",
       @shift_size=1048576>,
     @progname=nil>>>
Traceback (most recent call last):
container.rb:43:in `<main>': undefined method `database_authenticator' for #<Sample:0x00007fd8149da578> (NoMethodError)

また、別のインスタンスに差し替えることもできます。テストのときはモックに差し替える、というような用途で便利です。

sample = Sample.new(file_logger: Logger.new(STDOUT))
pp sample.file_logger
#<Logger:0x00007f84788cfdd0
 @default_formatter=
  #<Logger::Formatter:0x00007f84788cfce0 @datetime_format=nil>,
 @formatter=nil,
 @level=0,
 @logdev=
  #<Logger::LogDevice:0x00007f84788cfbc8
   @dev=#<IO:<STDOUT>>,
   @filename=nil,
   @mon_count=0,
   @mon_mutex=#<Thread::Mutex:0x00007f84788cf010>,
   @mon_owner=nil,
   @shift_age=nil,
   @shift_period_suffix=nil,
   @shift_size=nil>,
 @progname=nil>