동기 결과에 대한 처리 

원문 https://www.playframework.com/documentation/2.4.x/ScalaAsync 번역 반 , 의역 반
Play2 의 가장 중요한 부분이라 본다. 현재 나도 잘 이해하고 있지 못한 Reactive Web 개발 모델과 밀접하며 

Reactive Web Applications: Covers Play, Akka, and Reactive Streams

와 

Akka in Action

을 요즘 틈틈히 읽고 있는데 짧게 짧게 블로그를 통해 이해한 것을 정리 할 생각이다.

컨트롤러를 비동기 방식으로 다루기

내부적으로 플레이프레임워크는 상향식으로 비동기적이다. 플레이는 매 요청을 비동기적이며 논블럭 방식으로 다룬다. 

기본 설정이 비동기식 컨트롤러로 바뀌었는데, 다른 말로 하면 어플리켕션 코드가 컨트롤러에서 블럭되는것을 피해야 한다는 말이다. 즉 JDBC 콜, 스트리밍 API, HTTP 요청 및 오랜 시간이 걸리는 계산 같은 연산에 관련된 코드들 말이다. 

블럭되는 컨트롤러에 의해 처리를 하기 위해 동시에 처리되는 요청들을 더 많이 수행하기 위해서는 기본 실행되는 쓰레드의 숫자를 마구 늘릴 수 도 있겠지만 컨트롤러를 비동기식으로 접근하는것이 확장성에 있어서 더 나으며  부하에 대한 시스템의 반응에도 더 유리하다. 

(역주: 이런 건 많은 수의 클라이언트 처리에 대해 멀티쓰레딩으로 처리했던 톰캣 보다 node 나 vert.x 가 훨씬 반응성이 좋다는게 입증해준다. 하나의 쓰레드로 좀 더 효율적인 i/o 를 하면 여러개의 쓰레드로는 엄청나나 효율을 가져다 줄 수 있으니..) 

논블록 액션 만들기

만약 우리가 아직 결과를 얻지 못했는데 바로 액션에 대한 결과를 처리하려면 어떻게 해야하나? 해답은 퓨쳐 이다. 리턴 받을 때 까지 기다리는게 아니라 즉시  Future[Result]  를  리턴 받아서 다른 클라이언트를 대응하는 일을 할 수 있게 된다.  Result 에 실제 리턴값이 담기면 그 때 처리 할 것이다.

웹 클라이언트는 응답을 기다리는 동안 블럭 될 것이긴 하지만 그 클라이언트에 대한 처리를 위해  서버 자체가 블럭 되진 않는 다는 것이다. 즉 서버는 다른 클라이언트들을 처리하는데 사용 될 것이며 약속이 이행되면 바로 그 때 그 결과를 돌려 줄 것이다. 

어떻게  Future[Result]를 다룰까

 실제 값을 우리에게 줄 퓨처와 그것을 통해 결과를 얻는 퓨처 : 

import play.api.libs.concurrent.Execution.Implicits.defaultContext

val futurePIValue: Future[Double] = computePIAsynchronously()
val futureResult: Future[Result] = futurePIValue.map { pi =>
  Ok("PI value computed: " + pi)
}

플레이 비동기 API 콜 모두는 퓨처를 줄 것이다. 이건 니가 외부로 웹서비스를 요청 하거나 (play.api.libs.WS API) Akka 로 비동기 업무를 수행 할 때 도 사용된 다는 뜻이다. 즉 수행이 끝 날때까지 쓰레드가 놀고 있느게 아니라 퓨처를 받은 후에 즉시 퓨처에 실제 값이 들어 올때까지 다른 일을 하게 된다는 야그..

여기 비동기 블럭을 실행하고 결과를 얻는 Future 예제가 있다. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext

val futureInt: Future[Int] = scala.concurrent.Future {
  intensiveComputation()
}

참고 : 퓨처로 실행되는 스레드 코드를 이해하는 것이 중요합니다. 위의 두 코드 블록에서 기본 실행 컨텍스트를 가져 오고 있습니다. 이것은 콜백을 허용하는 퓨처 API의 모든 메소드로 전달되는 암시 적 매개 변수입니다. 실행 컨텍스트는 종종 스레드 풀과 동일하지만 필수적이지는 않습니다.

퓨처상에서 래핑하여 동기식 IO를 마술처럼 비동기로 전환 할 수는 없습니다. 응용 프로그램의 구조를 변경하여 작업을 차단하지 못하는 경우, 어떤 시점에서 작업을 실행해야하며 해당 스레드가 차단됩니다. 따라서 Future에 작업을 포함하는 것 외에도 예상되는 동시성을 처리하기에 충분한 스레드로 구성된 별도의 실행 컨텍스트에서 실행되도록 구성해야합니다. 자세한 내용은 플레이 스레드 풀 이해를 참조하십시오.

액터를 사용하여 작업을 차단하는 것도 도움이 될 수 있습니다. 액터는 타임 아웃 및 오류 처리, 실행 컨텍스트 블로킹 설정 및 서비스와 관련된 모든 상태 관리를위한 명확한 모델을 제공합니다. 또한 액터는 동시 캐시 및 데이터베이스 요청을 처리하고 백엔드 서버 클러스터에서 원격 실행을 허용하는 ScatterGatherFirstCompletedRouter와 같은 패턴을 제공합니다. 그러나 액터는 필요에 따라 과도하게 동작 될 수 도 있습니다.

퓨쳐 되돌려주기

지금까지 액션을 만들기 위해 Action.apply 빌더 메소드를 사용 했지만 비동기 결과를 보내기 위해 Action.async 빌더도 사용된다. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext

def index = Action.async {
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  futureInt.map(i => Ok("Got result: " + i))
}

액션은 기본적으로 비동기적이다 

플레이 액션은 기본적으로 비동기적이다. 예를 들어 아래 컨트롤러 코드 처럼 말이다.  { Ok(...) } 이 부분은 컨트롤러의 메소드 바디가 아니다. 익명 함수이며 Action 객체의 apply 메소드에 전달되며 Action 타입의 객체를 만든다. 내부적으로 익명 함수가 호출 될 것이며 그것의 결과는 퓨처로 감싸질 것이다.  그럼  Action.async 는 왜 만든거지?

