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
