[DDD 2nd] Serverless GraphQL with Kotlin

A presentation at DDD 2nd Backend Session in May 2019 in by Na Yoon Ho

Slide 1

Slide 1

Serverless + GraphQL with Kotlin 나윤호 @soldier4443

Slide 2

Slide 2

About me 애정결핍덕후 백엔드 담당! 안드로이드 개발자 2017.02 - 2018.12 마이다스아이티 2019.04 산타토익 앱 개발 신기술 덕후 Riiid

Slide 3

Slide 3

Architecture

Slide 4

Slide 4

GraphQL Kotlin Lambda API Gateway

Slide 5

Slide 5

GraphQL REST API Kotlin Kotlin(Ktor) Lambda EC2 API Gateway

Slide 6

Slide 6

Why Kotlin 1. 익숙한 언어와 환경 2. 앱과 동일한 언어 3. 정적 타입 언어

Slide 7

Slide 7

Why Serverless 1. 비용 절감 2. 기능에만 집중 3. 개인 프로젝트와 싱크

Slide 8

Slide 8

Why GraphQL 1. API 문서 안 만들어도 될 것 같음 2. 재미

Slide 9

Slide 9

GraphQL

Slide 10

Slide 10

GraphQL 서버에 데이터를 요청하는 Query Language Specification, not Implementation 꾸준히 성장하고 있습니다

Slide 11

Slide 11

REST API GraphQL 각 use case에 대해 매 번 Endpoint를 정의 클라이언트가 필요한 use case를 직접 명시 불필요한 정보까지 받아옴 필요한 정보만 받아옴 서버가 클라이언트에 의존하게 될 가능성 서버는 Schema만 정의. 클라이언트와의 의존도 낮음 Type 시스템을 통해서 쿼리 검증 가능

Slide 12

Slide 12

onClick { demo() }

Slide 13

Slide 13

Nested Fields Basic queries

Slide 14

Slide 14

Query with arguments Query with variables

Slide 15

Slide 15

Type Defintion Expose Queries

Slide 16

Slide 16

Introspection Queries 서버가 지원하는 스키마 정보를 물어볼 수 있는 Query 1. IDE에서 타입 추론 가능 2. 서버 문서나 코드를 보지 않고도 스키마 구조를 알 수 있음

Slide 17

Slide 17

Query Result

Slide 18

Slide 18

Query Result

Slide 19

Slide 19

Auto completion Generating documents

Slide 20

Slide 20

Resources Official Documentation Apollo GraphQL GraphQL Playground

Slide 21

Slide 21

Implementation

Slide 22

Slide 22

Module Hierarchy :server:data DB와 상호작용하는 모듈. :server:graphql GraphQL 구현. data 모듈의 데이터로 Schema를 생성하는 역할 :server:api AWS Lambda와 상호작용하는 모듈

Slide 23

Slide 23

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)

Slide 24

Slide 24

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)

Slide 25

Slide 25

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 }

Slide 26

Slide 26

!// 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?

Slide 27

Slide 27

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)) } }

Slide 28

Slide 28

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)) } }

Slide 29

Slide 29

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

Slide 30

Slide 30

Test / Deployment Run local Deploy https://github.com/importre/aws-sam-gradle-plugin

Slide 31

Slide 31

Thank You