val echo = Action { request =>
  Ok("Got request [" + request + "]")
}

Note: Action.apply 과 Action.async 양쪽 모두  Action objects 를 만들며 내부적으로 동일하게 다루어 진다.  .async 빌더는 그냥 퓨처를 돌려주는 API 기반의 액션을 간단히 만들기 위한 장치일 뿐이다. 비동기코드를 쓰는것을 좀 더 쉽게 만들어 준다는 것일 뿐.

타임아웃 다루기

종종 타임아웃을 적절히 다루게 되는데 웹브라우저가 너무 오랫동안 블럭되면 사용자들이 짜증날테니깐...프라미스에 타임아웃을 설정해서 그 때 까지 결과가 도착 안하면 님이 만든 예러 메세지를  그냥 돌려주게 하자. 아래는 InternalServerError 를 돌려준다. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._

def index = Action.async {
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops", 1.second)
  Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map {
    case i: Int => Ok("Got result: " + i)
    case t: String => InternalServerError(t)
  }
}

Next: Streaming HTTP responses


'PlayFramework2' 카테고리의 다른 글

[Play2] Action 과 Action.async 의 차이점  (0) 2017.02.26
[Play2] Iteratee & Enumerators 간단 정리  (0) 2017.02.18
[Play2] WebSockets  (0) 2016.10.13
[Play2] 외부의 원격액터와 통신  (0) 2016.10.11
[Play2] WS API (번역)  (0) 2016.10.11

WebSockets


WebSockets 는 웹 브라우저에서 양방향 통신을 가능케 하는 프로토콜 기반으로 사용 될 수 있습니다. (역주: 서버쪽에서 웹브라우저쪽으로도 메세지를 보낸다는 뜻이죠. 기존에는 브라우저에서 서버쪽으로 요청하는 폴링을 주로 사용 했었음) 클라이언트는 메세지를 보낼 수 있으며 서버는 언제라도 메세지를 받을 수 있습니다. 물론 그들 사이에 WebSocket 연결이 액티브 상태일 동안 말이죠. 

현재 HTML5 구현이 된 웹 브라우저들은 대부분 자바스크립트 웹소켓 API 를 통한 WebSockets를 지원 하고 있습니다. 그러나 웹소켓이 오직 웹 브라우저에서만 그 의미를 갖는 것은 아닙니다. 많은 웹소켓 클라이언트 라이브러리들을 활용 할 수 있으며 예를들어 서버가 다른 서버와 통신하기 위해서도 사용 할 수 있습니다네이티브 모바일 앱과도 말이죠. WebSockets 을 여기서 사용하는것은 플레이 서버가 사용하는 기존 TCP 포트를 재사용할 수 있는 이득을 가져다 줍니다. 

WebSockets 조작

지금까지 우리는 Action 인스턴스를 표준 HTTP 요청과 응답을 위해서 사용 하였습니다. 
WebSockets 은 전체적으로 다르게 사용되며 표준 Action 을 경유해서 조작되지 않습니다.

플레이는 두가지 다른 방식으로 웹 소켓을 지원하는데 첫번째는 액터를 사용하는것이고 두번째는 iteratees 를 사용하는 것입니다. 양쪽 모두WebSocket 에서 제공된 빌더를 사용합니다.

1. WebSockets 을 액터와 함께 조작하기 


WebSocket 의 acceptWithActor 메소드를 사용하여 웹소켓 통신 할 수 있는 환경을 만듭니다. 사용자 정의 액터에  props 의 매개변수로 전달 된 out 즉 외부에 메세지를 전송 할 수 있는 연결 통로를 제공합니다.

import play.api.mvc._
import play.api.Play.current

def socket = WebSocket.acceptWithActor[String, String] { request => out =>
  MyWebSocketActor.props(out)
}

acceptWithActor 메소드의 내부는 아래와 같습니다. 궁금하실까봐..복잡합니다. @@

def acceptWithActor[In, Out](f: RequestHeader => HandlerProps)(implicit in: FrameFormatter[In],
out: FrameFormatter[Out], app: Application, outMessageType: ClassTag[Out]): WebSocket[In, Out] = {
tryAcceptWithActor { req =>
Future.successful(Right((actorRef) => f(req)(actorRef)))
}
}


MyWebSocketActor 의 구현은 다음과 같습니다 : 

import akka.actor._

object MyWebSocketActor {
  def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}

class MyWebSocketActor(out: ActorRef) extends Actor {
  def receive = {
    case msg: String =>
      out ! ("I received your message: " + msg)
  }
}

클라이언트에서 보낸 메세지는 사용자 정의 액터에게 보내 질 것입니다. 그리고 다시 메세지를 돌려 줄 수 있습니다. 위의 액터는 메시지를받은 상태에서 클라이언트에서받은 모든 메시지를 간단히 보냅니다.


2. WebSockets 을 iteratees 로 조작하기

액터는 분리 된 메시지를 처리하기위한 더 나은 추상화이지만, iteratees 는 스트림 처리 에있어 더 좋은 추상화입니다. WebSocket 요청을 처리하려면 Action 대신 WebSocket을 사용합니다.

import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Execution.Implicits.defaultContext

def socket = WebSocket.using[String] { request =>
  // Log events to the console
  val in = Iteratee.foreach[String](println).map { _ =>
    println("Disconnected")
  }

  // Send a single 'Hello!' message
  val out = Enumerator("Hello!")

  (in, out)
}

WebSocket은 요청 헤더 (WebSocket 연결을 시작하는 HTTP 요청)에 액세스 할 수 있으므로 표준 헤더와 세션 데이터를 검색 할 수 있습니다. 그러나 요청 본문이나 HTTP 응답에는 액세스 할 수 없습니다. 이 방법으로 WebSocket을 구성 할 때 우리는 in과 out 채널을 모두 반환해야합니다.

 - in 채널은 각 메시지에 대해 통지 될 Iteratee [A, Unit] (여기서 A는 메시지 유형 - 여기서 우리는 String을 사용함)이며 소켓이 클라이언트 측에서 닫힐 때 EOF를 수신합니다.

