Play2 는 뼛속부터 비동기로 이루어져 있기 때문에 최강성능&부드러운 서버라는 장점을 가지고 있지만, 비동기라는 그리 직관적이지 않은 기술을 내부에 포함하고 있기 때문에 때론 굉장히 헥깔리게 만들기도 합니다. 하지만 Scala 언어및 다양한 동시성 라이브러리의 지원으로 추상층을 끌어올려 아주 간단한 코드로 그런 강력한 능력을 얻게 해주니깐 걱정마세요 ^^

우리가 SQL 문을 작성할때, 그 짧은 코딩으로 매우 많은 일들이 물밑에서 이루어지는 것처럼 즉 모든것을 알지 않아도 편하게 소기의 성과를 이루는 것처럼, Play2 내부에서 이루어지는 모든 것들을 상세하게 이해하지 않아도 됩니다. "해결" 을 하는게 응용개발자의 목적이니까요.  

이제 Play2 에서 HTTP 안의 body 를 어떻게 다루는지 살펴보겠습니다.


선행 참고) 

스프링에서 컨트롤러 함수의 예가 다음과 같은 모습을 취하는 반면 

@RequestMapping(method = RequestMethod.GET)
public String printHello(ModelMap model) {
model.addAttribute("message", "Hello Spring MVC Framework!");
return "hello";
}

Play2 웹개발에서는 아래와 같습니다. (위의 자바 예와 동일한 내용의 예는 아닙니다.) 

def doSomething = Action{
Ok.apply(views.html.index("Hi there"))
}


Body parsers


바디 파서란 무엇인가요? 

HTTP 요청은 보통 헤더와 함께 body가 따라옵니다. 헤더는 일반적으로 매우 작기 때문에  메모리에 안전하게 버퍼링 될 수 있기 때문에 Play에서는 RequestHeader 클래스를 사용하여 그 내용을 모델링하죠. body는 어떤 경우에는 매우 길 수도 있어서 메모리에 버퍼링 되지 않고 스트림으로 모델링 됩니다.그러나 대부분 request body 페이로드는 작고 메모리에서 모델링 될 수 있으므로 body 스트림을 메모리의 객체에 매핑하기 위해 Play는 BodyParser 를 제공합니다.

Play는 비동기향 프레임워크 이므로 전통적인 InputStream은 요청 본문을 읽는 데 사용할 수 없습니다. 입력 스트림이 블로킹하는 경우 읽기를 호출하면 호출하는 스레드가 데이터를 사용할 수있을 때까지 기다려야합니다. 대신 PlayAkka Streams라는 비동기 스트리밍 라이브러리를 사용하는데요. Akka Streams는 많은 비동기식 스트리밍 API가 원활하게 작동 할 수 있도록 해주는 Reactive Streams의 구현입니다. 따라서 전통적인 InputStream 기반 기술은 Play와 함께 사용하기에 적합하지 않으며 대신  Akka Streams 및 Reactive Streams 주변의 비동기 라이브러리의 생태계는 당신이 필요한 모든 것을 가지고 있습니다.


액션(Actions) 에 대하여

이전에 Action이 Request => Result 함수라고 했었던 적이 있는데요. 이것은 사실이 아닙니다. Action 트레잇을 보다 자세하게 살펴 보죠. 

* 보통 우리는 이렇게 사용했었죠?

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

* trait 은 이렇습니다. (역주: 사실 이것을 정확히 이해하려면  Request[A] => Result 라는 함수리터럴이 클래스라는 것과 내부에 apply 메소드를 가진다는 것을 알아야한다. 즉 스칼라언어에 대한 기초가 전제됨.) 

trait Action[A] extends (Request[A] => Result) {
  def parser: BodyParser[A]
}

제너릭 타입 A가 있으며, 액션에서 BodyParser [A]를 정의해야 한다는 것을 알 수 있습니다.
이어서 Request[A]는 다음과 같이 정의됩니다.

trait Request[+A] extends RequestHeader {
  def body: A
}

A 타입은 리퀘스트 바디의 타입이구요. 그런 리퀘스트 바디 타입은 스칼라 타입으로 (예 : String, NodeSeq, Array [Byte], JsonValue 또는 java.io.File) 사용할 수 있습니다.

요약하면 Action [A]는 BodyParser [A]를 사용하여 HTTP 요청에서 A 타입의 값을 검색&뽑아내고 Action apply 코드로 전달합니다.

