[AppSync 개념정리] AWS AppSync 및 Resolver Mapping Template 에 대해 개념을 정리해봅시다!

어떤 글인가요?

안녕하세요! 클래스메소드 주식회사의 김태우입니다 :)

저는 Serverless 관련 기술에 관심이 많은데요, 요즘에는 AppSync에 대해 이래저래 살펴보고 있습니다. GraphQL에 대한 경험이나 지식없이 AppSync를 처음 접하시는 분이라면 아마 이해가 되지 않는 부분이 꽤 많을 것으로 예상되는데요 (제가 그랬습니다;-;)

특히나 Resolver 를 작성하는 부분에 있어서 생소할 수 있는 VTL(Velocity Template Language)이라는 템플릿 언어 덕분에 더 헷갈릴지도 모릅니다.

그래서 이번 글에서는 제가 AWS AppSync 소개 및 AppSync Resolver Mapping Template Documentation 을 보고 알게된 것들을 정리해보도록 하겠습니다.

AppSync 란?

AWS AppSync란 관리형 GraphQL 서비스로서, 기본적으로 서버리스의 형태로 동작합니다.

img

AppSync의 전형적인 아키텍쳐는 위의 그림과 같습니다. 클라이언트에게 하나의 GraphQL endpoint를 제공하고, 클라이언트로부터 해당 endpoint로의 request(query, mutation)를 받으면 query(또는 mutation)에 대해 등록된 resolver를 호출합니다. 이 때 resolver는 다양한 데이터 소스로부터 데이터를 가져와서 클라이언트에게 response 합니다.

위 그림에서 데이터 소스는 DynamoDB, ElasticSearch, Lambda 밖에 표시하지 않았지만, 현시점에서는 RDB(RDS) 및 HTTP endpoint 까지 데이터소스로 등록이 가능합니다. 하지만 Lambda 를 데이터 소스로 등록할 수 있어서 그 밖의 데이터 소스도 얼마든지 Lambda 를 통해 접근가능하므로 매우 다양한 데이터 소스 조합이 가능한 구조입니다.

GraphQL 서버를 직접 운영하려고 하면 PrismaApollo 등을 활용하여 구성하게 되는데, 각각의 장단점이 있지만 AppSync의 최대 장점은 관리형이라는 점이지 않을까 생각합니다. 실제로 Prisma 클러스터를 AWS ECS 에 배포해서 구성해본 적이 있는데, 프론트엔드 -> 백엔드 -> 데이터베이스 의 구성이 아닌, 프론트엔드 -> 백엔드 -> Prisma 클러스터 -> 데이터베이스 라는 특이한 구성이 되어버려서 관리해야할 리소스에 대한 부담이 확 늘어나는 것을 느꼈던 적이 있었습니다. 그런 면에서 볼 때, AWS AppSync 는 관리할 필요가 없는 관리형 서비스(managed service)라는 점에서 큰 메리트가 있다고 생각합니다.

이 외에도 정말 다양한 기능들이 있지만 우선은 여기까지 소개하기로 하고, 바로 resolver 에 대한 이야기로 넘어가보겠습니다!

AppSync Resolver

AppSync 는 request 를 받으면 데이터소스에 해당 request 를 연결시키기 위해 (그리고 데이터 소스로부터의 response 를 처리하기 위해) resolver 를 호출합니다. AppSync 에서 지원하고 있는 resolver 는 2가지 종류가 있습니다.

Unit resolvers

말 그대로 한방에 바로 끝내버리는 resolver 입니다. 한방에 바로 데이터소스(DynamoDB, RDS 등)와 연결시켜서 request 와 response 를 처리해주는 resolver 입니다.

Pipeline resolvers

실제로 백엔드 API 를 개발하다보면 Unit resolvers 로 해결되지 않는 복잡한 로직들이 많습니다. 예를 들면, Friendship 테이블에서 두 사람이 친구로 등록된 경우에만 해당 로직을 처리한다던지, 포인트를 사용하여 결제하려는 경우 Point 테이블에서 유저의 포인트가 충분한 경우에만 결제 로직을 처리한다던지 등 여러가지 상황들이 있을 것 같습니다. 이럴 때 사용하면 좋은 것이 바로 Pipeline resolvers 입니다. Pipeline resolver 타입의 경우 AppSync 에서 제공하는 Function 기능을 활용하면 공통된 로직을 처리할 수 있습니다.

Resolver Mapping Template

그럼 resolver 는 어떤 언어로 작성하는 것일까요? 이번 글의 주제인 Velocity Template Engine(VTL) 으로 넘어가보겠습니다.