- out 채널은 웹 클라이언트에 보낼 메시지를 생성하는 열거 자 [A]입니다. EOF를 전송하여 서버 측의 연결을 닫을 수 있습니다.

이 예제에서는 각 메시지를 콘솔에 출력하는 간단한 iteratee를 생성합니다. 메시지를 보내려면 간단한 더미 enumerator 를 만들어 하나의 Hello! 메시지를 전송하세요.

Tip: WebSockets 은  https://www.websocket.org/echo.html. 여기서 테스트 가능합니다.  위치는 그냥 ws://localhost:9000. 로 설정하시구요.


Hello!를 전송 한 직후 입력 데이터를 버리고 소켓을 닫는 다른 예제를 작성해 보겠습니다. 

import play.api.mvc._
import play.api.libs.iteratee._

def socket = WebSocket.using[String] { request =>

  // Just ignore the input
  val in = Iteratee.ignore[String]

  // Send a single 'Hello!' message and close
  val out = Enumerator("Hello!").andThen(Enumerator.eof)

  (in, out)
}


다음은 입력 데이터가 표준 출력에 기록되고 Concurrent.broadcast를 사용하여 클라이언트에 브로드 캐스팅되는 다른 예입니다.

import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Execution.Implicits.defaultContext

def socket =  WebSocket.using[String] { request =>

  // Concurrent.broadcast returns (Enumerator, Concurrent.Channel)
  val (out, channel) = Concurrent.broadcast[String]

  // log the message to stdout and send response back to client
  val in = Iteratee.foreach[String] {
    msg =>
      println(msg)
      // the Enumerator returned by Concurrent.broadcast subscribes to the channel and will
      // receive the pushed messages
      channel push("I received your message: " + msg)
  }
  (in,out)
}




Play2 - 리모트 액터와 통신



개념 


 사물인터넷  서비스를 생각해보자.  브라우저나 스마트폰을 통해 웹서비스에  명령을 전달하여  전등이 꺼지는 서비스이다. 웹서비스는 명령을 전달 받아서 외부 미들웨어에 전달해야할 것이다. 이때 외부 미들웨어는 아카리모트로 되어 있다고 하자.  Play2 는 내부에 아카시스템이 있는데 이 걸 사용할 순 없고 또 다른 하나의 아카리모트 시스템을 만들어서 외부의 아카리모트와 통신하도록 하는 전략을 세워본다.


설정 


 
먼저 아카 시스템을 설정한다. 또하나의 conf 파일을 만들어서 아래와 같이 remote provider 와 접속 대상의 패스를 설정한다. 
include "common"

Provider {
akka {
loglevel = "DEBUG"
loggers = ["akka.event.slf4j.Slf4jLogger"]
actor {
provider = "akka.remote.RemoteActorRefProvider"

default-dispatcher {
}
}

remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 24567
}

log-sent-messages = on
log-received-messages = on
}
}

}


RelayPath = "akka.tcp://relayRemote@192.168.1.148:24321/user/relay"


컨트롤러

package controllers
import play.api.mvc._
import akka.actor._
import javax.inject._

import play.api.inject.ApplicationLifecycle
import play.api.libs.concurrent.Execution.Implicits.defaultContext

import scala.concurrent.duration._
import services.TalkToRelay
import services.Runner


import scala.concurrent.Future

@Singleton
class ConfigurationCtrl @Inject()(lifecycle: ApplicationLifecycle) extends Controller {

lazy val ttr = new TalkToRelay()

lifecycle.addStopHook { () =>
Future.successful(ttr.terminate())
}

def setDataStorage(ip: String) = Action {
println("....................... setDataStorage.................")
ttr.sendDataStorageIP(ip)
Ok("OK")
}
}
 서비스 객체를 만들고 플레이가 죽을때 원격시스템도 종료시키기 위해서 라이프싸이클 설정을 해줬다.
 지금은  tell 패턴을 사용했지만 ask 패턴을 사용한다면 리턴값을 Future 로 받을 수 있을 것이다.


서비스  


val config = ConfigFactory.load("remoteProvider")
val remoteSystem = ActorSystem("seiws", config.getConfig("Provider"))
val runner = remoteSystem.actorOf(Props[Runner], "runner")


def sendDataStorageIP(ip : String) = {
Logger.info("....................... remotingSystem start .................")
runner ! ip
}

def terminate() = {
remoteSystem.terminate()
Logger.info("....................... remotingSystem terminated .................")

}
}

 설정파일로 부터 설정을 가지고 와서 우리 자신만의 아카 시스템을 생성했다.

액터
val path =  ConfigFactory.load("remoteProvider").getString("RelayPath")
val remoteActor = context.actorSelection(path)


context.setReceiveTimeout(3.seconds)
sendIdentifyRequest()

def sendIdentifyRequest(): Unit =
remoteActor ! Identify(path)

def receive = identifying


def identifying: Receive = {
case ActorIdentity(`path`, Some(actor)) =>
context.watch(actor)
context.become(active(actor))
context.setReceiveTimeout(Duration.Undefined)

case ActorIdentity(`path`, None) => Logger.info(s"Remote actor not available: $path")
case ReceiveTimeout => Logger.info(s"ReceiveTimeout"); sendIdentifyRequest()
}

def active(actor: ActorRef): Receive = {
case msg : String =>
Logger.info("send a msg to remote actor ")
remoteActor ! msg
case "Shutdown" => remoteActor ! Shutdown
case _ => Logger.info(" receive a invalid msg ")
}

설정파일로 부터 원격액터 경로를 가지고서 액터 셀렉션을 통해 액터 참조를 구해서 통신한다. 
초기에 원격노드가 살아있는지 체킹 (예제에서는 3초) 하는 과정을 거치고 있다. 원격노드가 살아 있다면 identifying 에서  active 로 상태변경을 하고 메세지 처리를 한다.

'PlayFramework2' 카테고리의 다른 글

[Play2] ScalaAsync (번역)  (0) 2016.10.14
[Play2] WebSockets  (0) 2016.10.13
[Play2] WS API (번역)  (0) 2016.10.11
[Play2] 마이크로서비스 (microservices)  (0) 2016.10.11
[Play2] Akka 의 결합 - (번역)  (0) 2016.10.10

