콘텐츠로 이동

2024 12 31

2024-12-31

Play Framework Controller

  • 개요
    • Controller는 추상 클래스, 트레이트로 구성되어 있음
    • Play 2.8 기준 다음과 같음
  • 1. BaseController

    • Play에서 Controller가 가져야 할 기본 동작(Action 생성, request/response 헬퍼 메서드) 모아둔 트레이트
      • Controller의 가장 기본 베이스가 되는 트레이트
    • BaseController 직접 상속(믹스인) 시, 아래 중 하나 충족 필요
      1. implicit val controllerComponents: ControllerComponents 스코프 내에 두기
      2. def controllerComponents: ControllerComponents 메서드 오버라이드
        import play.api.mvc._
        
        class MyController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
            def index = Action {
                Ok("hello base controller")
            }
        }
        
    • 특징
      • Play 제공 액션 메서드 사용 가능 (Action, Action.async)
      • DI를 통해 ControllerComponents 받아올 것
      • BaseController 자체는 기본 골격만 제공, 다양한 편의 메서드는 타 트레이트나 추상 클래스를 통해 제공됨
  • 2. AbstractController

    • BaseController 상속 받고, 필요한 헬퍼들을 믹스인한 추상 클래스.
    • 생성자에서 ControllerComponents 주입 받아 보관
      abstract class AbstractController(protected val controllerComponents: ControllerComponents) extends BaseController
      
    • 특징
      • ControllerComponents 생성자에서 직접 주입받아 내부 필드로 보관. DI 간편
      • Play 제공 헬퍼 메서드(OK, NotFound, Redirect 등) 바로 사용 가능
  • 3. InjectedController
    • BaseController 상속, @Inject()ControllerComponents를 멤버로 자동 주입하는 추상 클래스
    • class MyController extends InjectedController 형식으로 정의한 뒤, 별도 생성자 주입 없이 DI 프레임워크 설정에 따라 controllerComponents를 알아서 주입
      import play.api.mvc._
      import javax.inject.Inject
      
      class MyController @Inject()() extends InjectedController {
        def index = Action {
            Ok("hello InjectedController")
        }
      }
      
  • 4. (구 버전) Controller
    • Play 2.7 이전에 쓰이던 trait. BaseController 및 추가 헬퍼를 섞어둔 형태
    • Play 2.8 기준 deprecated 되었거나, 사용 권장되지 않음.
    • AbstractController, InjectedController 사용이 권장됨
  • 정리
    /**
    
      * Defines utility methods to generate `Action` and `Results` types.
      *
    
      * This is intended to provide the idiomatic Play API for actions, allowing you to use "Action" for the default
      * action builder and "parse" to access Play's default body parsers. You may want to extend this to provide your own
      * base controller class, or write your own version with similar code.
      */
    trait BaseController extends BaseControllerHelpers {
    
      /**
    
       * The default ActionBuilder. Used to construct an action, for example:
       * This is meant to be a replacement for the now-deprecated Action object, and can be used in the same way.
       */
      def Action: ActionBuilder[Request, AnyContent] = controllerComponents.actionBuilder
    }
    
    /**
    
     * An abstract implementation of [[BaseController]] to make it slightly easier to use.
     */
    abstract class AbstractController(protected val controllerComponents: ControllerComponents) extends BaseController
    
    /**
    
     * A variation of [[BaseController]] that gets its components via method injection.
     */
    trait InjectedController extends BaseController {
        private[this] var _components: ControllerComponents = _
    
        /**
    
          * Call this method to set the [[ControllerComponents]] instance.
          */
        @Inject
        def setControllerComponents(components: ControllerComponents): Unit = {
            _components = components
        }
    }
    
  • ControllerComponents

    • 컨트롤러를 구성할 때 필요한 핵심 컴포넌트를 담은 객체
    • 컨트롤러가 HTTP 요청/응답을 처리하는데 필요한 다양한 요소를 한데 묶어, DI 방식으로 쉽게 전달 받을 수 있도록 함.
      trait ControllerComponents {
        def actionBuilder: ActionBuilder[Request, AnyContent] // Action 생성할 때 인증/권한 체크 공통 로직을 ActionBuilder로 분리 - 재활용
        def parsers: PlayBodyParsers  // JSON, XML, Form Data 파싱할 수 있는 parser
        def messagesApi: MessagesApi
        def langs: Langs
        def fileMimeTypes: FileMimeTypes
        def executionContext: scala.concurrent.ExecutionContext  // 비동기 로직 실행 시 필요한 ExecutionContext. Play 논블로킹 I/O. 컨트롤러 내부에서 비동기 처리 시 필요
      }
      
    • 역할
      1. ActionBuilder, PlayBodyParsers 등은 HTTP 요청을 실제로 처리하기 위한 필수 구성 요소
        • Action { implicit request => ... } 형태에 대한 처리에서 내부적으로 ActionBuilder, BodyParser가 모두 필요함
  • Action
    • HTTP 요청 받아서 처리, 그 결과로 HTTP 응답을 반환하는 하나의 함수 (컨트롤러 메서드를 감싸고 있은 요청 처리 로직)
    • 개념
      1. 요청 -> 액션 -> 응답
        • 클라이언트가 보낸 HTTP 요청을 Action이 받아 처리하고, 최종적으로 HTTP 응답 반환
      2. 함수형 인터페이스
        • Play Scala 기준, ActionRequest => Result 형태의 함수
        • 내부에서는 요청 파싱, 비즈니스 로직 수행, 응답 생성 과정을 거침
      3. ActionBuilder
        • Action은 보통 ActionBuilder (ex. DefaultActionBuilder, cc.actionBuilder 등) 통해 생성
        • 인증/인가, 공통 로직 등 ActionBuilder 레벨에서 적용해 중복 코드 줄이고 일관성 있는 처리 가능
    • 특장점
      1. 간결한 선언
        • Action { ... } 요청 처리로직 작성 -> 컨트롤러 코드 간결
      2. 미들웨어(필터) 역할
        • 인증/인가, 로깅, CORS 공통 로직 ActionBuilder 레벨에서 적용. 여러 개의 액션에 일괄적 반영 가능
        • ex. AuthenticatedAction { request => ... } 같은 식으로 인증을 거친 액션만 따로 정의 가능
      3. 비동기 지원
        • 액션 처리 과정에서 비동기 작업 수행 가능
      4. 리퀘스트 파싱 기능
  • Custom Action

    • 인증/인가 등의 로직을 커스텀 액션으로 만들어 여러 컨트롤러 메서드에 손쉽게 적용 가능
    • ActionBuilder를 활용하여 구현하는 방식 사용 1. 커스텀 Request 타입 정의
      • Request를 확장한 WrappedRequest를 만들어, 인증 정보나 사용자 정보 등을 담아 컨트롤러로 전달
        package actions
        
        import play.api.mvc._
        
        case class UserRequest[A](username: String, request: Request[A]) extends WrappedRequest[A](request)
        
    1. 커스텀 ActionBuilder 구현
      • ActionBuilder를 상속받아 원하는 인증/인가 로직을 invokeBlock 메서드 안에 구현
    2. 컨트롤러에서 사용

Slick Versions

play-slick 버전 참고 : https://github.com/playframework/play-slick

- 예전 센터 : slick 2.1.0 | play 2.5.19 | scala 2.11.7 - 신규 센터 : play-slick 5.0.2 | play 2.8.x | slick 3.3.2 + | Scala 2.12.x, 2.13.x

  • Slick 2.x
    • .list, .first -> 동기/블록킹식 사용
    • 내부적으로 Session 사용해 동기적으로 DB 조회하여 결과 반환
    • 스레드 블로킹하기에 동기로 리턴
  • Slick 3.x
    • .resultFuture 기반 -> 비동기/넌블러킹
      • 비동기로 데이터를 받아온다 : 즉시 Future 반환. DB 처리는 백그라운드 진행. 나중에 결과 준비 완료 시 콜백/완료 상태 알림
    • DBIOAction 중심.
      • db.run(...) 호출 시, Future[T] 반환