02. Web/Play Framework

Playframework Dependency Injection with Guice

knightpop 2016. 9. 17. 02:01

Play!도 Dependency Injection이 존재합니다. Spring에서도 많이 사용하듯이 Play!에서도 DI는 매우 유용한 기능입니다 Dependency Injection은 여러가지 방법이 있지만, Play 2.4에서 많이 사용하는 방식은 Google의 DI Library 인 Guice를 사용하는 방법입니다. 물론 여러가지 포스팅이 많지만, 매우 기초적인(?) 한글 포스팅이 없어서 이렇게 씁니다.

Guice를 이용한 DI는 Cats에서 많이 보는 implicit 방식을 이용한 API, Impli 방식과 유사한 방식을 사용하고 있습니다.(물론 이 방식이 범용적인 방식인지는… 저의 지식의 깊이가 낮아 알 수는 없습니다.)

1. 먼저 필요한 특질을 정의한 trait를 정의 합니다.

2. 그 후, 그것에 대한 세세한 기능을 작성한 Impli Class를 정의합니다.

3. AbstractModule을 상속 받는 Module Class의 configure 함수에 bind할 trait와 Impli Class를 정의합니다. 혹은 @Provider라는 Annotation 단, Instance 함수를 정의합니다.


이제 예제를 들며 살펴봅시다. 먼저 bind함수를 이용한 Dependency Injection를 봅시다.


Bind를 이용한 Dependency Injection

만약 SayHello라는 Trait가 있다고 합시다. 이 Trait는 sayHello라는 함수를 갖고 있고, 그것은 String을 반환하는 함수라는 특질을 갖고 있습니다. 이 trait는 다음과 같이 정의가 되어있습니다.


package models

trait SayHello {
  def sayHello : String
}


그리고 SayHello의 동작 방식을 정의한 Impli Class가 다음과 같이 정의가 되어 있다고 합시다.


class SayHelloImpli extends SayHello {
  override def sayHello: String = "Hello I`m Ktz"
}


그리고, 이 SayHello를 사용할 Play Controller가 있다고 합시다. 이것은 SayHello를 받아서 사용합니다. 그렇다면 SayHello를 사용할 SayHelloController는 SayHello와 SayHelloImpli와 다음과 같이 연결되어 있다고 볼 수 있습니다.




이제, SayHello와 SayHelloImpli를 잇는 사실을 Play에 알려줘야 합니다. 그러므로, Guice Library의 AbstractModule을 상속받은, 그 "연결 고리”를 정의하는 Modules Class를 만들어 줍니다.


class SayHelloModule extends AbstractModule{
  override def configure(): Unit = bind(classOf[SayHello]) to classOf[SayHelloImpli]
}


이 Module을 만든 후, 그것을 Play에 알려주기 위하여 application.conf에 다음과 같이 정의해줍니다.


play.modules.enabled += "models.SayHelloModule"


이후, SayHelloController에 @Inject Annotation을 선언하여 Dependency Injection를 합니다.


package controllers

import javax.inject.Inject

import models.SayHello
import play.api.mvc._

class SayHelloController @Inject()(sayhello : SayHello) extends Controller{
  def hello = Action{
    Ok(sayhello.sayHello)
  }
}


마지막으로, routes에 Controller Action을 추가합니다.


GET     /hello                  controllers.SayHelloController.hello


이후, http://localhost:9000/hello에 접속하면 SayHelloController의 hello def를 호출하여, "Hello I`m Ktz"라는 글자를 볼 수 있습니다.


@Provide를 이용한 Dependency Injection


이미 정의된 class나, trait에서 Singleton 방식의 Instance를 생성하는 경우가 있습니다. 이 경우, Singleton에서 Factory 방식으로 사용을 할 수 있는데, Guice Library를 이용한다면, @Provide Annotation을 사용하여 Instance를 생성할 수 있습니다.

만약, iam이라는 함수를 호출하는, 자신이 누구인지, String값을 반환하는 함수를 가진 Whoami라는 trait가 있다고 합시다.


trait Whoami {
  def iam : String
}


이 경우, trait를 상속 받아, iam이라는 함수를 정의하는 class를 만들 수 있지만, new을 하여, 그냥 Whoami라는 Instance를 생성할 수 있습니다. 아까, SayHello와 SayHelloImpli를 연결하는 함수인 SayHelloModule의 밑에 @Provides Annotation같이 선언한, iamwho라는 함수를 다음과 같이 추가 해봅시다.


class SayHelloModule extends AbstractModule{
  override def configure(): Unit = bind(classOf[SayHello]) to classOf[SayHelloImpli]

  @Provides
  def iamwho : Whoami = new Whoami {
    override def iam: String = "ktz"
  }
}


이 함수는 나의 이름인, ‘ktz“라는 이름을 리턴합니다. 이 경우, ImpliClass에 Inject하는 방식과 Controller에 Inject하는 방식, 두가지가 있습니다. 이 두가지 방식을 그림으로 보면 다음과 같은 차이가 있습니다.





먼저, Controller에 Inject하는 방식은 다음과 같습니다. 

일단, ktz라는 이름은 iamwho에서 반환 하므로, SayHelloImpli에서 이름을 뺍시다.


class SayHelloImpli extends SayHello {
  override def sayHello: String = s"Hello I`m "
}


그 후, Controller의 Consctructor Parameter에 Whoami 변수를 새로 추가 합니다.


class SayHelloController @Inject()(sayhello : SayHello, whoami: Whoami) extends Controller{
  def hello = Action{
    Ok(sayhello.sayHello ++ s"${whoami.iam}")
  }
}


이 후, Browser를 다시 Refresh를 해주면… 아까와 같은 화면을 볼 수 있습니다;;;


혹은 ktz는 SayHelloImpli에서 사용할 수 있습니다. 그렇다면 SayHelloImpli에 @Inject Annotation 다음과 같이 추가합니다.

SayHelloImpli에 Injection


class SayHelloImpli @Inject()(whoami: Whoami) extends SayHello {
  override def sayHello: String = s"Hello I`m ${whoami.iam}"
}


이후, Browser를 다시 Refresh 해주면… 다시 아까와 같은 화면을 볼 수 있습니다.