- Scala 2.11 기반 

- Akka 2.4.11 기반 

Play2.4 공식문서 참고



Play2 WS API 



개념 


때때로 플레이 어플리케이션 안에서 HTTP 서비스를 호출하고 싶을때가 있다.(역주: 마이크로 서비스 패턴에서 주로 사용함)  플레이는 이것을 위해  WS library 를 지원하는데 비동기 HTTP 콜을 할 수 있도록 도와준다. (역주: 시간이 걸릴 듯한 업무에 대한 처리를 맡기는데 사용..즉 푸쉬알람같은거 보내도록 메세지 보내놓고 바로 리턴하기 위함.)

 WS API 에는 중요한 2가지가 있는데 리퀘스트를 만드는것과 응답을 처리하는것이다.  먼저 GET POST HTTP 리퀘스트에 대해서 논의할 것이고 WS 부터오는 응답을 어떻게 처리할 것인지 살펴볼 것이다.  마지막으로 사용예에 대해서 살펴보자.


.. 정리중 ..


예제 




Play2 microservices 작성중.




개념 




예제 


'PlayFramework2' 카테고리의 다른 글

[Play2] 외부의 원격액터와 통신  (0) 2016.10.11
[Play2] WS API (번역)  (0) 2016.10.11
[Play2] Akka 의 결합 - (번역)  (0) 2016.10.10
[Play2] Remote Akka 연결하기 (번역)  (0) 2016.10.10
[Play2] DI (의존성 주입)  (0) 2016.10.10

Akka 와 함께 사용하기


아래 내용을 먼저 읽어서 Play 와 Akka 에 대한 관계를 먼저 파악을..

  @ In Play 2.0, Play delegated all requests to go through an actor. It heavily depended on Akka's future API and other parts.

  @ In Play 2.1, with the move of Akka's future API into Scala 2.10, Play started depending less directly on Akka. It gets all it's execution contexts from Akka, and provides integration with Akka, but that's about the extent of it.

  @ In Play 2.3, we're adding new features to aid Akka integration, particularly around WebSockets.

  @ In Play 2.4, Play will be ported to the new akka-http (formerly known as spray), at which point, Play will be as built on Akka as you can get.



Akka 는  확장성 높고 병행성있는 어플리케이션을 만들기 위해 더 나은 플랫폼을 제공하고 추상층을 높힌 액터모델을 사용하고 있다. 실패나 예외에 대해 대응하기위해서 ‘고장나게 냅둬라’ 모델을 채택하고있는데 , 절대 멈추지 않고 스스로 치유될 수 있는 어플리케이션을 만듬으로써  텔레콤 산업에서 큰 성공을 거두었다. 액터는 또한 위치투명성을 제공하여 손쉬운 분산프로그래밍, 클러스터링에 적합하다.

§액터시스템 어플리케이션

아카는 액터 시스템이라는 컨테이너에서 사용된다. 액터 시스템은 그것이 포함하고 있는 리소스들을 관리한다. 플레이 어플리케이션은 특별한 액터 시스템을 정의하여 사용한다. 이 액터 시스템은 어플리케이션 라이프 사이클을 관리하고 어플리케이션이 재시작되었을때 자동적으로 다시 시작하게 해준다.


액터 작성하기

일단 액터하나를 간단히 만들어 보자.  "Hello" 에 이름을 붙여서 다시 돌려주는 액터이다. 

import akka.actor._

object HelloActor {
def props = Props[HelloActor]

case class SayHello(name: String)
}

class HelloActor extends Actor {
import HelloActor._

def receive = {
case SayHello(name: String) =>
sender() ! "Hello, " + name
}
}

이 액터는 다음과 같은 아카 관례를 따른다:

  • - 메세지를 보내고 받을 수 있으며  , 그 프로토콜 (SayHello ) 은 동반 객체에 정의되있다. 
  • - Props 메소드를 동반 객체에 가지고 있다. 액터를 Props 팩토리 메소드를 이용하여 생성한다.


액터 생성하고 사용하기 

ActorSystem 이  액터를 생성하고 사용하기 위해 필요하다. 다음과 같이 만든다.  

import play.api.mvc._
import akka.actor._
import javax.inject._

import actors.HelloActor

@Singleton
class Application @Inject() (system: ActorSystem) extends Controller {

val helloActor = system.actorOf(HelloActor.props, "hello-actor")

//...
}

actorOf 는 새로운 액터를 생성하기 위해 사용된다.  싱글턴으로 선언한것을 눈여겨 보시라. 우린 액터와 그 레퍼런스들을 저장할 것 이기 때문에 필요한 부분이다.  만약 그렇지 않다면 컨트롤러가 만들어질때마다 새로운 액터가 만들어질 거란 의미이기 때문이다.  동일한 시스템에 동일한 액터를 가질 수 없다. 


액터에게 요청하기 

가장 기본적인 일은  액터에게 메세지를 전송하는 일이다. 액터에게 메세지를 보내면 반응이 없을 것인데 기본적으로  "보내고 잊기"  방식이기 때문이다. 보통 tell 패턴으로 알려져있다.

웹어플리케이션에서 tell 패턴 말고 다른 전력이 필요할때가 많이 있다. HTTP 는 요청하고 반응하는 프로토콜인데 이 경우에 ask 패턴을 사용할 가능성이 클 것이다. ask 패턴은 Future 를 리턴하는데 아래 예를 살펴보자. 

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
implicit val timeout = 5.seconds

def sayHello(name: String) = Action.async {
    (helloActor ? SayHello(name)).mapTo[String].map { message =>
     Ok(message)
    }
}

설명 해 보면 :

  • 먼저 ask 패턴은 임포트 되야하고, 이것은  ? 연산자를 액터에서 제공한다. (tell 은 ! 이다) 
    즉 helloActor 에게 SayHello(name) 메세지를 보내는것이다. 보낸 후에 바로 Future[Any] 를 받는다. 그 후에 mapTo 메소드로  기대하는 타입으로 매핑을 하고 , 최종적으로 돌려 받은 메세지를 Ok(message) 로 돌려준다. 
    타임아웃도 스코프내에 필요한데 타임아웃내에 반응이 오지 않으면 타임아웃 에러를 내보내면 무시한다. 

 액터 의존성 주입