기본으로 제공하는 바디 파서들 

일반적인 웹앱들 경우 굳이 커스텀 파서를 만들어서 사용할 필요가 없구요. Play의 내장형 파서로 간단하게 작업 할 수 있습니다. 여기에는 JSON, XML, Forms 용 파서가 포함되며 일반 텍스트 본문을 String으로 처리하고 ByteString으로 바이트 body를 처리합니다.


기본 파디 파서 

명시적으로 바디 파서를 선택하지 않은 경우에 적용되는 기본 본문 파서의 경우,  
들어오는 Content-Type 헤더를 살펴보고 이에 따라 본문을 파싱 합니다.
예를 들어, application / json 유형의 Content-Type은 JsValue로 구문 분석되지만
Application / x-www-form-urlencoded의 Content-Type은 Map [String, Seq [String]]으로 구문 분석됩니다.

기본 바디 파서는 AnyContent 타입의 바디를 만들어내는데요.  AnyContent에서 지원하는 다양한 타입은 asJson과 같은 as 메소드로 액세스 할 수 있습니다. 이것은 body 에서 Option 타입을 반환 하구요.

코드를 보고 이해를 해봅시다.

def save = Action { request =>
  val body: AnyContent = request.body
  val jsonBody: Option[JsValue] = body.asJson

  // Expecting json body
  jsonBody.map { json =>
    Ok("Got: " + (json \ "name").as[String])
  }.getOrElse {
    BadRequest("Expecting application/json request body")
  }
}

다음은 기본 바디 파서가 지원하는 타입들의 매핑입니다.

text/plain: String, accessible via asText.

application/json: JsValue, accessible via asJson.

application/xml, text/xml or application/XXX+xml: scala.xml.NodeSeq, accessible via asXml.

application/x-www-form-urlencoded: Map[String, Seq[String]], accessible via asFormUrlEncoded.

multipart/form-data: MultipartFormData, accessible via asMultipartFormData.

Any other content type: RawBuffer, accessible via asRaw.


성능상의 이유로 기본 body Parser는 HTTP spec에 정의 된 데로 request 메서드가 의미있는 본문으로 정의되지 않았다면 본문을 구문 분석하지 않습니다. 이는 POST, PUT 및 PATCH 요청의 바디만 구문 분석하지만 GET, HEAD 또는 DELETE는 분석하지 않는다는 것을 의미합니다. 이 메소드에 대한 요청 본문을 구문 분석하려면 아래에 설명 된 anyContent 본문 파서를 사용할 수 있습니다.


명시적 바디 파서 선택하기 

바디 파서를 명시적으로 선택하려면 바디 파서를 Action apply 또는 async 메서드에 직접 전달하여 수행 할 수 있습니다. Play는 여러 개의 바디 파서를 기본적으로 제공하는데 이는 BodyParsers.parse 객체를 통해 사용할 수 있습니다. 이 객체는 Controller 트레잇으로 편리하게 가져옵니다.

예를 들어, json 본문을 기대하는 동작을 정의하려면 (앞의 예와 같이) :

def save = Action(parse.json) { request =>
  Ok("Got: " + (request.body \ "name").as[String])
}

바디의 타입이 첨부터 JsValue 이므로 더 이상 Option 타입이 아니기 때문에 바디로 작업하기가 더 쉬워졌습니다. 이것이 옵션이 아닌 이유는 json body 파서가 request 에 있는 Content-Type이 application / json임을 먼저 확인하고 실제 요청이 그것과 다르면 415 Unsupported Media Type 응답을 되돌려 보내기 때문입니다. 따라서 우리의 Action 코드에서 다시 확인할 필요가 없는거죠.

이것은 물론 클라이언트가 요청시 올바른 Content-Type 헤더를 보내고 모든것이 제대로 작동되는 경우이며, 조금 더 편하게하고 싶다면, Content-Type을 무시하고 body를 json으로 구문 분석하려고 시도하는 tolerantJson을 대신 사용할 수 있습니다. 아래와 같은 : 

def save = Action(parse.tolerantJson) { request =>
  Ok("Got: " + (request.body \ "name").as[String])
}

다음은 request 바디를 파일에 저장하는  예제입니다.

def save = Action(parse.file(to = new File("/tmp/upload"))) { request =>
  Ok("Saved the request content to " + request.body)
}


