DIとは
dependency injection (依存性注入)のこと
外からconstractorにobject渡せるようにすることで依存を避けようっていうもの
実装
Interactorを例に書いていく
dry-containerとdry-auto_injectを使ってDIしてみた
DIしてない版
not_di.rb
module Interactor
class FindUser
def initialize(params)
@params = params
end
def call
user_repo = UserRepository.new
user_repo.by_pk(@params[:id])
end
end
end
UserRepositoryに依存しているのがわかる
dry-container使わない版
pure-di.rb
module Interactor
class FindUser
def initialize(params: {}, user_repo: UserRepository.new)
@params = params
@user_repo = user_repo
end
def call
@user_repo.by_pk(@params[:id])
end
end
end
initializdの引数がでっかくなってみづらいコードになりがち
これありますねぇ
dry-container版
dry-di.rb
module Container
module FindUser
extend Dry::Container::Mixin
# 注入するobjectを登録していく
# blockでdefaultのobjectを宣言できる
register "params"
register "user_repo" do
UserRepository.new
end
end
end
module Interactor
class FindUser
Import = Dry::AutoInject(Container::FindUser)
# includeしたものをmethodで呼び出せるようになる
include Import["params", "user_repo"]
def call
user_repo.by_pk(params[:id])
end
end
end
コードは増えたけど責務を切り分けることで見通しがよくなった
InteractorがContainerに依存していて、呼び出す人(Controllerとか)もContainerに依存しているので
いい感じに依存性を逆転させることができた
before: controller -> interactor
after: controller -> container <- interactor
何が嬉しいのか
- コードがスッキリする
initializeをcontainerに委託することでinteractorの責務が減った
classあたりの責務は減らしていきたい
- 単体テストが簡単にできる
InteractorのテストをDB用意しないでできる
上の例だと by_pk(id)
に反応するobject渡せば良い
test.rb
User = Struct.new(:id, :name, :age)
class TestUserRepo
def by_pk(id)
User.new(id, "tarou", 20)
end
end
params = { id: 1 }
user_repo = TestUserRepo.new
input = { params: params, user_repo: user_repo }
Interactor::FindUser.new(input).call
みたいにできる