Scala/Play framework

Play Framework 2.5. Actor 만들고 주입하기, 스케쥴링

partner_jun 2017. 4. 13. 14:10

Play Framework를 쓰는 이유 중 하나는 Akka Actor를 사용하기 위해서일 것이다. 물론 Spring에서도 쓸 수는 있지만 어플리케이션 생명 주기를 따르는 내부적인 Actor System을 기본적으로 가지고 있다는 것은 큰 장점이다. 덕분에, Akka Actor Depengency를 따로 추가할 필요도 없다. 


Play Framework에서 Actor를 만드는 방법은 크게 두 가지가 있다. ActorSystem을 주입받아 액터를 만들고 사용하는 방법과 AkkaGuiceSupport를 구현한 모듈을 통해 싱글톤 액터를 만드는 방법이다.


예제를 위해 간단한 액터 클래스를 정의해 두자.

import akka.actor._

case object PING
case object PONG

class PingPongActor extends Actor{
override def receive: Receive = {
case PING => sender ! PONG
case PONG => sender ! PING
}
}

PING을 받으면 PONG을, PONG을 받으면 PING object를 돌려주는 액터.




1. ActorSystem을 주입받아 액터 만들기

ActorSystem을 주입받아 사용한다는 점 외에는 특별할 것이 없다. 

import akka.actor._
import akka.pattern._
import akka.util.Timeout
import scala.concurrent.duration._

@Singleton
class SimpleController @Inject()
(actorSystem: ActorSystem) extends Controller{

implicit val timeout = Timeout(2 seconds)
val pingPongActor: ActorRef = actorSystem.actorOf(Props[PingPongActor])

def index = Action { implicit request =>
val result = Await.result(pingPongActor ? PING, Duration.Inf)
Ok(result.toString)
}

}




2. AkkaGuiceSupport를 상속한 모듈로 싱글톤 액터 만들기

주입가능한 싱글톤 액터를 만들기 위해서는 먼저 AbstractModuleAkkaGuiceSupport를 상속받은 모듈을 만든다.

import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport

class ActorConfigModule extends AbstractModule with AkkaGuiceSupport{
override def configure(): Unit = {
bindActor[PingPongActor]("pingpongActor")
// 파라미터로 받은 문자열은 Named 어노테이션으로 구분할 수 있게 해준다.
}
}

modules.ActorConfgModule.scala



conf/application.conf 파일에 만든 모듈을 추가한다.

play.modules.enabled += modules.ActorConfigModule


모듈에서 바인드한 싱글톤 액터를 주입받아 사용한다.

@Singleton
class SimpleController @Inject()
(@Named("pingpongActor") actor: ActorRef) extends Controller{

implicit val timeout = Timeout(2 seconds)

def index = Action { implicit request =>
val result = Await.result(actor ? PONG, Duration.Inf)
Ok(result.toString)
}

}




3) 액터에 주입하기

액터에 @Inject 어노테이션을 이용해 객체를 주입할 수도 있다.

import javax.inject.Inject
import akka.actor._
import models.Something

class SomethingActor @Inject()
(s: Something) extends Actor{

override def receive: Receive = {
case _ => sender ! s.message
}
}
import akka.actor._
import akka.pattern._
import akka.util.Timeout
import scala.concurrent.duration._

@Singleton
class SimpleController @Inject()
(@Named("somethingActor") s: ActorRef) extends Controller{

implicit val timeout = Timeout(2 seconds)

def index = Action { implicit request =>
val result = Await.result(s ? "Hello", Duration.Inf)
Ok(result.toString)
}

}



4)  Scheduling

ActorSystem을 주입 받아 주기적, 혹은 특정 시간에 액터에 메시지를 전달하는 Scheduling이 가능하다.

// @Inject()(actorSystem: ActorSystem)
import scala.concurrent.ExecutionContext.Implicits._

// (시작 딜레이, 간격, ActorRef, 보낼 메시지)
actorSystem.scheduler.schedule(0 seconds, 300 seconds, pingpongActor, PING)

// (시작 딜레이, ActorRef, 보낼 메시지)
actorSystem.scheduler.scheduleOnce(0 seconds, pingpongActor, PONG)

// (시작 딜레이)(Unit을 반환하는 함수)
actorSystem.scheduler.scheduleOnce(0 seconds){
println("Hello!")
}




그 외에 액터를 동적으로 만들어 주입하는 액터 팩토리나 액터의 쓰레드 개수같은 상세한 설정은 공식 문서에서 볼 수 있다.