바디 파서를 서로 엮기 (Combining) 

이전 예에서 모든 request 바디는 동일한 파일에 저장되는데 , 사실 이렇게 사용하지는 않지 않을까요? 요청 세션에서 사용자 이름을 추출하여 각 사용자에 대해 고유 한 파일을 제공하는 다른 방식의 사용자 정의 바디 파서를 작성해 보겠겠습니다.

val storeInUserFile = parse.using { request =>
  request.session.get("username").map { user =>
    file(to = new File("/tmp/" + user + ".upload"))
  }.getOrElse {
    sys.error("You don't have the right to upload here")
  }
}

def save = Action(storeInUserFile) { request =>
  Ok("Saved the request content to " + request.body)
}

Note: 여기서는 실제로 우리 자신의 BodyParser 를 작성하는 것이 아니라, 기존의 BodyParser 들을 결합하는 것입니다. BodyParser 를 바닥부터 다루는 방법은 고급 항목 섹션에서 다룹니다.


최대 내용 길이(Max content length)

text, json, xml 또는 formUrlEncoded와 같은 텍스트 기반 바디 파서는 모든 내용을 메모리에 로드 해야하기 때문에 최대 내용 길이를 사용합니다. 기본적으로 구문 분석 할 최대 콘텐츠 길이는 100KB 이며  application.confplay.http.parser.maxMemoryBuffer 속성을 지정하면 재정의 할 수 있습니다.

play.http.parser.maxMemoryBuffer=128K

raw 파서나 multipart / form-data와 같이 디스크의 내용을 버퍼링하는 파서의 경우 최대 내용 길이는 play.http.parser.maxDiskBuffer 속성을 사용하여 지정됩니다. 기본값은 10MB 이며  multipart / form-data 파서는 데이터 필드의 집계에 text max length 속성을 적용합니다.

주어진 동작에 대한 기본 최대 길이를 재정의 할 수도 있습니다.

// Accept only 10KB of data.
def save = Action(parse.text(maxLength = 1024 * 10)) { request =>
  Ok("Got: " + text)
}

maxLength를 사용하여 바디 파서를 래핑 할 수도 있구요.

// Accept only 10KB of data.
def save = Action(parse.maxLength(1024 * 10, storeInUserFile)) { request =>
  Ok("Saved the request content to " + request.body)
}


사용자 정의 바디 파서 작성

BodyParser 트레잇을 구현하여 사용자 정의 파서를 만들 수 있습니다. 이 트레잇은 그저 단순한 함수입니다.

trait BodyParser[+A] extends (RequestHeader => Accumulator[ByteString, Either[Result, A]])

이 함수의 시그니처는 처음에는 조금 어려울 수 있는데요.

이 함수는 RequestHeader를 사용합니다. 이것은 요청에 대한 정보를 확인하는 데 사용할 수 있으며 가장 일반적으로 Content-Type을 가져와서 바디를 올바르게 파싱 할 수 있습니다.

함수의 반환 형식은 Accumulator(누적기) 이며 Akka Streams Sink 주변의 얇은 레이어입니다. 이 누적기는 비동기적으로 요소 스트림을 결과로 누적하며 Akka 스트림 소스를 전달하여 실행할 수 있으며  완료시 사용할 Future 를 반환합니다. 그것은 Sink [E, Future [A]]와 본질적으로 동일하며 타입을 감싸는 래퍼 일뿐입니다.

그러나 Sink 와의 큰 차이점은 Accumulator가 map, mapFuture, recover 등과 같은 편리한 메소드를 제공한다는 것입니다. Sink의 경우는 이 모든 작업을 promise과 같은 결과로 작업 할 경우  mapMaterializedValue 호출로 래핑해야 한다는 점이죠. 

 apply 메소드가 반환하는 누적기는 ByteString 유형의 요소를 사용합니다. 이는 본질적으로 바이트 배열이지만 ByteString은 변경 불가능하고 바이트 및 슬라이스와 같은 많은 작업이 일정 (constant) 시간 내에 발생한다는 점에서 바이트 []와는 다릅니다. 

누적기의 반환 타입은 Either[Result, A] 입니다. Result를 반환하거나 A 타입의 본문을 반환한다는 것이죠. 일반적으로 오류가 발생한 경우 Result가 반환됩니다. 오류는 Content-Type이 바디 파서가 수락하는 타입과 일치하지 않거나 메모리 버퍼가 초과 된 경우 발생합니다. 


