본문 바로가기

02. Web/Play Framework

Playframework Dependency Injection with Guice

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 해주면… 다시 아까와 같은 화면을 볼 수 있습니다.


'02. Web > Play Framework' 카테고리의 다른 글