PlayFramework+GuiceでDIしているときに型クラスインスタンスでインジェクションされたクラスを使用する
PlayFrameworkでGuiceを使ってDIしているときに、 型クラスインスタンスでインジェクションされたクラスを使う方法を調べました。
以下のようなコードがあるとします。
// Factory.scala trait Factory[T] { def create: T } case class Type1() object Type1Factory { implicit object Type1Factory extend Factory[Type1] { override def create: Type1 = Type1() } } case class Type2(value: String) object Type2Factory { implicit object Type2Factory extend Factory[Type2] { // どうやってここに、type2Repositoryを渡せば良い? override def create: Type2 = type2Repository.load() } }
// UseCase.scala class UseCase @Inject()(type2Repository: Type2Repository) { def create[T: Factory]: T = { // 共通処理 ... implicitly[Factory[T]].create } }
// Controller.scala class Controller @Inject()(usecase: UseCase) { import Factory._ def create1: Type1 = usecase.create() def create2: Type2 = usecase.create() }
Factory
というcreate
というメソッドをもつ型クラスがあって、
その型クラスインスタンスとしてType1Factory
とType2Factory
が定義されています。
Type1Factory
はとくに他に依存しないcreateメソッドを持ちますが、
Type2Factory
のcreate
メソッドでは、Type2Repository
を使う必要があります。
Type2Repository
はインタフェースで、他のところにDBからデータを取得する処理を行うType2RepositoryImpl
クラスが定義されています。
Type2Repository
はGuiceでUseCase
クラスにInjectされます。
このとき、DI管理下のType2Repository
のインスタンスをType2Factory
に渡す方法がわからず、困っていました。
Type2Repository
が必要なのはType2Factory
だけなので、Factory
のメソッドの引数にType2Repository
を渡したくありません。
型クラスインスタンスが増え、そのインスタンスが別のRepositoryを要求する場合、引数を追加していかなければならないためです。
良い方法が思いつかなくて悩んでいたところ、Twitterで教えていただきました。
Scalaは値に対して import が可能なので、無理に object に DI する方向よりも、型クラスインスタンスの定義をDI管理化のclass上で定義しておいて、DI した値に対して import して上げるほうが単純なコードにできるかと思います
— がくぞ (@gakuzzzz) February 17, 2020
値に対するimportでうまくいきそうです。以下が、改良版のコードになります。
// UseCase.scala class UseCase @Inject()(type2Repository: Type2Repository) { def create[T: Factory]: T = { // 共通処理 ... implicitly[Factory[T]].create } implicit val type1Factory: Factory[Type1] = new Factory[Type1] { override def create: Type1 = Type1() } implicit val Type2Factory: Factory[Type2] = new Factory[Type2] { override def create: Type2 = type2Repository.load() } }
// Controller.scala class Controller @Inject()(usecase: UseCase) { import usecase._ def create1: Type1 = usecase.create() def create2: Type2 = usecase.create() }
型クラスインスタンスをDI管理下のUseCaseクラスで定義することで、Type2Repository
を呼び出すことができました。
また、先程はControllerクラスでimport Factory._で型クラスインスタンスをimportしていましたが、
scalaでは値に対してimportできるようなので、インジェクトされたUseCase
インスタンスに対してimportすることでimplicit
が解決できるようになります。