Serverless + GraphQL with Kotlin 나윤호 @soldier4443
A presentation at DDD 2nd Backend Session in May 2019 in by Na Yoon Ho
Serverless + GraphQL with Kotlin 나윤호 @soldier4443
About me 애정결핍덕후 백엔드 담당! 안드로이드 개발자 2017.02 - 2018.12 마이다스아이티 2019.04 산타토익 앱 개발 신기술 덕후 Riiid
Architecture
GraphQL Kotlin Lambda API Gateway
GraphQL REST API Kotlin Kotlin(Ktor) Lambda EC2 API Gateway
Why Kotlin 1. 익숙한 언어와 환경 2. 앱과 동일한 언어 3. 정적 타입 언어
Why Serverless 1. 비용 절감 2. 기능에만 집중 3. 개인 프로젝트와 싱크
Why GraphQL 1. API 문서 안 만들어도 될 것 같음 2. 재미
GraphQL
GraphQL 서버에 데이터를 요청하는 Query Language Specification, not Implementation 꾸준히 성장하고 있습니다
REST API GraphQL 각 use case에 대해 매 번 Endpoint를 정의 클라이언트가 필요한 use case를 직접 명시 불필요한 정보까지 받아옴 필요한 정보만 받아옴 서버가 클라이언트에 의존하게 될 가능성 서버는 Schema만 정의. 클라이언트와의 의존도 낮음 Type 시스템을 통해서 쿼리 검증 가능
onClick { demo() }
Nested Fields Basic queries
Query with arguments Query with variables
Type Defintion Expose Queries
Introspection Queries 서버가 지원하는 스키마 정보를 물어볼 수 있는 Query 1. IDE에서 타입 추론 가능 2. 서버 문서나 코드를 보지 않고도 스키마 구조를 알 수 있음
Query Result
Query Result
Auto completion Generating documents
Resources Official Documentation Apollo GraphQL GraphQL Playground
Implementation
Module Hierarchy :server:data DB와 상호작용하는 모듈. :server:graphql GraphQL 구현. data 모듈의 데이터로 Schema를 생성하는 역할 :server:api AWS Lambda와 상호작용하는 모듈
return KGraphQL.schema { !// Configuration for this getSchema configure { useDefaultPrettyPrinter = true } !// List of supported types type<User>() type<InstantTask>() enum<TaskType>() !// Custom scalar types longScalar<LocalDateTime> { serialize = LocalDateTimeConverter.converter deserialize = LocalDateTimeConverter.inverter } !// Available queries query(“user”) { suspendResolver { id: String !-> repository.getUserById(id) } } query(“users”) { suspendResolver { !-> repository.getUsers() } } query(“task”) { suspendResolver { id: String !-> repository.getTaskById(id)
return KGraphQL.schema { !// Configuration for this getSchema configure { useDefaultPrettyPrinter = true } !// List of supported types type<User>() type<InstantTask>() enum<TaskType>() !// Custom scalar types longScalar<LocalDateTime> { serialize = LocalDateTimeConverter.converter deserialize = LocalDateTimeConverter.inverter } !// Available queries query(“user”) { suspendResolver { id: String !-> repository.getUserById(id) } } query(“users”) { suspendResolver { !-> repository.getUsers() } } query(“task”) { suspendResolver { id: String !-> repository.getTaskById(id) data class User( val id: String, val username: String, val tasks: List<Task> = listOf() ) abstract open open open ) class Task( val type: TaskType, val id: String, val name: String enum class TaskType { INSTANT } data class InstantTask( override val type: TaskType = INSTANT, override val id: String, override val name: String, val time: LocalDateTime ) : Task(type, id, name)
return KGraphQL.schema { !// Configuration for this getSchema configure { useDefaultPrettyPrinter = true } !// List of supported types type<User>() type<InstantTask>() enum<TaskType>() !// Custom scalar types longScalar<LocalDateTime> { serialize = LocalDateTimeConverter.converter deserialize = LocalDateTimeConverter.inverter } !// Available queries query(“user”) { suspendResolver { id: String !-> repository.getUserById(id) } } query(“users”) { suspendResolver { !-> repository.getUsers() } } query(“task”) { suspendResolver { id: String !-> repository.getTaskById(id) interface Converter<T, U> { val converter: (T) !-> U val inverter: (U) !-> T }
!// Custom scalar types longScalar<LocalDateTime> { serialize = LocalDateTimeConverter.converter deserialize = LocalDateTimeConverter.inverter return } KGraphQL.schema { !// Configuration for this getSchema configure { queries !// Available useDefaultPrettyPrinter = true query(“user”) { } suspendResolver { id: String !-> repository.getUserById(id) !// List } of supported types type<User>() } type<InstantTask>() enum<TaskType>() query(“users”) { suspendResolver { !-> repository.getUsers() } !// Custom scalar types } longScalar<LocalDateTime> { serialize = LocalDateTimeConverter.converter deserialize query(“task”) { = LocalDateTimeConverter.inverter } suspendResolver { id: String !-> repository.getTaskById(id) !// Available queries } query(“user”) { } suspendResolver { id: String !-> repository.getUserById(id) query(“tasks”) { } suspendResolver { !-> repository.getTasks() } } } } query(“users”) { suspendResolver { !-> repository.getUsers() } } query(“task”) { suspendResolver { id: String !-> repository.getTaskById(id) interface Repository { suspend fun getUsers(): List<User> suspend fun getUserById(id: String): User? suspend fun getTasks(): List<Task> } suspend fun getTaskById(id: String): Task?
class PostRequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { override fun handleRequest( input: APIGatewayProxyRequestEvent?, context: Context? ): APIGatewayProxyResponseEvent { val body = input!?.body!?.fromJson<PostRequestParams>() !?: return error(422, “body is missing in POST request!”) val query = body.query val variables = body.variables.toJson() return body(schema.execute(query, variables)) } }
class PostRequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { override fun handleRequest( input: APIGatewayProxyRequestEvent?, context: Context? ): APIGatewayProxyResponseEvent { val body = input!?.body!?.fromJson<PostRequestParams>() !?: return error(422, “body is missing in POST request!”) val query = body.query val variables = body.variables.toJson() return body(schema.execute(query, variables)) } }
class PostRequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { override fun handleRequest( input: APIGatewayProxyRequestEvent?, context: Context? ): APIGatewayProxyResponseEvent { val body = input!?.body!?.fromJson<PostRequestParams>() !?: return error(422, “body is missing in POST request!”) val query = body.query val variables = body.variables.toJson() return body(schema.execute(query, variables)) } } GraphQLPost: Type: “AWS!::Serverless!::Function” Properties: Handler: “com.lovelessgeek.housemanager.api.handler.PostRequestHandler!::handleRequest” CodeUri: “./build/libs/api-all.jar” Events: IndexApi: Type: “Api” Properties: Path: “/v1/graphql” Method: “post” Runtime: “java8” Timeout: 40 MemorySize: 256
Test / Deployment Run local Deploy https://github.com/importre/aws-sam-gradle-plugin
Thank You