body 를 다른 어딘가로 직접 전송하기

바디 파서를 작성하는 일반적인 사용 사례는 실제로 바디를 파싱하고 싶지 않을 때를 위한 것 입니다. 무슨 얘기냐 하면 다른 곳으로 스트리밍 하려고 할 때를 말하는데요. 다음 코드를 보시죠.

import javax.inject._
import play.api.mvc._
import play.api.libs.streams._
import play.api.libs.ws._
import scala.concurrent.ExecutionContext
import akka.util.ByteString

class MyController @Inject() (ws: WSClient)(implicit ec: ExecutionContext) {

  def forward(request: WSRequest): BodyParser[WSResponse] = BodyParser { req =>
    Accumulator.source[ByteString].mapFuture { source =>
      request
        // TODO: stream body when support is implemented
        // .withBody(source)
        .execute()
        .map(Right.apply)
    }
  }

  def myAction = Action(forward(ws.url("https://example.com"))) { req =>
    Ok("Uploaded")
  }
}


Akka Streams 을 사용하여 파싱하기 

드문 경우지만 Akka Streams를 사용하여 사용자 지정 파서를 작성해야 할 수도 있습니다. 대부분의 경우, 대부분의 케이스에는 ByteString에 바디를 버퍼링하는 것으로 충분합니다.이 메서드는 명령형 메서드와 임의 액세스를 바디에 사용할 수 있기 때문에 일반적으로 훨씬 간단한 구문 분석 방법을 제공합니다.

그러나, 가능하지 않은 경우, 예를 들어 파싱해야 하는 본문이 너무 길어서 메모리에 저장되지 않는 경우와 같이 사용자 정의 파서를 작성해야 할 수도 있습니다.

Akka Streams 사용법에 대한 자세한 설명은 이 설명서의 범위를 벗어납니다. 시작하는 가장 좋은 방법은  Akka Streams documentation 설명서를 읽는 것이지만 아래 코드에서 Akka Streams 쿡북의 Parsing lines from a stream of ByteStrings 을 기반으로 하는 CSV 파서를 보여드리죠.

import play.api.mvc._
import play.api.libs.streams._
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import akka.util.ByteString
import akka.stream.scaladsl._

val csv: BodyParser[Seq[Seq[String]]] = BodyParser { req =>

  // A flow that splits the stream into CSV lines
  val sink: Sink[ByteString, Future[Seq[Seq[String]]]] = Flow[ByteString]
    // We split by the new line character, allowing a maximum of 1000 characters per line
    .via(Framing.delimiter(ByteString("\n"), 1000, allowTruncation = true))
    // Turn each line to a String and split it by commas
    .map(_.utf8String.trim.split(",").toSeq)
    // Now we fold it into a list
    .toMat(Sink.fold(Seq.empty[Seq[String]])(_ :+ _))(Keep.right)

  // Convert the body to a Right either
  Accumulator(sink).map(Right.apply)
}




WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

- Scala 2.11 기반 

- Akka 2.4.11 기반 

Play2.4 공식문서 참고



Play2 WS API 



개념 


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

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


.. 정리중 ..


예제 


'PlayFramework2' 카테고리의 다른 글

[Play2] WebSockets  (0) 2016.10.13
[Play2] 외부의 원격액터와 통신  (0) 2016.10.11
[Play2] WS API (번역)  (0) 2016.10.11
[Play2] 마이크로서비스 (microservices)  (0) 2016.10.11
[Play2] Akka 의 결합 - (번역)  (0) 2016.10.10
[Play2] Remote Akka 연결하기 (번역)  (0) 2016.10.10

WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret



Play2 microservices 작성중.




개념 




예제 



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

런타임 의존성 주입  


의존성 주입은 컴포넌트들끼리 서로 독립적이게 하기 위한 방법이다. 서로를 정적으로 포함하기보다는 동적으로 서로에게 주입된다.  플레이는 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



WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

play2 에서 Anorm 으로 PostgreSQL 사용하기 


* 일단 IntelliJ 에 play2 개발 환경이 갖춰져 있다는 전제입니다. 

* play 2.48     ( 최신은 2.5 이나 정보를 찾는 면에서 있어서 어려움이 있다) 
* anorm 2.5
* posgresql driver  9.3-1102-jdbc41

1. application.conf 