원한다면 Guice  를 가지고 액터와 액터 설정을 바인드 할 수도 있다.
플레이 설정을 활용하는 액터를 원한다면 이렇게 하면 된다. 

import akka.actor._
import javax.inject._
import play.api.Configuration

object ConfiguredActor {
case object GetConfig
}

class ConfiguredActor @Inject() (configuration: Configuration) extends Actor {
import ConfiguredActor._

val config = configuration.getString("my.config").getOrElse("none")

def receive = {
case GetConfig =>
sender() ! config
}
}


플레이는 액터바인딩을 제공하는데 도움을 주는 몇몇 헬퍼를 제공한다. 이것들은 액터 스스로이 DI 되게 하며, 다른 컴포넌트들 안으로 주입되는 액터에 대한 액터 참조를 허용한다. 이런 헬퍼들을 사용하는 액터를 바인드 하기위해 다음 문서에서 나타내는 모듈을 만든다.  dependency injection documentation, 그리고 AkkaGuiceSupport 트레잇과 믹스하고  bindActor 메소드를 액터와 바인드 하기위해 사용한다:

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

import actors.ConfiguredActor

class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[ConfiguredActor]("configured-actor")
}
}


이 액터는 configured-actor 로 이름 붙혀지고 주입을 위한 자격이 부여될 것이다.
이제 다른 컴포넌트들과 당신의 컨트롤러 안에서 액터를 활용 할 수 있다. 

import play.api.mvc._
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import javax.inject._
import actors.ConfiguredActor._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

@Singleton
class Application @Inject() (@Named("configured-actor") configuredActor: ActorRef)
(implicit ec: ExecutionContext) extends Controller {

implicit val timeout: Timeout = 5.seconds

def getConfig = Action.async {
(configuredActor ? GetConfig).mapTo[String].map { message =>
Ok(message)
}
}
}

자식 액터에게 의존성 주입하기 

루트 액터에게 주입하는 방법은 위에 보았지만 많은 액터들은 자식 액터들일 것이다. 그들은 플레이 앱의 라이프 싸이클에 묶여있지 않기 때문에 그들에게 상태를 전달해줘야한다.

그러기 위해서 플레이는  Guice’s AssistedInject 를 지원한다.

주입될때 설정과 함께 작동하는 키가 추가 될 수 있다.  

import akka.actor._
import javax.inject._
import com.google.inject.assistedinject.Assisted
import play.api.Configuration

object ConfiguredChildActor {
case object GetConfig

trait Factory {
def apply(key: String): Actor
}
}

class ConfiguredChildActor @Inject() (configuration: Configuration,
@Assisted key: String) extends Actor {
import ConfiguredChildActor._

val config = configuration.getString(key).getOrElse("none")

def receive = {
case GetConfig =>
sender() ! config
}
}

key 파라미터가 @Assisted 으로 선언된것을 확인하자. 이것은 수동으로 제공되었다는것을 말한다. 


우리는 Factory 트레잇을 정의했다. 이것은 key 를 가지고 액터를 리턴한다. 우린 이것을 구현하지 않을 것인데 Guice 가 우리를 위해 구현 할 것이다. 키 파라미터를 전달하는것 뿐 만 아니라 설정 디펜던시를 위치시키는것 그리고 저것을 주입하는일 등의 구현을 제공한다. 트레잇은 그저 액터를 리턴한다.  이 액터를 테스팅 할때 어떤 액터라도 리턴 할 수 있는 팩토리를 주입 할 수 있을 것이다.예를들어 우리에게 가짜 자식 액터를 주입할 수 있게 할 것이다. 

이제 액터는 InjectedActorSupport  를 확장하며 우리가 만든 팩토리를 사용할 것이다.

import akka.actor._
import javax.inject._
import play.api.libs.concurrent.InjectedActorSupport

object ParentActor {
case class GetChild(key: String)
}

class ParentActor @Inject() (
childFactory: ConfiguredChildActor.Factory
) extends Actor with InjectedActorSupport {
import ParentActor._

def receive = {
case GetChild(key: String) =>
val child: ActorRef = injectedChild(childFactory(key), key)
sender() ! child
}
}

 injectedChild 를 이용하여 자식 액터에 대한 참조를 얻는다. 

마지막으로 우리의 액터를 바인드 할 필요로 해졌다. 우리의 모듈상에서   bindActorFactory 메소드를 사용하여  부모 액터를 바인드 한다.. 그리고 또한 자식 팩토리를 자식 구현을 위해 바인드 한다.

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

import actors._

class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[ParentActor]("parent-actor")
bindActorFactory[ConfiguredChildActor, ConfiguredChildActor.Factory]
}
}

ConfiguredChildActor.Factory인스턴스를 자동으로 바인드하기위해 Guice 를 얻는다.그것은  ConfiguredChildActor를 위한 설정 인스턴스를 제공 할 것이다.

설정하기 

기본 액터 시스템 설정은 플레이 어플리케이션 설정 파일에서 읽혀진다. 예를들어 어플리케이션 액터 시스템의 디폴트 디스패처 를 설정한다고 치면 conf/application.conf에 이걸 추가한다

akka.actor.default-dispatcher.fork-join-executor.parallelism-max = 64
akka.actor.debug.receive = on

아카 로깅 설정은 이것을 참고 하라 ->  configuring logging.

설정 prefix 바꾸기

또 다른 아카 액터 시스템을 위해 akka.* 세팅을 이용하길 원하면 다음 처럼

play.akka.config = "my-akka"

이제 앞으로  akka 대신해서  my-akka 에서 읽혀질 것이다. 

my-akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64
my-akka.actor.debug.receive = on

빌트인 액터 시스템 이름 

플레이 액터 시스템의 기본 이름은 application. 이며 변경 가능하다.

play.akka.actor-system = "custom-name"