Apahce Velocity 프로젝트 페이지를 보면 아래와 같이 적혀있습니다.

Velocity is a Java-based template engine. It permits web page designers to reference methods defined in Java code. Web designers can work in parallel with Java programmers to develop web sites according to the Model-View-Controller (MVC) model, meaning that web page designers can focus solely on creating a well-designed site, and programmers can focus solely on writing top-notch code. Velocity separates Java code from the web pages, making the web site more maintainable over the long run and providing a viable alternative to Java Server Pages (JSPs) or PHP.

즉, 자바기반의 템플릿 엔진이며 원래 용도는 jsp 나 php 의 대안으로써 웹페이지 개발시에 이용되는 언어라는 것이네요. (호불호가 갈릴것 같지만) 개인적으로 VTL로 resolver 를 작성하면 다른 범용적인 언어로 작성한 것에 비해 코드가 깔끔하게 느껴지는데, VTL을 GraphQL resolver로 사용할 생각을 했던 AWS AppSync 개발팀이 심히 존경스럽습니다 :)

resolver console

AppSync 의 resolver mapping template 은 위의 그림과 같이 두 부분으로 나뉘어 있습니다. 하나는 Request Mapping Template 으로써 클라이언트로부터 들어오는 request 를 데이터 소스로 넘길 때의 형식(데이터 소스에 따라 상이)을 정의합니다. 또 다른 하나는 Response Mapping Template 으로써 클라이언트에게 데이터 소스의 아웃풋을 전달할 때의 형식(GraphQL Schema 의 해당하는 Type)을 정의합니다. 참고로 위의 그림은 Lambda 를 데이터소스로 사용할 때의 기본 템플릿입니다.

이 두가지 mapping template 에서 로직을 삽입할 수 있게해주는 것이 바로 VTL입니다. VTL을 이용하면 mutation 에서 timestamp 같은 default 값을 삽입해서 데이터소스에 넘겨준다던지, 데이터를 적절히 가공하여 데이터소스에 넘겨준다던지, 유저에 따라 response의 내용을 필터링하거나 바꾼다던지 등의 여러가지 작업을 수행할 수 있습니다.

자세한 내용은 AWS 문서에 매우 따라하기 쉽고 이해하기 쉽게 정리되어 있어서, 이와 관련해서는 이 글에서는 별도로 설명하지 않겠지만 VTL 이 대략 어떤 느낌인지를 전달하기 위해 가장 기본이 되는 #set() 문법에 대해 소개하겠습니다.

#set($variable_name = "value")

#set($name = "taewoo")

VTL 에서는 #set($변수명 = 값) 의 문법을 통해 변수를 지정합니다. 이 때, 지원되는 변수타입은 numbers, strings, arrays, lists, maps 등이 있습니다. map 변수의 경우 아래와 같이 지정합니다.

#set($exampleMap = {
  "id": $context.arguments.id,
  "name": "$context.arguments.name",
  "capitalizedName" : $context.arguments.name.toUpperCase()
} )

$context 는 클라이언트 요청에 관한 데이터가 저장되어 있는 변수로써 주로 $context.arguments.name 의 경우 name 이라는 파라미터 값을 참조하게 됩니다. 세심하게 관찰하시는 분은 $exampleMap에 id 는 그냥 $context.arguments.id 인데 반해 name"$context.arguments.name"인 것을 눈치채셨을텐데요, VTL 에서는 string 값을 참조할 때 쌍따옴표(")를 붙여줍니다.

그럼, 이번에는 $exampleMap 을 직접 Mapping Template 에디터에서 확인해보도록 하겠습니다. 참고로, 이 내용은 AWS 문서를 보면서 따라해본 결과물입니다 :)

우선, 사용할 스키마는 아래와 같습니다. schema-editor

Lambda 타입의 데이터소스를 활용할 것이기 때문에 Lambda 함수도 간단히 만들어둡니다. vtl-example-twkiiim-lambda

방금 만든 Lambda 함수를 데이터 소스로 등록하고, query 의 resolver mapping template 은 아래와 같이 입력합니다. mapping-template

AppSync의 쿼리 에디터에서 아래와 같은 결과를 받아볼 수 있습니다. query-editor

Lambda 가 실행되었는지 CloudWatch Logs 를 통해 확인해보면 아래와 같은 로그가 찍혀있는 것을 볼 수 있습니다. cloudwatch

마무리

지금까지 AppSync 에 대한 간략한 소개 및 resolver mapping template 에 대해 살펴보았습니다. AppSync 에 입문하시는 분들에게 제 글이 조금이나마 도움이 되기를 바랍니다.

끝까지 읽어주셔서 감사합니다! :)