- DB 접근 설정을 합니다.


db.default.url="jdbc:postgresql://userip/DatabaseName"

db.default.username= your user
db.default.password= your password
db.default.driver=org.postgresql.Driver

2. build.sbt 에서

- JDBC postgresql 라이브러리에 대한 종속성을 추가해줍니다. 자동으로 다운로드 받습니다.

- postgresql 드라이버 버전이 "9.1-901.jdbc4" 로  안되서 올렸더니 잘 됩니다.

libraryDependencies ++= Seq(
jdbc ,
"com.typesafe.play" %% "anorm" % "2.5.0",
cache ,
ws ,
specs2 % Test,
"org.postgresql" % "postgresql" % "9.3-1102-jdbc41")


3. 코딩 

- 모델 클래스 작성 

case class Thing (
thing_id : Int,
name : String
)


*  Anorm 다루기  (MyBatis 에 가까운 놈임, Slick 나 Squeryl 이 ORM. 개인적으로 ORM 은 별로 같아요..

(참고로 DB.withConnection 은 최근 버전에서는 deprecated 됬습니다만... 옛날버전이 자료가 많아요 OTL )


1) 스트림 API 사용해서 데이터 가져와서 객체에 담기 

def findThingByID_streaming(id : Int): List[Thing] = DB.withConnection {

implicit connection =>
val sql: SqlQuery = SQL("select * from tbl_thing_info where thing_id = {id}")
sql.on("id" -> id)

sql().map ( row =>

Thing(row[Int]("thing_id"), row[String]("name"))

).toList
}

2) paser 를 이용해서  데이터 가져와서 객체에 담기 


val thingParser: RowParser[Thing] = {

int("thing_id") ~ str("name") map {
case thing_id ~ name =>
Thing(thing_id, name)
}
}

import anorm.ResultSetParser

val thingsParser: ResultSetParser[List[Thing]] = {
thingParser *
}



def findThingByID_parser(id : Int): List[Thing] = DB.withConnection {
implicit connection =>
val sql = SQL("select * from tbl_thing_info where thing_id = {id}").on("id" -> id)
sql.as(thingsParser)
}



p.s

ORM 경우는 Slick 이 있는데 개인적으로 ORM 을 좋아하지 않아서 사용안한다. 대략적인 사용법은 아래에 정리함.



참고 :https://www.playframework.com/documentation/2.4.x/ScalaAnorm


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret

* 역주 :  완벽한 전체 내용이 포함되어 있지 않습니다.
             저도 잘 아는게 아니라서 ㅜㅜ  잘 알게되면 추가 / 수정 하겠습니다.


Deploy Playframework (Play!) to AWS Elastic Beanstalk with Jenkins (번역) 

플레이프레임워크는 가볍고 높은 성능을 가진 웹어플리케이션 플랫폼으로써 상태를 갖지 않는 웹 티어에서 완벽하게 작동한다. AWS Elastic Beanstalk 에서도 환상적으로 작동되는데 이 포스트는 플레이프레임워크를 AWS Beanstalk 에서 작동시키기 위해 젠킨스를 이용하는 방법에 대해 말한다.  

플레이프레임워크 버전 2.3+ 과 2.3,2.4,2.5에서 테스팅 되었다.

Step 1: 플레이프레임워크 프로젝트를 준비하라

새로운  "Java SE" container types 타입을 이용하여 Elastic Beanstalk 상에 디플로이 하기 위해 우리는 우리의 플레이 프레임워크에 약간의 변경을 할 것이다. 먼저 dist 라는 이름의 디렉토리를 최상위에 만들어라. .ebextension 과 Procfile를 통해서 ElasticBeanstalk 에게 프로젝트를 런치하는 방법에 대해 알려줄것이다. 

https://github.com/Enalmada/play-beanstalk  (만들어져있는 프로젝트) 


IntelliJ directory structure example for Playframework ElasticBeanstalk

Procfile파일을 만들어서 넣고./bin/ajmoss 의 ajmoss 자리에 build.sbt  에 정의되어있는 네 프로젝트 이름을 넣고-Dconfig.file=conf/live.application.conf를 추가하라 아래처럼 ~

web: ./bin/ajmoss -Dhttp.port=5000 -Dconfig.file=conf/live.application.conf