Note: 아카 클러스터 내에서 플레이 아카를 사용 하는데 매우 유용하다. 

비동기 태스크 스케쥴링하기.

액터로 task 를 실행하거나 메세지를 전송하는 것을 스케쥴링 할 수 있다.  (펑션 or Runnable). Cancellable 돌려 받고 , 스케쥴 연산을 실행한것을 취소하기위해 cancel 를 사용한다.

예를들어 testActor 매  300 microseconds 동안 메세지를 보낸다.

import scala.concurrent.duration._

val cancellable = system.scheduler.schedule(
0.microseconds, 300.microseconds, testActor, "tick")

Note: This example uses implicit conversions defined in scala.concurrent.durationto convert numbers to Duration objects with various time units.

유사하게 지금 부터 10밀리세컨드초에 다음 코드를 실행 시킨다.  

import play.api.libs.concurrent.Execution.Implicits.defaultContext
system.scheduler.scheduleOnce(10.milliseconds) {
file.delete()
}

자신만의 액터 시스템 사용하기 

빌트인 액터 시스템을 사용하길 추천하지만 , 바르게 설정된 클래스로더, 라이프싸이클 훅 등 같은 것을 셋 업하는것으로 당신의 액터 시스템을 사용 할 수 도 있다. 다음을 꼭 참조하세요. 

  • stop hook 을 등록하세요. 플레이가 종료되면 액터 시스템도 종료 되도록 말이죠.
  • * 정확한 클래스로더를 Play Environment 로 부터 전달하세요. 그렇지 않으면 아카는 당신의 어플리케이션 클래스들을 찾을 수 없을테니까요. 
  • * play.akka.config 를 사용하여  플레이가 아카 설정을 읽는 위치를 변경 시키든지, 시스템이 동일한 리모트 포트를 바인드하려 할때 문제를 일으킬 수 있으므로 디폴트 아카 config 로부터 아카 설정을 읽지 않는다.  



'PlayFramework2' 카테고리의 다른 글

[Play2] WS API (번역)  (0) 2016.10.11
[Play2] 마이크로서비스 (microservices)  (0) 2016.10.11
[Play2] Remote Akka 연결하기 (번역)  (0) 2016.10.10
[Play2] DI (의존성 주입)  (0) 2016.10.10
[Play2] Cookie 와 Session  (0) 2016.09.29


Play Framework - 원격 Akka 액터에 연결하기

원본 : http://qiita.com/visualskyrim/items/350ba0112cd9a95388ff

참고:


디펜던시 추가 

build.sbt 에  Akka 원격 라이브러리를 추가한다. 

libraryDependencies ++= Seq(
  // ... other libs
  "com.typesafe.akka" %% "akka-remote" % "2.3.4"
)

Akka 설정 조정 

For API 

Play API 를 위해서 conf/application.conf 에 있는 디폴트 Akka 세팅을 다음과 같이 변경 한다.

akka {
  actor {
    provider = "akka.remote.RemoteActorRefProvider" # offer the provider
  }

  remote {
    enabled-transports = ["akka.remote.netty.tcp"] # enable protocol
    netty.tcp {
      hostname = "127.0.0.1" # your host
      port = 2553 # port
    }
  }
}

만약 설정 파일에 아카 오브젝트가 없다면, 설정파일에 그냥 추가하라.

For Actor

위에서 했던 것과 비슷한 설정을src/main/resources/application.conf에 하라.

yourSystem { # the name your actor system is going to use
  akka { 
    # other thing is just the same as that in API
    loglevel = "DEBUG"
    loggers = ["akka.event.slf4j.Slf4jLogger"]

    actor {
      provider = "akka.remote.RemoteActorRefProvider"

      default-dispatcher {
      }
    }

    remote {
      enabled-transports = ["akka.remote.netty.tcp"]
      netty.tcp {
        hostname = "127.0.0.1"
        port = 2552 # Note if you are running API and Actor program in localhost, make sure they are not using the same port
      }
    }
  }
}

 그리고나서 코드에 이 세팅을 적용하라.

// ... some codes for launch the program val system = ActorSystem("CoolSystem", config.getConfig("yourSystem"))

Note 위의 provider 를 해당 아카 버전으로  주의 있게 바꾸고 올바른 프로바이더를 선택하라.

Program

API

자바를 이용해서 플레이를 사용할 것이다. 스칼라에 대한 것은 여기를 봐라.  this.

컨트롤러안에서 리모트 액터로 연결한다.  아래 코드 참고 :