좋다. 이제 ElasticBeanstalk 에 Deploy 하기위한 준비는 끝났다. Java SE containers 는  nginx  를  reverse proxy 역할로 사용하는데 기본 프록시는 5000번 포트를 사용한다. 만약 커스텀 설정이 필요하면 가능하고 다음을 살펴보라  customize the reverse proxy using these instructions.

Step 2: 젠킨스 만들고 설정하기 

만약 AWS 안에 젠킨스 인스턴스가 없다면 이것을 참고해서 설치하자. install jenkins as outlined in this article.   Oracle JDK 8  젠킨스는 이것도 필요하다. OpenJDK 버전은 지원하지 않는다. 

다음으로 Playframework 프로젝트를 빌드하기위해 아래 처럼   Typesafe Activator 를 인스톨하자:

sudo cd /opt/
sudo wget https://downloads.typesafe.com/typesafe-activator/1.3.7/typesafe-activator-1.3.7-minimal.zip
sudo unzip typesafe-activator-1.3.7-minimal.zip
sudo ln -s activator-1.3.7-minimal activator
sudo touch /etc/profile.d/activator.sh
sudo chmod +x /etc/profile.d/activator.sh
sudo echo 'export PATH=$PATH:/opt/activator/' >> /etc/profile.d/activator.sh
sudo source /etc/profile.d/activator.sh
sudo chown jenkins /opt/activator/activator
sudo chgrp jenkins /opt/activator/activator

이제 activator 는 인스톨되었고  AWS Elastic Beanstalk Deployment Plugin. 를 인스톨 한다.

이제 젠킨스 job 을 만들 준비가 되었다. 당신의 리포지토리에 접속하기위한 소스코드 매니지먼트를 셋업한다. 나는 github +  Git Plugin 을 체크아웃하기위해 사용한다.

다음으로 빌드 스텝을 만드는데 프로젝트 분산을 위해 .zip 파일로  프로젝트를 만든다.  
"Execute Shell" bash 커맨드로 아래와 같이 한다 : 

/opt/activator/activator clean dist
mv target/universal/*.zip target/elasticbeanstalk.zip

이제 우린 AWS Elastic Benastalk 를 위한 추가 빌드 스텝을 추가 할 것이다.  작은 전처리 작업이 필요하다 

Step 3: AWS  권한 을 만들라 

AWS 상에 젠킨스를 접근하고 Elastic Beanstalk 리소스를 제어할 수 있는  key/secret key 페어가 필요하다. S3 버킷을 프로비전해야하고 버킷에 우리의 빌드/리소스를 업로드할 것이다.  가장 간단한 방법은  S3 Bucket 에 직접적으로 권한을 주는것이다 다음 예와 같이 :

{
    "Version": "2008-10-17",
    "Id": "Policy1400141113811",
    "Statement": [
        {
            "Sid": "Stmt1400141111261",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::603736890550:user/jenkins"
            },
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:PutObjectAcl",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::ajmoss-artifacts/*"
        }
    ]
}

노트 : 이 예제는 나의  S3 bucket 이다 이름은 'ajmoss-artifacts'.

Step 4:  AWS Elastic Beanstalk 빌드 스텝을 설정한다.

Jenkins 를 설정하며   "AWS Elastic Beanstalk"  을 위한 빌드 스텝으로 각 섹션을 설정할 것이다.

Example of configuring AWS Elastic Beanstalk build step for jenkins

"AWS Credentials and Region" 에  access key / secret key 를 넣자.

"Application and Environment" 에 EB 리소스의 이름을 넣자. 

"Packaging" 은 root object 를 설정한다 다음과 같이  target/elasticbeanstalk.zip

"Uploading"  업로드 하기위한 S3 bucket 를 설정하며  "S3 Key Prefix"  는 젠킨스를 위한  디렉토리이다. 

"Version and Deployment" 각 빌드의 이름을 설정한다.

나는 
${GIT_COMMIT}-${BUILD_TAG} 를 사용하는데  git commit hash plus the build tag from Jenkins 를 사용한다

Step 5: Kick Ass

이제 Elastic Beanstalk applications 을 손쉽게 할 수 있게 됬다.


WRITTEN BY
[前草] 이승현 (wowlsh93@gmail.com)
스타코프 (데이터지능플랫폼pd) (관심분야: 에너지IoT, 시계열(NILM) 데이터, 폴리글랏 프로그래밍 )

트랙백  0 , 댓글이 없습니다.
secret