public static F.Promise<Result> askYourActorSomething(final String info) { String actorPath = actorHelper.getPath(); // get akka path of your worker, this will not show in my example ActorSelection actor = Akka.system().actorSelection(actorPath); return play.libs.F.Promise.wrap(ask(actor, new MessageToActor(info), 5000)).map( // use ask pattern so that we can get sync response from actor; wrap into Promise new F.Function<Object, Result>() { // the callback when actor sends back response public Result apply(Object resp) { return ok((String) resp); } } ); }

여기서 주의깊게 봐야할 포인트는 만약 ask 패턴을 사용한다면 결과를 Promise 로 감싸야한다는점.

Actor

액터 코드:

class Worker extends Actor {
  override def receive = {
    case MessageToActor(info) => // get message from API
      sender ! "worked!" //  response to API
  }
}

런치 코드

// fetch configs
val remoteConfig = ConfigFactory.load.config.getConfig("yourSystem").getConfig("akka").getConfig("remote").getConfig("netty.tcp")
val actorHost = remoteConfig.getString("hostname")
val actorPort = remoteConfig.getInt("port")
val workerName = "worker"

val actorPath = "akka.tcp://" + "yourSystem" + "@" + actorHost + ":" + actorPort + "/user/" + workerName
println(actorPath) // here you know what your actor's path is, well, just for show, don't do this sort of thing your code.
val system = ActorSystem("CoolSystem",config.getConfig("yourSystem"))
val actor = system.actorOf((new Worker()), name = workerName)

이제 만약 컨트롤러  askYourActorSomething  가 호출되면, 경로에 해당하는 당신의 액터에 메세지를 보낼 것이다. 그리고 나서 액터는 이 메세지를 받고 API 컨트롤러에게 다시 문자를 돌려 줄 것이다.  마지막으로 API  는 "worked!" 를 리턴 할 것 이다.

한 가지 더 ..

제품화된  플레이 어플리케이션에서 원격 액터를 사용할 예정이라면  , 특별히 분산 환경이라면 , 좀 더 해야할 것들이 생긴다..

방화벽

API 와 액터 프로그램이 서로 접근하지 못하게 할 것이다.

만약 EC2 를 사용한다면 security groups. 를 세팅해서 해결하라.  서로 다른 그룹의 인바운드에 있는지 확인해야한다. 



런타임 의존성 주입  


의존성 주입은 컴포넌트들끼리 서로 독립적이게 하기 위한 방법이다. 서로를 정적으로 포함하기보다는 동적으로 서로에게 주입된다.  플레이는 JSR 330. 기반으로 런타임 의존성 주입을 지원한다. 의존해야하는 특정 컴포넌트를 발견하지 못하면 실행할때까지는 에러를 발견하지 못할것인데  플레이는 컴파일타임 DI 를 또한 지원한다. 플레이에서는 디폴트 의존성 주입 기능으로 Guice 사용한다.  하지만 뭐 다른것을 사용해도 좋다. 

디펜던시 선언 

컨트롤러나 컴포넌트를 가지고 있을때 다른 컴포넌트를 요구할 수 있는데 이때  @Inject 어노데이션을 사용한다. @Inject 는 필드나 생성자에서 사용될 수 있지만 생성자에서 사용하길 추천한다.
예를 보자:

import javax.inject._
import play.api.libs.ws._

class MyComponent @Inject() (ws: WSClient) {
  // ...
}

 @Inject 는 클래스 이름 뒤에 오고 생성자 전에 온다 반드시 괄호를 써줘라.

의존성 주입 컨트롤러 

2가지 방법의 의존성 주입 컨트롤러를 만들 수 있다. 

Injected routes generator

디폴트로 플레이는 정적라우터를 생성할것이고 모든 액션들이 정적 메소드라고 보면 된다. 설정을 변경해서 injected routes generator 로 변경할 수 있으며 각각 디펜던시로써 라우트 된다. 즉 당신의 컨트롤러들을 스스로 의존성 주입되게 할 수 있다는 뜻이다. (역주: 디폴트로 컨트롤라가 object 였을것이다. 이거 사용하면 class 로 변경가능해짐.)

우리는 injected routes generator 쓰기를 추천한다.  이것을 쓰기위해서 설정을 해야하는데 

build.sbt: 파일에 다음을 추가하면 된다. 

routesGenerator := InjectedRoutesGenerator


Injected actions

정적 라우트 제네레이터 를 사용하면 @ 와 함께 액션을 주입하게 지시 할 수 있다. 

GET        /some/path           @controllers.Application.index

lifecycle 컴포넌트 생존주기

의존성 주입 시스템은 생존주기를 관리하는데 다음 과 같다. 

  • 해당 컴포넌트가 필요 할때 마다 새 인스턴스를  만든다 : 컴포넌트가 한번이상 사용될때 디폴트로 여러번 생성된다.  단지 한번만 생성되게 하고 싶으면  singleton.주석을 사용하라. 
  • 필요해 질때, 게으른 초기화로 만들어진다.  If a component is never used by another component, then it won’t be created at all. This is usually what you want. For most components there’s no point creating them until they’re needed. However, in some cases you want components to be started up straight away or even if they’re not used by another component. For example, you might want to send a message to a remote system or warm up a cache when the application starts. You can force a component to be created eagerly by using an eager binding.
  • 인스턴스가 자동으로 청소되지 않는다. beyond normal garbage collection. Components will be garbage collected when they’re no longer referenced, but the framework won’t do anything special to shut down the component, like calling aclose method. However, Play provides a special type of component, called theApplicationLifecycle which lets you register components to shut down when the application stops.

Singletons

Sometimes you may have a component that holds some state, such as a cache, or a connection to an external resource, or a component might be expensive to create. In these cases it may be important that there is only be one instance of that component. This can be achieved using the @Singleton annotation:

import javax.inject._

@Singleton
class CurrentSharePrice {
  @volatile private var price = 0

  def set(p: Int) = price = p
  def get = price
}

Stopping/cleaning up

Some components may need to be cleaned up when Play shuts down, for example, to stop thread pools. Play provides an ApplicationLifecycle component that can be used to register hooks to stop your component when Play shuts down:

import scala.concurrent.Future
import javax.inject._
import play.api.inject.ApplicationLifecycle

@Singleton
class MessageQueueConnection @Inject() (lifecycle: ApplicationLifecycle) {
  val connection = connectToMessageQueue()
  lifecycle.addStopHook { () =>
    Future.successful(connection.stop())
  }

  //...
}

The ApplicationLifecycle will stop all components in reverse order from when they were created. This means any components that you depend on can still safely be used in your components stop hook, since because you depend on them, they must have been created before your component was, and therefore won’t be stopped until after your component is stopped.

Note: It’s very important to ensure that all components that register a stop hook are singletons. Any non singleton components that register stop hooks could potentially be a source of memory leaks, since a new stop hook will be registered each time the component is created.

Providing custom bindings

It is considered good practice to define an trait for a component, and have other classes depend on that trait, rather than the implementation of the component. By doing that, you can inject different implementations, for example you inject a mock implementation when testing your application.

In this case, the DI system needs to know which implementation should be bound to that trait. The way we recommend that you declare this depends on whether you are writing a Play application as an end user of Play, or if you are writing library that other Play applications will consume.

Play applications

We recommend that Play applications use whatever mechanism is provided by the DI framework that the application is using. Although Play does provide a binding API, this API is somewhat limited, and will not allow you to take full advantage of the power of the framework you’re using.

Since Play provides support for Guice out of the box, the examples below show how to provide bindings for Guice.

Binding annotations

The simplest way to bind an implementation to an interface is to use the Guice@ImplementedBy annotation. For example:

import com.google.inject.ImplementedBy

@ImplementedBy(classOf[EnglishHello])
trait Hello {
  def sayHello(name: String): String
}

class EnglishHello extends Hello {
  def sayHello(name: String) = "Hello " + name
}

Programmatic bindings

In some more complex situations, you may want to provide more complex bindings, such as when you have multiple implementations of the one trait, which are qualified by@Named annotations. In these cases, you can implement a custom Guice Module:

import com.google.inject.AbstractModule
import com.google.inject.name.Names
  
class HelloModule extends AbstractModule {
  def configure() = {

    bind(classOf[Hello])
      .annotatedWith(Names.named("en"))
      .to(classOf[EnglishHello])

    bind(classOf[Hello])
      .annotatedWith(Names.named("de"))
      .to(classOf[GermanHello])
  }
}

To register this module with Play, append it’s fully qualified class name to theplay.modules.enabled list in application.conf:

play.modules.enabled += "modules.HelloModule"

Configurable bindings

Sometimes you might want to read the Play Configuration or use a ClassLoaderwhen you configure Guice bindings. You can get access to these objects by adding them to your module’s constructor.

In the example below, the Hello binding for each language is read from a configuration file. This allows new Hello bindings to be added by adding new settings in yourapplication.conf file.

import com.google.inject.AbstractModule
import com.google.inject.name.Names
import play.api.{ Configuration, Environment }
  
class HelloModule(
  environment: Environment,
  configuration: Configuration) extends AbstractModule {
  def configure() = {
    // Expect configuration like:
    // hello.en = "myapp.EnglishHello"
    // hello.de = "myapp.GermanHello"
    val helloConfiguration: Configuration =
      configuration.getConfig("hello").getOrElse(Configuration.empty)
    val languages: Set[String] = helloConfiguration.subKeys
    // Iterate through all the languages and bind the
    // class associated with that language. Use Play's
    // ClassLoader to load the classes.
    for (l <- languages) {
      val bindingClassName: String = helloConfiguration.getString(l).get
      val bindingClass: Class[_ <: Hello] =
        environment.classLoader.loadClass(bindingClassName)
        .asSubclass(classOf[Hello])
      bind(classOf[Hello])
        .annotatedWith(Names.named(l))
        .to(bindingClass)
    }
  }
}

Note: In most cases, if you need to access Configuration when you create a component, you should inject the Configuration object into the component itself or into the component’s Provider. Then you can read the Configuration when you create the component. You usually don’t need to read Configuration when you create the bindings for the component.

Eager bindings

In the code above, new EnglishHello and GermanHello objects will be created each time they are used. If you only want to create these objects once, perhaps because they’re expensive to create, then you should use the @Singleton annotation asdescribed above. If you want to create them once and also create them eagerly when the application starts up, rather than lazily when they are needed, then you can Guice’s eager singleton binding.

import com.google.inject.AbstractModule
import com.google.inject.name.Names
  
class HelloModule extends AbstractModule {
  def configure() = {

    bind(classOf[Hello])
      .annotatedWith(Names.named("en"))
      .to(classOf[EnglishHello]).asEagerSingleton

    bind(classOf[Hello])
      .annotatedWith(Names.named("de"))
      .to(classOf[GermanHello]).asEagerSingleton
  }
}

Eager singletons can be used to start up a service when an application starts. They are often combined with a shutdown hook so that the service can clean up its resources when the application stops.

Play libraries

If you’re implementing a library for Play, then you probably want it to be DI framework agnostic, so that your library will work out of the box regardless of which DI framework is being used in an application. For this reason, Play provides a lightweight binding API for providing bindings in a DI framework agnostic way.

To provide bindings, implement a Module to return a sequence of the bindings that you want to provide. The Module trait also provides a DSL for building bindings:

import play.api.inject._

class HelloModule extends Module {
  def bindings(environment: Environment,
               configuration: Configuration) = Seq(
    bind[Hello].qualifiedWith("en").to[EnglishHello],
    bind[Hello].qualifiedWith("de").to[GermanHello]
  )
}

This module can be registered with Play automatically by appending it to theplay.modules.enabled list in reference.conf:

play.modules.enabled += "com.example.HelloModule"
  • The Module bindings method takes a Play Environment and Configuration. You can access these if you want to configure the bindings dynamically.
  • Module bindings support eager bindings. To declare an eager binding, add .eagerlyat the end of your Binding.

In order to maximise cross framework compatibility, keep in mind the following things:

  • Not all DI frameworks support just in time bindings. Make sure all components that your library provides are explicitly bound.
  • Try to keep binding keys simple - different runtime DI frameworks have very different views on what a key is and how it should be unique or not.

Excluding modules

If there is a module that you don’t want to be loaded, you can exclude it by appending it to the play.modules.disabled property in application.conf:

play.modules.disabled += "play.api.db.evolutions.EvolutionsModule"

Advanced: Extending the GuiceApplicationLoader

Play’s runtime dependency injection is bootstrapped by the GuiceApplicationLoaderclass. This class loads all the modules, feeds the modules into Guice, then uses Guice to create the application. If you want to control how Guice initializes the application then you can extend the GuiceApplicationLoader class.

There are several methods you can override, but you’ll usually want to override thebuilder method. This method reads the ApplicationLoader.Context and creates aGuiceApplicationBuilder. Below you can see the standard implementation forbuilder, which you can change in any way you like. You can find out how to use theGuiceApplicationBuilder in the section about testing with Guice.

import play.api.ApplicationLoader
import play.api.Configuration
import play.api.inject._
import play.api.inject.guice._

class CustomApplicationLoader extends GuiceApplicationLoader() {
  override def builder(context: ApplicationLoader.Context): GuiceApplicationBuilder = {
    val extra = Configuration("a" -> 1)
    initialBuilder
      .in(context.environment)
      .loadConfig(extra ++ context.initialConfiguration)
      .overrides(overrides(context): _*)
  }
}

When you override the ApplicationLoader you need to tell Play. Add the following setting to your application.conf:

play.application.loader = "modules.CustomApplicationLoader"

You’re not limited to using Guice for dependency injection. By overriding theApplicationLoader you can take control of how the application is initialized. Find out more in the next section.

Next: Compile time dependency injection


+ Recent posts