

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 에 대한 VTL 해석기 자습서 AWS AppSync
<a name="tutorials"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

데이터 소스 및 해석기는 AWS AppSync에서 GraphQL 요청을 번역하고 AWS 리소스에서 정보를 가져오는 데 사용됩니다. AWS AppSync는 특정 데이터 소스 유형과의 자동 프로비저닝 및 연결을 지원합니다. AWS AppSync는 또한 AWS Lambda Amazon DynamoDB, 관계형 데이터베이스(Amazon Aurora Serverless), Amazon OpenSearch Service 및 HTTP 엔드포인트를 데이터 소스로 지원합니다. GraphQL API를 기존 AWS 리소스와 함께 사용하거나 처음부터 데이터 소스 및 해석기를 빌드할 수 있습니다. 다음 섹션에서는 일반적인 GraphQL 사용 사례 몇 가지를 튜토리얼 형태로 설명합니다.

AWS AppSync는 해석기에 Apache Velocity 템플릿 언어(VTL)로 작성된 *매핑* 템플릿을 사용합니다. 매핑 템플릿 사용에 대한 자세한 내용은 [해석기 매핑 템플릿 참조](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference)를 참조하세요. VTL 사용에 대한 자세한 내용은 [해석기 매핑 템플릿 프로그래밍 안내서](resolver-mapping-template-reference-programming-guide.md#aws-appsync-resolver-mapping-template-reference-programming-guide)에서 확인할 수 있습니다.

AWS AppSync는 스키마에서 프로비저닝(선택 사항) 및 샘플 스키마 시작에 설명된 대로 GraphQL 스키마에서 DynamoDB 테이블의 자동 프로비저닝을 지원합니다. 또한 스키마를 생성하고 해석기를 연결할 기존 DynamoDB 테이블에서 가져올 수도 있습니다. 이 부분에 대한 자세한 내용은 Amazon DynamoDB에서 가져오기(선택 사항) 단원을 참조하세요.

**Topics**
+ [DynamoDB 해석기를 사용하여 간단한 게시 애플리케이션 생성](tutorial-dynamodb-resolvers.md)
+ [AWS Lambda 해석기 사용](tutorial-lambda-resolvers.md)
+ [OpenSearch Service 해석기 사용](tutorial-elasticsearch-resolvers.md)
+ [로컬 해석기 사용](tutorial-local-resolvers.md)
+ [GraphQL 해석기 결합](tutorial-combining-graphql-resolvers.md)
+ [DynamoDB 배치 작업 사용](tutorial-dynamodb-batch.md)
+ [DynamoDB 트랜잭션 수행](tutorial-dynamodb-transact.md)
+ [HTTP 해석기 사용](tutorial-http-resolvers.md)
+ [Aurora Serverless v2 해석기 사용](tutorial-rds-resolvers.md)
+ [파이프라인 해석기 사용](tutorial-pipeline-resolvers.md)
+ [버전화된 데이터 소스에 Delta Sync 작업 사용](tutorial-delta-sync.md)

# DynamoDB 해석기를 사용하여 간단한 게시 애플리케이션 생성
<a name="tutorial-dynamodb-resolvers"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

이 자습서에서는 자체 Amazon DynamoDB 테이블을 AWS AppSync로 가져와 GraphQL API에 연결하는 방법을 보여줍니다.

사용자를 대신하여 AWS AppSync가 DynamoDB 리소스를 프로비저닝하도록 할 수 있습니다. 또는 선호하는 경우 데이터 원본 및 해석기를 생성하여 기존 테이블을 GraphQL 스키마에 연결할 수 있습니다. 두 경우 모두 GraphQL 문을 통해 DynamoDB 데이터베이스를 읽고 여기에 쓸 수 있고 실시간 데이터를 구독할 수 있습니다.

GraphQL 문을 DynamoDB 작업으로 변환하고 응답을 다시 GraphQL로 변환하기 위해서는 순서대로 완료해야 하는 특정한 구성 단계가 있습니다. 이 자습서에서는 여러 가지 실제 시나리오와 데이터 액세스 패턴을 통해 구성 프로세스를 설명합니다.

## DynamoDB 테이블 설정
<a name="setting-up-your-ddb-tables"></a>

이 자습서를 시작하려면 먼저 아래 단계에 따라 AWS 리소스를 프로비저닝해야 합니다.

1. CLI에서 다음 AWS CloudFormation 템플릿을 사용하여 AWS 리소스를 프로비저닝합니다.

   ```
   aws cloudformation create-stack \
       --stack-name AWSAppSyncTutorialForAmazonDynamoDB \
       --template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml \
       --capabilities CAPABILITY_NAMED_IAM
   ```

   또는 계정의 미국 서부 2(오레곤) 리전에서 다음 CloudFormation 스택을 시작할 수 있습니다 AWS .

   [https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml)

   다음을 생성합니다.
   + `Post` 데이터를 가지고 있는 `AppSyncTutorial-Post`라는 DynamoDB 테이블
   +  AWS AppSync가 `Post` 테이블과 상호 작용할 수 있도록 허용하는 IAM 역할 및 관련 IAM 관리형 정책입니다.

1. 스택 및 생성된 리소스에 대해 자세히 알아보려면 다음 CLI 명령을 실행합니다.

   ```
   aws cloudformation describe-stacks --stack-name AWSAppSyncTutorialForAmazonDynamoDB
   ```

1. 나중에 리소스를 삭제하려면 다음을 실행할 수 있습니다.

   ```
   aws cloudformation delete-stack --stack-name AWSAppSyncTutorialForAmazonDynamoDB
   ```

## GraphQL API 생성
<a name="creating-your-graphql-api"></a>

 AWS AppSync에서 GraphQL API를 생성하려면:

1. 에 로그인 AWS Management Console 하고 [AppSync 콘솔](https://console.aws.amazon.com/appsync/)을 엽니다.

   1. **API 대시보드**에서 **API 생성**을 선택합니다.

1. **API 사용자 지정 또는 Amazon DynamoDB에서 가져오기** 창에서 **처음부터 빌드**를 선택합니다.

   1. 같은 창 오른쪽에 있는 **시작**을 선택합니다.

1. **API 이름** 필드에서 API 이름을 `AWSAppSyncTutorial`로 설정합니다.

1. **생성(Create)**을 선택합니다.

 AWS AppSync 콘솔은 API 키 인증 모드를 사용하여 새 GraphQL API를 생성합니다. 콘솔에서 GraphQL API의 나머지 부분을 설정하고 이 자습서의 나머지 부분에서 이 API에 대한 쿼리를 실행할 수 있습니다.

## 기본 Post API 정의
<a name="defining-a-basic-post-api"></a>

이제 AWS AppSync GraphQL API를 생성했으므로 게시물 데이터의 기본 생성, 검색 및 삭제를 허용하는 기본 스키마를 설정할 수 있습니다.

1. 에 로그인 AWS Management Console 하고 [AppSync 콘솔](https://console.aws.amazon.com/appsync/)을 엽니다.

   1. **API 대시보드**에서 방금 만든 API를 선택합니다.

1. **사이드바**에서 **스키마**를 선택합니다.

   1. **스키마** 창에서 파일 콘텐츠를 다음 코드로 바꿉니다.

     ```
     schema {
         query: Query
         mutation: Mutation
     }
     
     type Query {
         getPost(id: ID): Post
     }
     
     type Mutation {
         addPost(
             id: ID!
             author: String!
             title: String!
             content: String!
             url: String!
         ): Post!
     }
     
     type Post {
         id: ID!
         author: String
         title: String
         content: String
         url: String
         ups: Int!
         downs: Int!
         version: Int!
     }
     ```

1. **저장**을 선택합니다.

이 스키마는 `Post` 객체를 추가하고 가져올 작업과 `Post` 형식을 정의합니다.

## DynamoDB 테이블에 대한 데이터 소스 구성
<a name="configuring-the-data-source-for-the-ddb-tables"></a>

다음으로 스키마에 정의된 쿼리 및 뮤테이션을 `AppSyncTutorial-Post` DynamoDB 테이블에 연결합니다.

먼저 AWS AppSync는 테이블을 인식해야 합니다. 다음과 같이 AWS AppSync에서 데이터 소스를 설정하여 인식하도록 할 수 있습니다.

1. 에 로그인 AWS Management Console 하고 [AppSync 콘솔](https://console.aws.amazon.com/appsync/)을 엽니다.

   1. **API 대시보드**에서 GraphQL API를 선택합니다.

   1. **사이드바**에서 **데이터 소스**를 선택합니다.

1. **데이터 원본 생성**을 선택합니다.

   1. **데이터 소스 이름**에 `PostDynamoDBTable`을 입력합니다.

   1. **데이터 소스 유형**에서 **Amazon DynamoDB 테이블**을 선택합니다.

   1. **리전**에서 **US-WEST-2**를 선택합니다.

   1. **테이블 이름**에서 **AppSyncTutorial-Post** DynamoDB 테이블을 선택합니다.

   1. 새 IAM 역할을 만들거나(권장) `lambda:invokeFunction` IAM 권한이 있는 기존 역할을 선택합니다. 기존 역할에는 [데이터 원본 연결](attaching-a-data-source.md) 섹션에 설명된 대로 신뢰 정책이 필요합니다.

      다음은 리소스에서 작업을 수행하는 데 필요한 권한을 가진 IAM 정책의 예입니다.

------
#### [ JSON ]

****  

      ```
      { 
           "Version":"2012-10-17",		 	 	  
           "Statement": [ 
               { 
                   "Effect": "Allow", 
                   "Action": [ "lambda:invokeFunction" ], 
                   "Resource": [ 
                       "arn:aws:lambda:us-east-1:111122223333:function:myFunction", 
                       "arn:aws:lambda:us-east-1:111122223333:function:myFunction:*" 
                   ] 
               } 
           ] 
       }
      ```

------

1. **생성(Create)**을 선택합니다.

## addPost 해석기 설정(DynamoDB PutItem)
<a name="setting-up-the-addpost-resolver-dynamodb-putitem"></a>

 AWS AppSync가 DynamoDB 테이블을 인식한 후 **해석기를** 정의하여 개별 쿼리 및 변형에 연결할 수 있습니다. 생성한 첫 번째 해석기는 `addPost` 해석기로, 이 해석기를 통해 `AppSyncTutorial-Post` DynamoDB 테이블에서 게시물을 생성할 수 있습니다.

해석기의 구성 요소는 다음과 같습니다.
+ 해석기를 연결할 GraphQL 스키마의 위치. 이 경우 `addPost` 형식의 `Mutation` 필드에서 해석기를 설정합니다. 이 해석기는 호출자가 `mutation { addPost(...){...} }`를 호출하면 호출됩니다.
+ 이 해석기에 사용할 데이터 원본. 이 경우 앞서 정의한 `PostDynamoDBTable` 데이터 원본을 사용하려고 합니다. 따라서 `AppSyncTutorial-Post` DynamoDB 테이블에 항목을 추가할 수 있습니다.
+ 요청 매핑 템플릿. 요청 매핑 템플릿의 목적은 호출자로부터 수신 요청을 받아 AWS AppSync가 DynamoDB에 대해 수행할 수 있는 지침으로 변환하는 것입니다.
+ 응답 매핑 템플릿. 응답 매핑 템플릿의 작업은 DynamoDB의 응답을 받아 GraphQL에 필요한 것으로 변환하는 것입니다. 이러한 템플릿은 DynamoDB의 데이터 모양과 GraphQL의 `Post` 형식이 다른 경우 유용하지만 이 경우 모양이 서로 동일하기 때문에 데이터를 그냥 전달하면 됩니다.

 해석기를 설정하려면:

1. 에 로그인 AWS Management Console 하고 [AppSync 콘솔](https://console.aws.amazon.com/appsync/)을 엽니다.

   1. **API 대시보드**에서 GraphQL API를 선택합니다.

   1. **사이드바**에서 **데이터 소스**를 선택합니다.

1. **데이터 원본 생성**을 선택합니다.

   1. **데이터 소스 이름**에 `PostDynamoDBTable`을 입력합니다.

   1. **데이터 소스 유형**에서 **Amazon DynamoDB 테이블**을 선택합니다.

   1. **리전**에서 **US-WEST-2**를 선택합니다.

   1. **테이블 이름**에서 **AppSyncTutorial-Post** DynamoDB 테이블을 선택합니다.

   1. 새 IAM 역할을 만들거나(권장) `lambda:invokeFunction` IAM 권한이 있는 기존 역할을 선택합니다. 기존 역할에는 [데이터 원본 연결](attaching-a-data-source.md) 섹션에 설명된 대로 신뢰 정책이 필요합니다.

      다음은 리소스에서 작업을 수행하는 데 필요한 권한을 가진 IAM 정책의 예입니다.

------
#### [ JSON ]

****  

      ```
      { 
           "Version":"2012-10-17",		 	 	  
           "Statement": [ 
               { 
                   "Effect": "Allow", 
                   "Action": [ "lambda:invokeFunction" ], 
                   "Resource": [ 
                       "arn:aws:lambda:us-west-2:123456789012:function:myFunction", 
                       "arn:aws:lambda:us-west-2:123456789012:function:myFunction:*" 
                   ] 
               } 
           ] 
       }
      ```

------

1. **생성(Create)**을 선택합니다.

1. **스키마** 탭을 선택합니다.

1. 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 **addPost** 필드를 찾은 다음 **연결**을 선택합니다.

1. **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.

1. **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.

1. 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

   ```
   {
       "version" : "2017-02-28",
       "operation" : "PutItem",
       "key" : {
           "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
       },
       "attributeValues" : {
           "author" : $util.dynamodb.toDynamoDBJson($context.arguments.author),
           "title" : $util.dynamodb.toDynamoDBJson($context.arguments.title),
           "content" : $util.dynamodb.toDynamoDBJson($context.arguments.content),
           "url" : $util.dynamodb.toDynamoDBJson($context.arguments.url),
           "ups" : { "N" : 1 },
           "downs" : { "N" : 0 },
           "version" : { "N" : 1 }
       }
   }
   ```

   **참고:** *형식*은 모든 키와 속성 값에 대해 지정됩니다. 예를 들어, `author` 필드를 `{ "S" : "${context.arguments.author}" }`로 설정합니다. `S` 부분은 to AWS AppSync 및 DynamoDB에 값이 문자열 값임을 나타냅니다. 실제 값은 `author` 인수에서 채워집니다. 마찬가지로 `version` 필드는 형식에 `N`을 사용하기 때문에 숫자 필드입니다. 마지막으로, `ups`, `downs` 및 `version` 필드를 초기화합니다.

   이 자습서에서는 DynamoDB에 삽입된 새 항목을 인덱싱하는 GraphQL `ID!` 유형이의 형식으로도 사용할 수 `$utils.autoId()` 있는 라는 자동 ID 생성을 위한 유틸리티와 함께 클라이언트 arguments. AWS AppSync com의 일부로 제공되도록 지정했습니다`"id" : { "S" : "${$utils.autoId()}" }`. 그런 다음 `id: ID!`의 스키마 정의 외부에 `addPost()`를 둘 수 있는데 자동으로 삽입됩니다. 이 자습서에서는 이러한 기술을 사용하지 않지만 DynamoDB 테이블에 쓰는 경우에는 이 기술의 사용을 고려해야 합니다.

   매핑 템플릿에 대한 자세한 내용은 [해석기 매핑 템플릿 개요](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview) 참조 문서를 참조하십시오. GetItem 요청 매핑에 대한 자세한 내용은 [GetItem](aws-appsync-resolver-mapping-template-reference-dynamodb-getitem.md) 참조 문서를 참조하십시오. 행식에 대한 자세한 내용은 [형식 시스템(요청 매핑)](aws-appsync-resolver-mapping-template-reference-dynamodb-typed-values-request.md) 참조 문서를 참조하십시오.

1. 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

   ```
   $utils.toJson($context.result)
   ```

    **참고:** `AppSyncTutorial-Post` 테이블의 데이터 모양이 GraphQL의 `Post` 형식 모양과 정확하게 일치하기 때문에 응답 매핑 템플릿에서는 결과를 바로 전달하기만 합니다. 또한 파일을 하나만 생성할 수 있도록 이 자습서의 모든 예에서는 동일한 응답 매핑 템플릿을 사용합니다.

1. **저장**을 선택합니다.

### 게시물 추가를 위한 API 직접 호출
<a name="call-the-api-to-add-a-post"></a>

해석기가 설정되었으므로 AWS AppSync는 들어오는 `addPost` 변형을 DynamoDB PutItem 작업으로 변환할 수 있습니다. 이제 변형을 실행해 테이블에 데이터를 입력할 수 있습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다.

  ```
  mutation addPost {
    addPost(
      id: 123
      author: "AUTHORNAME"
      title: "Our first post!"
      content: "This is our first post."
      url: "https://aws.amazon.com/appsync/"
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 새로 생성한 게시물의 결과가 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "addPost": {
        "id": "123",
        "author": "AUTHORNAME",
        "title": "Our first post!",
        "content": "This is our first post.",
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 1
      }
    }
  }
  ```

다음과 같은 작업이 수행되었습니다.
+ AWS AppSync가 `addPost` 변형 요청을 받았습니다.
+ AWS AppSync는 요청과 요청 매핑 템플릿을 가져와 요청 매핑 문서를 생성했습니다. 이 문서는 다음과 같은 형태입니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "PutItem",
      "key" : {
          "id" : { "S" : "123" }
      },
      "attributeValues" : {
          "author": { "S" : "AUTHORNAME" },
          "title": { "S" : "Our first post!" },
          "content": { "S" : "This is our first post." },
          "url": { "S" : "https://aws.amazon.com/appsync/" },
          "ups" : { "N" : 1 },
          "downs" : { "N" : 0 },
          "version" : { "N" : 1 }
      }
  }
  ```
+ AWS AppSync는 요청 매핑 문서를 사용하여 DynamoDB`PutItem` 요청을 생성하고 실행했습니다.
+ AWS AppSync는 `PutItem` 요청 결과를 가져와 GraphQL 유형으로 다시 변환했습니다.

  ```
  {
      "id" : "123",
      "author": "AUTHORNAME",
      "title": "Our first post!",
      "content": "This is our first post.",
      "url": "https://aws.amazon.com/appsync/",
      "ups" : 1,
      "downs" : 0,
      "version" : 1
  }
  ```
+ 응답 매핑 문서를 통해 전달했습니다(변경 없이 전달됨).
+ GraphQL 응답에서 새로 생성된 객체가 반환되었습니다.

## getPost 해석기 설정(DynamoDB GetItem)
<a name="setting-up-the-getpost-resolver-ddb-getitem"></a>

이제 `AppSyncTutorial-Post` DynamoDB 테이블에 데이터를 추가할 수 있으므로 `AppSyncTutorial-Post` 테이블에서 데이터를 검색할 수 있도록 `getPost` 쿼리를 설정해야 합니다. 이렇게 하기 위해 다른 해석기를 설정합니다.
+ **스키마** 탭을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **쿼리** 유형에서 **getPost** 필드를 찾은 다음 **연결**을 선택합니다.
+ **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "GetItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
      }
  }
  ```
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```
+ **저장**을 선택합니다.

### 게시물 가져오기를 위한 API 직접 호출
<a name="call-the-api-to-get-a-post"></a>

이제 해석기가 설정되었으며 AWS AppSync는 수신 `getPost` 쿼리를 DynamoDB`GetItem` 작업으로 변환하는 방법을 알고 있습니다. 이제 앞에서 생성한 게시물을 검색하는 쿼리를 실행할 수 있습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음을 붙여 넣습니다.

  ```
  query getPost {
    getPost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ DynamoDB에서 가져온 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "getPost": {
        "id": "123",
        "author": "AUTHORNAME",
        "title": "Our first post!",
        "content": "This is our first post.",
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 1
      }
    }
  }
  ```

다음과 같은 작업이 수행되었습니다.
+ AWS AppSync가 `getPost` 쿼리 요청을 받았습니다.
+ AWS AppSync는 요청과 요청 매핑 템플릿을 가져와 요청 매핑 문서를 생성했습니다. 이 문서는 다음과 같은 형태입니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "GetItem",
      "key" : {
          "id" : { "S" : "123" }
      }
  }
  ```
+ AWS AppSync는 요청 매핑 문서를 사용하여 DynamoDB GetItem 요청을 생성하고 실행했습니다.
+ AWS AppSync는 `GetItem` 요청 결과를 가져와 GraphQL 유형으로 다시 변환했습니다.

  ```
  {
      "id" : "123",
      "author": "AUTHORNAME",
      "title": "Our first post!",
      "content": "This is our first post.",
      "url": "https://aws.amazon.com/appsync/",
      "ups" : 1,
      "downs" : 0,
      "version" : 1
  }
  ```
+ 응답 매핑 문서를 통해 전달했습니다(변경 없이 전달됨).
+ 응답에서 가져온 객체가 반환되었습니다.

또는 다음 예를 사용합니다.

```
query getPost {
  getPost(id:123) {
    id
    author
    title
  }
}
```

`getPost` 쿼리에 `id`, `author`, `title`만 필요한 경우, 요청 매핑 템플릿을 변경하여 프로젝션 표현식을 사용하여 DynamoDB 테이블에서 원하는 속성만 지정하면 DynamoDB에서 AWS AppSync로 불필요한 데이터 전송을 방지할 수 있습니다. 예를 들어 요청 매핑 템플릿은 아래 코드 조각과 비슷할 수 있습니다.

```
{
    "version" : "2017-02-28",
    "operation" : "GetItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "projection" : {
     "expression" : "#author, id, title",
     "expressionNames" : { "#author" : "author"}
    }
}
```

## updatePost 뮤테이션 생성(DynamoDB UpdateItem)
<a name="create-an-updatepost-mutation-ddb-updateitem"></a>

이제 DynamoDB에서 `Post` 객체를 생성하고 검색할 수 있습니다. 다음에는, 객체 업데이트할 수 있도록 새 변형을 설정합니다. 이렇게 하기 위해 UpdateItem DynamoDB 작업을 사용합니다.
+ **스키마** 탭을 선택합니다.
+ **스키마** 창에서 다음과 같이 `Mutation` 형식을 수정하여 새로운 `updatePost` 변형을 추가할 수 있습니다.

  ```
  type Mutation {
      updatePost(
          id: ID!,
          author: String!,
          title: String!,
          content: String!,
          url: String!
      ): Post
      addPost(
          author: String!
          title: String!
          content: String!
          url: String!
      ): Post!
  }
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 새로 생성한 **updatePost** 필드를 찾은 다음 **연결**을 선택합니다.
+ **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "SET author = :author, title = :title, content = :content, #url = :url ADD version :one",
          "expressionNames": {
              "#url" : "url"
          },
          "expressionValues": {
              ":author" : $util.dynamodb.toDynamoDBJson($context.arguments.author),
              ":title" : $util.dynamodb.toDynamoDBJson($context.arguments.title),
              ":content" : $util.dynamodb.toDynamoDBJson($context.arguments.content),
              ":url" : $util.dynamodb.toDynamoDBJson($context.arguments.url),
              ":one" : { "N": 1 }
          }
      }
  }
  ```

   **참고:** 이 해석기는 PutItem 작업과 크게 다른 DynamoDB UpdateItem 작업을 사용합니다. 전체 항목을 쓰는 대신 특정 속성을 업데이트하도록 DynamoDB에 요청합니다. DynamoDB 업데이트 표현식을 사용하여 이 작업을 완료합니다. 표현식 자체는 `expression` 섹션의 `update` 필드에서 지정합니다. 이 표현식은 `author`, `title`, `content` 및 url 속성을 설정한 다음 `version` 필드를 증분하도록 합니다. 사용되는 값은 표현식 자체에 나타나지 않습니다. 대신 표현식에는 이름이 콜론으로 시작되는 자리 표시자가 있습니다. 표현식은 `expressionValues` 필드에서 정의됩니다. 마지막으로, DynamoDB에는 `expression`에 표시할 수 없는 예약어가 있습니다. 예를 들어, `url`은 예약어기 때문에 `url` 필드를 업데이트하려면 이름 자리 표시자를 사용해 `expressionNames` 필드에 해당 자리 표시자를 정의합니다.

  `UpdateItem` 요청 매핑에 대한 자세한 내용은 [UpdateItem](aws-appsync-resolver-mapping-template-reference-dynamodb-updateitem.md) 참조 문서를 참조하십시오. 업데이트 표현식을 작성하는 방법에 대한 자세한 내용은 [DynamoDB UpdateExpressions 문서](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)를 참조하십시오.
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```

### 게시물 업데이트를 위한 API 직접 호출
<a name="call-the-api-to-update-a-post"></a>

이제 해석기가 설정되었으며 AWS AppSync는 들어오는 `update` 변형을 DynamoDB`Update` 작업으로 변환하는 방법을 알고 있습니다. 이제 앞서 작성한 항목을 업데이트하는 변형을 실행할 수 있습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

  ```
  mutation updatePost {
    updatePost(
      id:"123"
      author: "A new author"
      title: "An updated author!"
      content: "Now with updated content!"
      url: "https://aws.amazon.com/appsync/"
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ DynamoDB에서 업데이트된 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "updatePost": {
        "id": "123",
        "author": "A new author",
        "title": "An updated author!",
        "content": "Now with updated content!",
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 2
      }
    }
  }
  ```

이 예제에서는 요청 매핑 템플릿이 AWS AppSync 및 DynamoDB에 해당 `downs` 필드로 어떤 작업도 수행하도록 요청하지 않았기 때문에 `ups` 및 필드가 수정되지 않았습니다. 또한 AWS AppSync및 DynamoDB에 `version` 필드에 1을 추가하도록 요청했기 때문에 `version` 필드가 1씩 증가했습니다.

## updatePost 해석기 수정(DynamoDB UpdateItem)
<a name="modifying-the-updatepost-resolver-dynamodb-updateitem"></a>

이 작업은 `updatePost` 변형을 시작하기 좋은 지점이지만 다음과 같이 두 가지 주요한 문제가 있습니다.
+ 필드를 하나만 업데이트하고자 하는 경우에도 필드를 모두 업데이트해야 합니다.
+ 두 사람이 객체를 수정하는 경우 정보가 손실될 수 있습니다.

이러한 문제를 해결하기 위해 요청에 지정된 인수만 수정한 다음 `UpdateItem` 작업에 조건을 추가하도록 `updatePost` 변형을 수정하려고 합니다.

1. **스키마** 탭을 선택합니다.

1. **스키마** 패널에서 `Mutation` 유형의 `updatePost` 필드를 수정하여 `author`, `title`, `content` 및 `url` 인수에서 느낌표를 제거하고 `id` 필드는 그대로 둡니다. 이렇게 하면 해당 인수가 선택적 인수가 됩니다. 또한 새로운 필수 인수인 `expectedVersion` 버전을 추가합니다.

   ```
   type Mutation {
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       addPost(
           author: String!
           title: String!
           content: String!
           url: String!
       ): Post!
   }
   ```

1. **저장**을 선택합니다.

1. 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 **updatePost** 필드를 찾습니다.

1. **PostDynamoDBTable**을 선택하여 기존 해석기를 엽니다.

1. **Configure the request mapping template(요청 매핑 템플릿 구성)**에서 다음과 같이 요청 매핑 템플릿을 수정합니다.

   ```
   {
       "version" : "2017-02-28",
       "operation" : "UpdateItem",
       "key" : {
           "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
       },
   
       ## Set up some space to keep track of things you're updating **
       #set( $expNames  = {} )
       #set( $expValues = {} )
       #set( $expSet = {} )
       #set( $expAdd = {} )
       #set( $expRemove = [] )
   
       ## Increment "version" by 1 **
       $!{expAdd.put("version", ":one")}
       $!{expValues.put(":one", { "N" : 1 })}
   
       ## Iterate through each argument, skipping "id" and "expectedVersion" **
       #foreach( $entry in $context.arguments.entrySet() )
           #if( $entry.key != "id" && $entry.key != "expectedVersion" )
               #if( (!$entry.value) && ("$!{entry.value}" == "") )
                   ## If the argument is set to "null", then remove that attribute from the item in DynamoDB **
   
                   #set( $discard = ${expRemove.add("#${entry.key}")} )
                   $!{expNames.put("#${entry.key}", "$entry.key")}
               #else
                   ## Otherwise set (or update) the attribute on the item in DynamoDB **
   
                   $!{expSet.put("#${entry.key}", ":${entry.key}")}
                   $!{expNames.put("#${entry.key}", "$entry.key")}
                   $!{expValues.put(":${entry.key}", { "S" : "${entry.value}" })}
               #end
           #end
       #end
   
       ## Start building the update expression, starting with attributes you're going to SET **
       #set( $expression = "" )
       #if( !${expSet.isEmpty()} )
           #set( $expression = "SET" )
           #foreach( $entry in $expSet.entrySet() )
               #set( $expression = "${expression} ${entry.key} = ${entry.value}" )
               #if ( $foreach.hasNext )
                   #set( $expression = "${expression}," )
               #end
           #end
       #end
   
       ## Continue building the update expression, adding attributes you're going to ADD **
       #if( !${expAdd.isEmpty()} )
           #set( $expression = "${expression} ADD" )
           #foreach( $entry in $expAdd.entrySet() )
               #set( $expression = "${expression} ${entry.key} ${entry.value}" )
               #if ( $foreach.hasNext )
                   #set( $expression = "${expression}," )
               #end
           #end
       #end
   
       ## Continue building the update expression, adding attributes you're going to REMOVE **
       #if( !${expRemove.isEmpty()} )
           #set( $expression = "${expression} REMOVE" )
   
           #foreach( $entry in $expRemove )
               #set( $expression = "${expression} ${entry}" )
               #if ( $foreach.hasNext )
                   #set( $expression = "${expression}," )
               #end
           #end
       #end
   
       ## Finally, write the update expression into the document, along with any expressionNames and expressionValues **
       "update" : {
           "expression" : "${expression}"
           #if( !${expNames.isEmpty()} )
               ,"expressionNames" : $utils.toJson($expNames)
           #end
           #if( !${expValues.isEmpty()} )
               ,"expressionValues" : $utils.toJson($expValues)
           #end
       },
   
       "condition" : {
           "expression"       : "version = :expectedVersion",
           "expressionValues" : {
               ":expectedVersion" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion)
           }
       }
   }
   ```

1. **저장**을 선택합니다.

이 템플릿은 보다 복잡한 예 중 하나입니다. 이 템플릿은 보다 복잡한 예 중 하나로, 매핑 템플릿의 이점과 유연성을 보여줍니다. 이 템플릿은 모든 인수를 루핑하면서 `id`와 `expectedVersion`을 건너뜁니다. 인수가 무언가로 설정된 경우 AWS AppSync 및 DynamoDB에 DynamoDB의 객체에서 해당 속성을 업데이트하도록 요청합니다. 속성이 null로 설정된 경우 사후 객체에서 해당 속성을 제거하도록 AWS AppSync 및 DynamoDB에 요청합니다. 인수가 지정되어 있지 않은 경우에는 속성을 그냥 둡니다. 이 템플릿 역시 `version` 필드를 증가시킵니다.

또한 새로운 `condition` 섹션이 있습니다. 조건 표현식을 사용하면 작업이 수행되기 전에 이미 DynamoDB에 있는 객체의 상태를 기반으로 요청이 성공해야 하는지 여부를 AWS AppSync와 DynamoDB에 알릴 수 있습니다. 이 경우에는 DynamoDB에 현재 있는 항목의 `version` 필드가 `expectedVersion` 인수와 정확하게 일치하는 경우에만 `UpdateItem` 요청에 성공하면 됩니다.

조건 표현식에 대한 자세한 내용은 [조건 표현식](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md) 참조 문서를 참조하십시오.

### 게시물 업데이트를 위한 API 직접 호출
<a name="id1"></a>

새 해석기를 사용해 `Post` 객체를 업데이트해 보십시오.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

  ```
  mutation updatePost {
    updatePost(
      id:123
      title: "An empty story"
      content: null
      expectedVersion: 2
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ DynamoDB에서 업데이트된 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "updatePost": {
        "id": "123",
        "author": "A new author",
        "title": "An empty story",
        "content": null,
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 3
      }
    }
  }
  ```

이 요청에서는 AWS AppSync와 DynamoDB에 `title` 및 `content` 필드만 업데이트하도록 요청했습니다. `version` 필드를 증분하는 것을 제외하고 다른 모든 필드는 그대로 둡니다. `title` 속성을 새 값으로 설정하고 게시물에서 `content` 속성을 제거했습니다. `author`, `url`, `ups` 및 `downs` 필드는 그대로 남아 있습니다.

변형 요청을 다시 실행해도 요청은 정확하게 그대로 유지됩니다. 다음과 유사한 응답이 나타납니다.

```
{
  "data": {
    "updatePost": null
  },
  "errors": [
    {
      "path": [
        "updatePost"
      ],
      "data": {
        "id": "123",
        "author": "A new author",
        "title": "An empty story",
        "content": null,
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 3
      },
      "errorType": "DynamoDB:ConditionalCheckFailedException",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)"
    }
  ]
}
```

조건 표현식이 false로 평가되었으므로 요청에 실패합니다.
+ 요청을 처음 실행했을 때 DynamoDB에 있는 게시물의 `version` 필드 값은 `2`였고, 이는 `expectedVersion` 인수와 일치했습니다. 요청에 성공하자 DynamoDB의 `version` 필드가 `3`으로 증가했습니다.
+ 요청을 두 번째로 실행했을 때 DynamoDB에 있는 게시물의 `version` 필드 값은 `3`이었고, 이는 `expectedVersion` 인수와 일치하지 않았습니다.

이러한 패턴을 일반적으로 *낙관적 잠금*이라고 합니다.

 AWS AppSync DynamoDB 해석기의 기능은 DynamoDB에서 포스트 객체의 현재 값을 반환한다는 것입니다. 현재 값은 GraphQL 응답의 `data` 섹션에 있는 `errors` 필드에서 찾을 수 있습니다. 애플리케이션에서는 이러한 정보를 사용하여 처리 방식을 결정합니다. 이 경우, DynamoDB에 있는 객체의 `version` 필드가 `3`으로 설정되어 있은데, 방금 `expectedVersion` 인수를 `3`으로 업데이트했으므로 결과는 다시 성공입니다.

조건 검사 실패 처리에 대한 자세한 내용은 [조건 표현식](aws-appsync-resolver-mapping-template-reference-dynamodb-condition-expressions.md) 매핑 템플릿 참조 문서를 참조하십시오.

## upvotePost 및 downvotePost 뮤테이션 생성(DynamoDB UpdateItem)
<a name="create-upvotepost-and-downvotepost-mutations-ddb-updateitem"></a>

`Post` 형식에는 `ups` 및 `downs` 필드가 있어 추천 수 및 비추천 수를 기록할 수 있습니다. 하지만 지금까지 API를 사용하면 이러한 작업을 수행할 수 없었습니다. 게시물을 추천 및 비추천하도록 하는 몇 가지 변형을 추가해 보겠습니다.
+ **스키마** 탭을 선택합니다.
+ **스키마** 창에서 다음과 같이 `Mutation` 유형을 수정하여 새로운 `upvotePost` 및 `downvotePost` 뮤테이션을 추가할 수 있습니다.

  ```
  type Mutation {
      upvotePost(id: ID!): Post
      downvotePost(id: ID!): Post
      updatePost(
          id: ID!,
          author: String,
          title: String,
          content: String,
          url: String,
          expectedVersion: Int!
      ): Post
      addPost(
          author: String!,
          title: String!,
          content: String!,
          url: String!
      ): Post!
  }
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 새로 생성한 **upvotePost** 필드를 찾은 다음 **연결**을 선택합니다.
+ **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "ADD ups :plusOne, version :plusOne",
          "expressionValues" : {
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 새로 생성한 `downvotePost` 필드를 찾은 다음 **연결**을 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "ADD downs :plusOne, version :plusOne",
          "expressionValues" : {
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```
+ **저장**을 선택합니다.

### 게시물을 추천 및 비추천하는 API 직접 호출
<a name="call-the-api-to-upvote-and-downvote-a-post"></a>

이제 새 해석기가 설정되었으며 AWS AppSync는 수신 `upvotePost` 또는 `downvote` 변형을 DynamoDB UpdateItem 작업으로 변환하는 방법을 알고 있습니다. 이제 앞서 생성한 게시물을 추천 또는 비추천하는 변형을 실행할 수 있습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

  ```
  mutation votePost {
    upvotePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 게시물이 DynamoDB에서 업데이트되고 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "upvotePost": {
        "id": "123",
        "author": "A new author",
        "title": "An empty story",
        "content": null,
        "url": "https://aws.amazon.com/appsync/",
        "ups": 6,
        "downs": 0,
        "version": 4
      }
    }
  }
  ```
+ **Execute query(쿼리 실행)**를 몇 번 더 선택합니다. 쿼리를 실행할 때마다 `ups` 및 `version` 필드가 1씩 증가하는 것이 보여야 합니다.
+ 다음과 같이 `downvotePost` 변형을 호출하도록 쿼리를 변경합니다.

  ```
  mutation votePost {
    downvotePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다. 쿼리를 실행할 때마다 `downs` 및 `version` 필드가 1씩 증가하는 것이 보여야 합니다.

  ```
  {
    "data": {
      "downvotePost": {
        "id": "123",
        "author": "A new author",
        "title": "An empty story",
        "content": null,
        "url": "https://aws.amazon.com/appsync/",
        "ups": 6,
        "downs": 4,
        "version": 12
      }
    }
  }
  ```

## deletePost 해석기 설정(DynamoDB DeleteItem)
<a name="setting-up-the-deletepost-resolver-ddb-deletepost"></a>

설정하려는 다음 변형은 게시물을 삭제하는 변형입니다. 이 작업은 `DeleteItem` DynamoDB 작업을 사용하여 수행합니다.
+ **스키마** 탭을 선택합니다.
+ **스키마** 창에서 다음과 같이 `Mutation` 형식을 수정하여 새로운 `deletePost` 변형을 추가할 수 있습니다.

  ```
  type Mutation {
      deletePost(id: ID!, expectedVersion: Int): Post
      upvotePost(id: ID!): Post
      downvotePost(id: ID!): Post
      updatePost(
          id: ID!,
          author: String,
          title: String,
          content: String,
          url: String,
          expectedVersion: Int!
      ): Post
      addPost(
          author: String!,
          title: String!,
          content: String!,
          url: String!
      ): Post!
  }
  ```

  이번에는`expectedVersion` 필드를 선택 사항으로 설정했는데, 이에 관한 사항은 나중에 요청 매핑 템플릿을 추가할 때 설명하겠습니다.
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 새로 생성한 **delete** 필드를 찾은 다음 **연결**을 선택합니다.
+ **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "DeleteItem",
      "key": {
          "id": $util.dynamodb.toDynamoDBJson($context.arguments.id)
      }
      #if( $context.arguments.containsKey("expectedVersion") )
          ,"condition" : {
              "expression"       : "attribute_not_exists(id) OR version = :expectedVersion",
              "expressionValues" : {
                  ":expectedVersion" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion)
              }
          }
      #end
  }
  ```

   **참고:** `expectedVersion` 인수는 선택적 인수입니다. 호출자가 요청에서 `expectedVersion` 인수를 설정하면, 템플릿에서는 항목이 이미 삭제된 경우 또는 DynamoDB에 있는 `version` 속성이 `expectedVersion`과 정확하게 일치하는 경우, `DeleteItem` 요청이 완료되게 하는 조건을 추가합니다. 그냥 두면 `DeleteItem` 요청에 아무런 조건 표현식도 지정되지 않습니다. 이는 `version`의 값이나 해당 항목이 DynamoDB에 존재하는지 여부에 관계없이 완료됩니다.
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```

   **참고:** 항목을 삭제하고 있더라도 항목이 아직 삭제되지 않은 경우 삭제된 항목을 반환할 수 있습니다.
+ **저장**을 선택합니다.

`DeleteItem` 요청 매핑에 대한 자세한 내용은 [DeleteItem](aws-appsync-resolver-mapping-template-reference-dynamodb-deleteitem.md) 참조 문서를 참조하십시오.

### 게시물 삭제를 위한 API 직접 호출
<a name="call-the-api-to-delete-a-post"></a>

이제 해석기가 설정되었으며 AWS AppSync는 들어오는 `delete` 변형을 DynamoDB`DeleteItem` 작업으로 변환하는 방법을 알고 있습니다. 이제 변형을 실행해 테이블에 데이터를 삭제할 수 있습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

  ```
  mutation deletePost {
    deletePost(id:123) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ DynamoDB에서 게시물이 삭제되었습니다. 참고로 AWS AppSync는 DynamoDB에서 삭제된 항목의 값을 반환합니다.이 값은 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "deletePost": {
        "id": "123",
        "author": "A new author",
        "title": "An empty story",
        "content": null,
        "url": "https://aws.amazon.com/appsync/",
        "ups": 6,
        "downs": 4,
        "version": 12
      }
    }
  }
  ```

`deletePost`에 대한 호출이 DynamoDB에서 실제로 삭제된 항목인 경우에만 값이 반환됩니다.
+ **Execute query(쿼리 실행)**을 다시 선택합니다.
+ 호출에는 계속 성공하지만 반환되는 값은 없습니다.

  ```
  {
    "data": {
      "deletePost": null
    }
  }
  ```

이제 게시물을 삭제해 보는데, 이번에는 `expectedValue`를 지정해 보겠습니다. 지금까지 작업한 게시물 하나를 방금 삭제했기 때문에 먼저 새 게시물을 생성해야 합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다.

  ```
  mutation addPost {
    addPost(
      id:123
      author: "AUTHORNAME"
      title: "Our second post!"
      content: "A new post."
      url: "https://aws.amazon.com/appsync/"
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 새로 생성한 게시물의 결과가 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 곧 필요할 것이기 때문에 새로 생성한 객체의 `id`를 적어 둡니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "addPost": {
        "id": "123",
        "author": "AUTHORNAME",
        "title": "Our second post!",
        "content": "A new post.",
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 1
      }
    }
  }
  ```

이제 게시물을 삭제해 보는데 `expectedVersion`에 대해 잘못된 값을 입력할 것입니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

  ```
  mutation deletePost {
    deletePost(
      id:123
      expectedVersion: 9999
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.

  ```
  {
    "data": {
      "deletePost": null
    },
    "errors": [
      {
        "path": [
          "deletePost"
        ],
        "data": {
          "id": "123",
          "author": "AUTHORNAME",
          "title": "Our second post!",
          "content": "A new post.",
          "url": "https://aws.amazon.com/appsync/",
          "ups": 1,
          "downs": 0,
          "version": 1
        },
        "errorType": "DynamoDB:ConditionalCheckFailedException",
        "locations": [
          {
            "line": 2,
            "column": 3
          }
        ],
        "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)"
      }
    ]
  }
  ```

  DynamoDB에 있는 게시물의 `version` 값이 인수에 지정된 `expectedValue`와 일치하지 않아 조건 표현식이 false로 평가되었기 때문에 요청에 실패했습니다. 객체의 현재 값은 GraphQL 응답에서 `data` 섹션의 `errors` 필드에 반환됩니다.
+ 이 요청을 다시 실행하되 `expectedVersion`을 수정합니다.

  ```
  mutation deletePost {
    deletePost(
      id:123
      expectedVersion: 1
    ) {
      id
      author
      title
      content
      url
      ups
      downs
      version
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 이번에는 요청에 성공하고 DynamoDB에서 삭제된 값이 반환됩니다.

  ```
  {
    "data": {
      "deletePost": {
        "id": "123",
        "author": "AUTHORNAME",
        "title": "Our second post!",
        "content": "A new post.",
        "url": "https://aws.amazon.com/appsync/",
        "ups": 1,
        "downs": 0,
        "version": 1
      }
    }
  }
  ```
+ **Execute query(쿼리 실행)**을 다시 선택합니다.
+ 호출에 계속해서 성공하지만 이번에는 게시물이 이미 DynamoDB에서 삭제되었으므로 반환되는 값이 없습니다.

```
{
  "data": {
    "deletePost": null
  }
}
```

## allPost 해석기 설정(DynamoDB Scan)
<a name="setting-up-the-allpost-resolver-dynamodb-scan"></a>

지금까지 API는 살펴보고자 하는 각 게시물의 `id`를 아는 경우에만 유용했습니다. 테이블의 게시물을 모두 반환하는 새 해석기를 추가해 보겠습니다.
+ **스키마** 탭을 선택합니다.
+ **스키마** 창에서 다음과 같이 `Query` 형식을 수정하여 새로운 `allPost` 쿼리를 추가할 수 있습니다.

  ```
  type Query {
      allPost(count: Int, nextToken: String): PaginatedPosts!
      getPost(id: ID): Post
  }
  ```
+ 새로운 `PaginationPosts` 형식을 다음과 같이 추가합니다.

  ```
  type PaginatedPosts {
      posts: [Post!]!
      nextToken: String
  }
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **쿼리** 유형에서 새로 생성한 **allPost** 필드를 찾은 다음 **연결**을 선택합니다.
+ **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "Scan"
      #if( ${context.arguments.count} )
          ,"limit": $util.toJson($context.arguments.count)
      #end
      #if( ${context.arguments.nextToken} )
          ,"nextToken": $util.toJson($context.arguments.nextToken)
      #end
  }
  ```

  이 해석기에는 두 가지 선택적 인수가 있는데, `count`는 단일 호출 시 반환되는 최대 항목 수를 지정하고, `nextToken`은 다음 결과 집합을 가져오는 데 사용할 수 있습니다(`nextToken`의 값을 가져오는 위치는 나중에 설명할 예정임).
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```

   **참고: ** 이 응답 매핑 템플릿은 지금까지 사용한 다른 모든 템플릿과 다릅니다. `allPost` 쿼리의 결과는 게시물 목록과 페이지 매김 토큰을 포함하고 있는 `PaginatedPosts`입니다. 이 객체의 모양은 AWS AppSync DynamoDB Resolver에서 반환되는 것과 다릅니다. 게시물 목록은 AWS AppSync DynamoDB Resolver 결과`items`에서 호출되지만 `posts`에서는 호출됩니다`PaginatedPosts`.
+ **저장**을 선택합니다.

`Scan` 요청 매핑에 대한 자세한 내용은 [스캔](aws-appsync-resolver-mapping-template-reference-dynamodb-scan.md) 참조 문서를 참조하십시오.

### 모든 게시물을 스캔하는 API 직접 호출
<a name="call-the-api-to-scan-all-posts"></a>

이제 해석기가 설정되었으며 AWS AppSync는 수신 `allPost` 쿼리를 DynamoDB`Scan` 작업으로 변환하는 방법을 알고 있습니다. 이제 테이블을 스캔해 게시물을 모두 검색할 수 있습니다.

지금까지 작업했던 데이터를 모두 삭제했기 때문에 이 요청을 수행하기 전에 몇 가지 데이터로 테이블을 채워야 합니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다.

  ```
  mutation addPost {
    post1: addPost(id:1 author: "AUTHORNAME" title: "A series of posts, Volume 1" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post2: addPost(id:2 author: "AUTHORNAME" title: "A series of posts, Volume 2" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post3: addPost(id:3 author: "AUTHORNAME" title: "A series of posts, Volume 3" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post4: addPost(id:4 author: "AUTHORNAME" title: "A series of posts, Volume 4" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post5: addPost(id:5 author: "AUTHORNAME" title: "A series of posts, Volume 5" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post6: addPost(id:6 author: "AUTHORNAME" title: "A series of posts, Volume 6" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post7: addPost(id:7 author: "AUTHORNAME" title: "A series of posts, Volume 7" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post8: addPost(id:8 author: "AUTHORNAME" title: "A series of posts, Volume 8" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
    post9: addPost(id:9 author: "AUTHORNAME" title: "A series of posts, Volume 9" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.

이제 테이블을 스캔해 보겠습니다. 한 번에 5개 결과를 반환합니다.
+ **쿼리** 창에 다음 쿼리를 붙여 넣습니다.

  ```
  query allPost {
    allPost(count: 5) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 처음 5개 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "allPost": {
        "posts": [
          {
            "id": "5",
            "title": "A series of posts, Volume 5"
          },
          {
            "id": "1",
            "title": "A series of posts, Volume 1"
          },
          {
            "id": "6",
            "title": "A series of posts, Volume 6"
          },
          {
            "id": "9",
            "title": "A series of posts, Volume 9"
          },
          {
            "id": "7",
            "title": "A series of posts, Volume 7"
          }
        ],
        "nextToken": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRkJEdXdUK09hcnovRGhNTGxLTGdMUEFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF6ajFodkhKU1paT1pncTRaUUNBUkNBZ2dHWnJiR1dQWGxkMDB1N0xEdGY4Z2JsbktzRjRua1VCcks3TFJLcjZBTFRMeGFwVGJZMDRqOTdKVFQyYVRwSzdzbVdtNlhWWFVCTnFIOThZTzBWZHVkdDI2RlkxMHRqMDJ2QTlyNWJTUWpTbWh6NE5UclhUMG9KZWJSQ2JJbXBlaDRSVlg0Tis0WTVCN1IwNmJQWWQzOVhsbTlUTjBkZkFYMVErVCthaXZoNE5jMk50RitxVmU3SlJ5WmpzMEFkSGduM3FWd2VrOW5oeFVVd3JlK1loUks5QkRzemdiMDlmZmFPVXpzaFZ4cVJRbC93RURlOTcrRmVJdXZNby9NZ1F6dUdNbFRyalpNR3FuYzZBRnhwa0VlZTFtR0FwVDFISElUZlluakptYklmMGUzUmcxbVlnVHVSbDh4S0trNmR0QVoraEhLVDhuNUI3VnF4bHRtSnlNUXBrZGl6KzkyL3VzNDl4OWhrMnVxSW01ZFFwMjRLNnF0dm9ZK1BpdERuQTc5djhzb0grVytYT3VuQ2NVVDY4TVZ1Wk5KYkRuSEFSSEVlaTlVNVBTelU5RGZ6d2pPdmhqWDNJMWhwdWUrWi83MDVHVjlPQUxSTGlwZWZPeTFOZFhwZTdHRDZnQW00bUJUK2c1eC9Ec3ZDbWVnSDFDVXRTdHVuU1ZFa2JpZytQRC9oMUwyRTNqSHhVQldaa28yU256WUc0cG0vV1RSWkFVZHZuQT09In0="
      }
    }
  }
  ```

5개 결과가 반환되고 다음 결과 집합을 가져오는데 사용할 수 있는 `nextToken`도 있습니다.
+ 이전 결과 집합에서 `allPost`을 포함하도록 `nextToken` 쿼리를 업데이트합니다.

  ```
  query allPost {
    allPost(
      count: 5
      nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRlluNktJRWl6V0ZlR3hJOVJkaStrZUFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5cW8yUGFSZThnalFpemRCTUNBUkNBZ2dHWk1JODhUNzhIOFVUZGtpdFM2ZFluSWRyVDg4c2lkN1RjZzB2d1k3VGJTTWpSQ2U3WjY3TkUvU2I1dWNETUdDMmdmMHErSGJSL0pteGRzYzVEYnE1K3BmWEtBdU5jSENJdWNIUkJ0UHBPWVdWdCtsS2U5L1pNcWdocXhrem1RaXI1YnIvQkt6dU5hZmJCdE93NmtoM2Jna1BKM0RjWWhpMFBGbmhMVGg4TUVGSjBCcXg3RTlHR1V5N0tUS0JLZlV3RjFQZ0JRREdrNzFYQnFMK2R1S2IrVGtZZzVYMjFrc3NyQmFVTmNXZmhTeXE0ZUJHSWhqZWQ5c3VKWjBSSTc2ZnVQdlZkR3FLNENjQmxHYXhpekZnK2pKK1FneEU1SXduRTNYYU5TR0I4QUpmamR2bU1wbUk1SEdvWjlMUUswclczbG14RDRtMlBsaTNLaEVlcm9pem5zcmdINFpvcXIrN2ltRDN3QkJNd3BLbGQzNjV5Nnc4ZnMrK2FnbTFVOUlKOFFrOGd2bEgySHFROHZrZXBrMWlLdWRIQ25LaS9USnBlMk9JeEVPazVnRFlzRTRUU09HUlVJTkxYY2MvdW1WVEpBMUthV2hWTlAvdjNlSnlZQUszbWV6N2h5WHVXZ1BkTVBNWERQdTdjVnVRa3EwK3NhbGZOd2wvSUx4bHNyNDVwTEhuVFpyRWZvVlV1bXZ5S2VKY1RUU1lET05hM1NwWEd2UT09In0="
    ) {
      posts {
        id
        author
      }
      nextToken
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 나머지 4개 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 게시물이 모두 9개이며 남은 게시물이 없기 때문에 이 결과 집합에는 `nextToken`이 없습니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "allPost": {
        "posts": [
          {
            "id": "2",
            "title": "A series of posts, Volume 2"
          },
          {
            "id": "3",
            "title": "A series of posts, Volume 3"
          },
          {
            "id": "4",
            "title": "A series of posts, Volume 4"
          },
          {
            "id": "8",
            "title": "A series of posts, Volume 8"
          }
        ],
        "nextToken": null
      }
    }
  }
  ```

## allPostsByAuthor 해석기 설정(DynamoDB Query)
<a name="setting-up-the-allpostsbyauthor-resolver-ddb-query"></a>

DynamoDB에서 모든 게시물을 스캔하는 것 이외에 DynamoDB를 쿼리해 특정 작성자가 생성한 게시물을 검색할 수 있습니다. 앞서 이미 생성한 DynamoDB 테이블에는 특정 작성자가 생성한 게시물을 모두 검색하는 DynamoDB `Query` 작업에 사용할 수 있는 `author-index`라는 `GlobalSecondaryIndex`가 있습니다.
+ **스키마** 탭을 선택합니다.
+ **스키마** 창에서 다음과 같이 `Query` 형식을 수정하여 새로운 `allPostsByAuthor` 쿼리를 추가할 수 있습니다.

  ```
  type Query {
      allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts!
      allPost(count: Int, nextToken: String): PaginatedPosts!
      getPost(id: ID): Post
  }
  ```

   **참고:** 여기서는 `allPost` 쿼리에 사용한 것과 동일한 `PaginatedPosts` 형식을 사용합니다.
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **쿼리** 유형에서 새로 생성한 **allPostsByAuthor** 필드를 찾은 다음 **연결**을 선택합니다.
+ **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "Query",
      "index" : "author-index",
      "query" : {
        "expression": "author = :author",
          "expressionValues" : {
            ":author" : $util.dynamodb.toDynamoDBJson($context.arguments.author)
          }
      }
      #if( ${context.arguments.count} )
          ,"limit": $util.toJson($context.arguments.count)
      #end
      #if( ${context.arguments.nextToken} )
          ,"nextToken": "${context.arguments.nextToken}"
      #end
  }
  ```

  `allPost` 해석기처럼 이 해석기에는 두 가지 선택적 인수가 있는데, `count`는 단일 호출 시 반환되는 최대 항목 수를 지정하고, `nextToken`은 다음 결과 집합을 가져오는 데 사용할 수 있습니다(`nextToken`의 값은 이전 호출에서 얻을 수 있음).
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```

   **참고:** 이는 `allPost` 해석기에서 사용한 것과 동일한 응답 매핑 템플릿입니다.
+ **저장**을 선택합니다.

`Query` 요청 매핑에 대한 자세한 내용은 [쿼리](aws-appsync-resolver-mapping-template-reference-dynamodb-query.md) 참조 문서를 참조하십시오.

### 작성자별 모든 게시물을 쿼리하는 API 직접 호출
<a name="call-the-api-to-query-all-posts-by-an-author"></a>

이제 해석기가 설정되었으며 AWS AppSync는 `author-index` 인덱스에 대해 수신 `allPostsByAuthor` 변형을 DynamoDB`Query` 작업으로 변환하는 방법을 알고 있습니다. 이제 테이블을 쿼리해 특정 작성자별로 게시물을 모두 검색할 수 있습니다.

지금까지 사용한 모든 게시물은 작성자가 동일하기 때문에 검색하기 전에 추가 게시물로 테이블을 채우겠습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다.

  ```
  mutation addPost {
    post1: addPost(id:10 author: "Nadia" title: "The cutest dog in the world" content: "So cute. So very, very cute." url: "https://aws.amazon.com/appsync/" ) { author, title }
    post2: addPost(id:11 author: "Nadia" title: "Did you know...?" content: "AppSync works offline?" url: "https://aws.amazon.com/appsync/" ) { author, title }
    post3: addPost(id:12 author: "Steve" title: "I like GraphQL" content: "It's great" url: "https://aws.amazon.com/appsync/" ) { author, title }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.

이제 `Nadia`가 작성한 게시물을 모두 반환하도록 테이블을 쿼리하겠습니다.
+ **쿼리** 창에 다음 쿼리를 붙여 넣습니다.

  ```
  query allPostsByAuthor {
    allPostsByAuthor(author: "Nadia") {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ `Nadia`가 작성한 모든 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "allPostsByAuthor": {
        "posts": [
          {
            "id": "10",
            "title": "The cutest dog in the world"
          },
          {
            "id": "11",
            "title": "Did you know...?"
          }
        ],
        "nextToken": null
      }
    }
  }
  ```

`Query`에 대한 페이지 매김은 `Scan`을 수행할 때와 동일하게 작동합니다. 예를 들어, `AUTHORNAME`의 모든 게시물을 살펴보겠습니다. 이 경우 한 번에 5개 결과를 반환합니다.
+ **쿼리** 창에 다음 쿼리를 붙여 넣습니다.

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "AUTHORNAME"
      count: 5
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ `AUTHORNAME`가 작성한 모든 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "allPostsByAuthor": {
        "posts": [
          {
            "id": "6",
            "title": "A series of posts, Volume 6"
          },
          {
            "id": "4",
            "title": "A series of posts, Volume 4"
          },
          {
            "id": "2",
            "title": "A series of posts, Volume 2"
          },
          {
            "id": "7",
            "title": "A series of posts, Volume 7"
          },
          {
            "id": "1",
            "title": "A series of posts, Volume 1"
          }
        ],
        "nextToken": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ=="
      }
    }
  }
  ```
+ 다음과 같이 이전 쿼리에서 반환된 값으로 `nextToken` 인수를 업데이트합니다.

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "AUTHORNAME"
      count: 5
      nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ=="
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ `AUTHORNAME`가 작성한 잔여 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "allPostsByAuthor": {
        "posts": [
          {
            "id": "8",
            "title": "A series of posts, Volume 8"
          },
          {
            "id": "5",
            "title": "A series of posts, Volume 5"
          },
          {
            "id": "3",
            "title": "A series of posts, Volume 3"
          },
          {
            "id": "9",
            "title": "A series of posts, Volume 9"
          }
        ],
        "nextToken": null
      }
    }
  }
  ```

## 집합 사용
<a name="using-sets"></a>

지금까지 `Post` 형식은 플랫 키/값 객체였습니다. 세트, 목록 및 맵과 같은 AWS AppSyncDynamoDB 해석기를 사용하여 복잡한 객체를 모델링할 수도 있습니다.

태그를 포함하도록 `Post` 형식을 업데이트해 보겠습니다. 게시물에는 태그가 0개 이상 있을 수 있는데, 태그는 DynamoDB에 문자열 집합으로 저장됩니다. 또한 태그를 추가 및 제거하는 몇 가지 변형과 특정 태그가 지정된 게시물을 스캔하는 새 쿼리를 설정해 볼 것입니다.
+ **스키마** 탭을 선택합니다.
+ **스키마** 창에서 다음과 같이 `Post` 형식을 수정하여 새로운 `tags` 필드를 추가할 수 있습니다.

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
  }
  ```
+ **스키마** 창에서 다음과 같이 `Query` 형식을 수정하여 새로운 `allPostsByTag` 쿼리를 추가할 수 있습니다.

  ```
  type Query {
    allPostsByTag(tag: String!, count: Int, nextToken: String): PaginatedPosts!
    allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts!
    allPost(count: Int, nextToken: String): PaginatedPosts!
    getPost(id: ID): Post
  }
  ```
+ **스키마** 창에서 다음과 같이 `Mutation` 유형을 수정하여 새로운 `addTag` 및 `removeTag` 뮤테이션을 추가할 수 있습니다.

  ```
  type Mutation {
    addTag(id: ID!, tag: String!): Post
    removeTag(id: ID!, tag: String!): Post
    deletePost(id: ID!, expectedVersion: Int): Post
    upvotePost(id: ID!): Post
    downvotePost(id: ID!): Post
    updatePost(
      id: ID!,
      author: String,
      title: String,
      content: String,
      url: String,
      expectedVersion: Int!
    ): Post
    addPost(
      author: String!,
      title: String!,
      content: String!,
      url: String!
    ): Post!
  }
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **쿼리** 유형에서 새로 생성한 **allPostsByTag** 필드를 찾은 다음 **연결**을 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "Scan",
      "filter": {
        "expression": "contains (tags, :tag)",
          "expressionValues": {
            ":tag": $util.dynamodb.toDynamoDBJson($context.arguments.tag)
          }
      }
      #if( ${context.arguments.count} )
          ,"limit": $util.toJson($context.arguments.count)
      #end
      #if( ${context.arguments.nextToken} )
          ,"nextToken": $util.toJson($context.arguments.nextToken)
      #end
  }
  ```
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "posts": $utils.toJson($context.result.items)
      #if( ${context.result.nextToken} )
          ,"nextToken": $util.toJson($context.result.nextToken)
      #end
  }
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 새로 생성한 **addTag** 필드를 찾은 다음 **연결**을 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "ADD tags :tags, version :plusOne",
          "expressionValues" : {
              ":tags" : { "SS": [ $util.toJson($context.arguments.tag) ] },
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 새로 생성한 **removeTag** 필드를 찾은 다음 **연결**을 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
      "version" : "2017-02-28",
      "operation" : "UpdateItem",
      "key" : {
          "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
      },
      "update" : {
          "expression" : "DELETE tags :tags ADD version :plusOne",
          "expressionValues" : {
              ":tags" : { "SS": [ $util.toJson($context.arguments.tag) ] },
              ":plusOne" : { "N" : 1 }
          }
      }
  }
  ```
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```
+ **저장**을 선택합니다.

### 태그를 사용하는 API 직접 호출
<a name="call-the-api-to-work-with-tags"></a>

이제 해석기를 설정했으므로 AWS AppSync는 들어오는 , `addTag` `removeTag`및 `allPostsByTag` 요청을 DynamoDB`UpdateItem` 및 `Scan` 작업으로 변환하는 방법을 알고 있습니다.

실행해 보기 위해 이전에 생성한 게시물 중 선택해 보겠습니다. 예를 들어, `Nadia`에서 작성한 게시물을 사용해 보겠습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 쿼리를 붙여 넣습니다.

  ```
  query allPostsByAuthor {
    allPostsByAuthor(
      author: "Nadia"
    ) {
      posts {
        id
        title
      }
      nextToken
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ Nadia의 모든 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "allPostsByAuthor": {
        "posts": [
          {
            "id": "10",
            "title": "The cutest dog in the world"
          },
          {
            "id": "11",
            "title": "Did you known...?"
          }
        ],
        "nextToken": null
      }
    }
  }
  ```
+ 제목이 `"The cutest dog in the world"`인 게시물을 사용하겠습니다. 나중에 사용할 것이기 때문에 이 게시물의 `id`를 적어 둡니다.

`dog` 태그를 추가합니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

  ```
  mutation addTag {
    addTag(id:10 tag: "dog") {
      id
      title
      tags
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 새 태그로 게시물이 업데이트됩니다.

  ```
  {
    "data": {
      "addTag": {
        "id": "10",
        "title": "The cutest dog in the world",
        "tags": [
          "dog"
        ]
      }
    }
  }
  ```

태그를 다음과 같이 더 추가할 수 있습니다.
+ `tag` 인수를 `puppy`로 변경하도록 변형을 업데이트합니다.

  ```
  mutation addTag {
    addTag(id:10 tag: "puppy") {
      id
      title
      tags
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 새 태그로 게시물이 업데이트됩니다.

  ```
  {
    "data": {
      "addTag": {
        "id": "10",
        "title": "The cutest dog in the world",
        "tags": [
          "dog",
          "puppy"
        ]
      }
    }
  }
  ```

태그를 삭제할 수도 있습니다.
+ **쿼리** 창에 다음 변형을 붙여 넣습니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

  ```
  mutation removeTag {
    removeTag(id:10 tag: "puppy") {
      id
      title
      tags
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ 게시물이 업데이트되고 `puppy` 태그가 삭제됩니다.

  ```
  {
    "data": {
      "addTag": {
        "id": "10",
        "title": "The cutest dog in the world",
        "tags": [
          "dog"
        ]
      }
    }
  }
  ```

또한 태그가 지정된 게시물을 모두 검색할 수도 있습니다.
+ **쿼리** 창에 다음 쿼리를 붙여 넣습니다.

  ```
  query allPostsByTag {
    allPostsByTag(tag: "dog") {
      posts {
        id
        title
        tags
      }
      nextToken
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ `dog` 태그가 지정된 게시물이 다음과 같이 모두 반환됩니다.

  ```
  {
    "data": {
      "allPostsByTag": {
        "posts": [
          {
            "id": "10",
            "title": "The cutest dog in the world",
            "tags": [
              "dog",
              "puppy"
            ]
          }
        ],
        "nextToken": null
      }
    }
  }
  ```

## 목록 및 맵 사용
<a name="using-lists-and-maps"></a>

DynamoDB 집합을 사용하는 것 이외에 DynamoDB 목록 및 맵을 사용하여 단일 객체 내에서 복잡한 데이터를 모델링할 수 있습니다.

게시물에 주석을 추가하는 기능을 추가해 보겠습니다. 이 기능은 DynamoDB의 `Post` 객체에 대한 맵 객체 목록으로 모델링됩니다.

 **참고:** 실제 애플리케이션에서는 설명을 고유의 테이블에 모델링하게 될 것입니다. 이 자습서에서는 설명을 `Post` 테이블에 추가하겠습니다.
+ **스키마** 탭을 선택합니다.
+ **스키마** 창에서 다음과 같이 새로운 `Comment` 형식을 추가합니다.

  ```
  type Comment {
      author: String!
      comment: String!
  }
  ```
+ **스키마** 창에서 다음과 같이 `Post` 형식을 수정하여 새로운 `comments` 필드를 추가할 수 있습니다.

  ```
  type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
    tags: [String!]
    comments: [Comment!]
  }
  ```
+ **스키마** 창에서 다음과 같이 `Mutation` 형식을 수정하여 새로운 `addComment` 변형을 추가할 수 있습니다.

  ```
  type Mutation {
    addComment(id: ID!, author: String!, comment: String!): Post
    addTag(id: ID!, tag: String!): Post
    removeTag(id: ID!, tag: String!): Post
    deletePost(id: ID!, expectedVersion: Int): Post
    upvotePost(id: ID!): Post
    downvotePost(id: ID!): Post
    updatePost(
      id: ID!,
      author: String,
      title: String,
      content: String,
      url: String,
      expectedVersion: Int!
    ): Post
    addPost(
      author: String!,
      title: String!,
      content: String!,
      url: String!
    ): Post!
  }
  ```
+ **저장**을 선택합니다.
+ 오른쪽의 **데이터 유형** 창의 **뮤테이션** 유형에서 새로 생성한 **addComment** 필드를 찾은 다음 **연결**을 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **PostDynamoDBTable**을 선택합니다.
+ 다음을 **Configure the request mapping template(요청 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  {
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
      "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
    },
    "update" : {
      "expression" : "SET comments = list_append(if_not_exists(comments, :emptyList), :newComment) ADD version :plusOne",
      "expressionValues" : {
        ":emptyList": { "L" : [] },
        ":newComment" : { "L" : [
          { "M": {
            "author": $util.dynamodb.toDynamoDBJson($context.arguments.author),
            "comment": $util.dynamodb.toDynamoDBJson($context.arguments.comment)
            }
          }
        ] },
        ":plusOne" : $util.dynamodb.toDynamoDBJson(1)
      }
    }
  }
  ```

  이 업데이트 표현식은 새 주석이 포함된 목록을 기존 `comments` 목록에 추가합니다. 목록이 아직 없으면 생성됩니다.
+ 다음을 **Configure the response mapping template(응답 매핑 템플릿 구성)**에 붙여 넣습니다.

  ```
  $utils.toJson($context.result)
  ```
+ **저장**을 선택합니다.

### 설명 추가를 위한 API 직접 호출
<a name="call-the-api-to-add-a-comment"></a>

이제 해석기를 설정했으므로 AWS AppSync는 수신 `addComment` 요청을 DynamoDB`UpdateItem` 작업으로 변환하는 방법을 알고 있습니다.

태그를 추가한 것과 동일한 게시물에 주석을 추가해 보겠습니다.
+ **Queries** 탭을 선택합니다.
+ **쿼리** 창에 다음 쿼리를 붙여 넣습니다.

  ```
  mutation addComment {
    addComment(
      id:10
      author: "Steve"
      comment: "Such a cute dog."
    ) {
      id
      comments {
        author
        comment
      }
    }
  }
  ```
+ **Execute query(쿼리 실행)**(주황색 재생 버튼)를 누릅니다.
+ Nadia의 모든 게시물이 쿼리 창 오른쪽에 있는 결과 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

  ```
  {
    "data": {
      "addComment": {
        "id": "10",
        "comments": [
          {
            "author": "Steve",
            "comment": "Such a cute dog."
          }
        ]
      }
    }
  }
  ```

이 요청을 여러 번 실행하면 목록에 주석이 여러 개 추가됩니다.

## 결론
<a name="conclusion"></a>

이 자습서에서는 AWS AppSync 및 GraphQL을 사용하여 DynamoDB의 Post 객체를 조작할 수 있는 API를 구축했습니다. 자세한 내용은 [해석기 매핑 템플릿 참조](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference) 단원을 참조하십시오.

정리를 위해 콘솔에서 AppSync GraphQL API를 삭제할 수 있습니다.

이 자습서에서 생성한 DynamoDB 테이블과 IAM 역할을 삭제하려면 다음을 실행하여 `AWSAppSyncTutorialForAmazonDynamoDB` 스택을 삭제하거나 CloudFormation 콘솔을 방문하여 스택을 삭제할 수 있습니다.

```
aws cloudformation delete-stack \
    --stack-name AWSAppSyncTutorialForAmazonDynamoDB
```

# 에서 AWS Lambda 해석기 사용 AWS AppSync
<a name="tutorial-lambda-resolvers"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

를 AWS AppSync AWS Lambda 와 함께 사용하여 모든 GraphQL 필드를 확인할 수 있습니다. 예를 들어, GraphQL 쿼리는 Amazon Relational Database Service(Amazon RDS) 인스턴스로 호출을 보내고, GraphQL 뮤테이션은 Amazon Kinesis 스트림에 쓸 수 있습니다. 이 단원에서는 GraphQL 필드 작업의 호출에 따라 비즈니스 로직을 수행하는 Lambda 함수의 작성 방법을 설명합니다.

## Lambda 함수 생성
<a name="create-a-lam-function"></a>

다음 예는 블로그 게시물 애플리케이션의 일부로 블로그 게시물에 대해 다양한 연산을 수행하는 `Node.js`로 작성된 Lambda 함수를 보여줍니다.

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var posts = {
         "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"},
         "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"},
         "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null },
         "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"},
         "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got an Invoke Request.");
    switch(event.field) {
        case "getPost":
            var id = event.arguments.id;
            callback(null, posts[id]);
            break;
        case "allPosts":
            var values = [];
            for(var d in posts){
                values.push(posts[d]);
            }
            callback(null, values);
            break;
        case "addPost":
            // return the arguments back
            callback(null, event.arguments);
            break;
        case "addPostErrorWithData":
            var id = event.arguments.id;
            var result = posts[id];
            // attached additional error information to the post
            result.errorMessage = 'Error with the mutation, data has changed';
            result.errorType = 'MUTATION_ERROR';
            callback(null, result);
            break;
        case "relatedPosts":
            var id = event.source.id;
            callback(null, relatedPosts[id]);
            break;
        default:
            callback("Unknown field, unable to resolve" + event.field, null);
            break;
    }
};
```

Lambda 함수는 ID별로 게시물을 검색하고, 게시물을 추가하고, 게시물 목록을 가져오고, 지정된 게시물에 대해 관련 게시물을 가져오는 작업을 처리합니다.

 **참고:** `event.field`에 대해 `switch` 명령문을 사용하면 Lambda 함수가 현재 해석 중인 필드를 확인할 수 있습니다.

 AWS 관리 콘솔 또는 AWS CloudFormation 스택을 사용하여이 Lambda 함수를 생성합니다. CloudFormation 스택에서 함수를 만들려면 다음 AWS Command Line Interface (AWS CLI) 명령을 사용할 수 있습니다.

```
aws cloudformation create-stack --stack-name AppSyncLambdaExample \
--template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml \
--capabilities CAPABILITY_NAMED_IAM
```

여기에서 AWS 계정의 미국 서부(오레곤) AWS 리전에서 CloudFormation 스택을 시작할 수도 있습니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml)

## Lambda용 데이터 소스 구성
<a name="configure-data-source-for-lamlong"></a>

Lambda 함수를 만든 후 AWS AppSync 콘솔에서 GraphQL API로 이동한 다음 **데이터 소스** 탭을 선택합니다.

**데이터 소스 생성**을 선택하고 친숙한 **데이터 소스 이름**(예: **Lambda**)을 입력한 다음 **데이터 소스 유형**에서 **AWS Lambda 함수**를 선택합니다. **리전**에서 함수와 동일한 리전을 선택합니다. (제공된 CloudFormation 스택에서 함수를 생성했다면 해당 함수는 **US-WEST-2**에 있을 것입니다.) **함수 ARN**의 경우, Lambda 함수의 Amazon Resource Name(ARN)을 선택합니다.

Lambda 함수를 선택한 후 새 AWS Identity and Access Management (IAM) 역할( AWS AppSync가 적절한 권한을 할당하는 역할)을 생성하거나 다음 인라인 정책이 있는 기존 역할을 선택할 수 있습니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "arn:aws:lambda:us-east-1:111122223333:function:LAMBDA_FUNCTION"
        }
    ]
}
```

------

또한 다음과 같이 IAM 역할에 대해 AWS AppSync와의 신뢰 관계를 설정해야 합니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

## GraphQL 스키마 생성
<a name="creating-a-graphql-schema"></a>

이제 데이터 소스가 Lambda 함수에 연결되었으며 GraphQL 스키마를 생성해 보겠습니다.

 AWS AppSync 콘솔의 스키마 편집기에서 스키마가 다음 스키마와 일치하는지 확인합니다.

```
schema {
    query: Query
    mutation: Mutation
}

type Query {
    getPost(id:ID!): Post
    allPosts: [Post]
}

type Mutation {
    addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!
}

type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    relatedPosts: [Post]
}
```

## 해석기 구성
<a name="configuring-resolvers"></a>

이제 Lambda 데이터 소스와 유효한 GraphQL 스키마를 등록했으며, 해석기를 사용하여 GraphQL 필드를 Lambda 데이터 소스에 연결할 수 있습니다.

해석기를 생성하려면 매핑 템플릿이 필요합니다. 매핑 템플릿에 대한 자세히 알아보려면 [Resolver Mapping Template Overview](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview) 단원을 참조하세요.

Lambda 매핑 템플릿에 대한 자세한 내용은 [Resolver mapping template reference for Lambda](resolver-mapping-template-reference-lambda.md#aws-appsync-resolver-mapping-template-reference-lambda) 단원을 참조하세요.

이 단계에서는 `getPost(id:ID!): Post`, `allPosts: [Post]`, `addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!`, `Post.relatedPosts: [Post]` 필드에 대한 해석기를 Lambda 함수에 연결합니다.

 AWS AppSync 콘솔의 스키마 편집기 오른쪽에서에 대한 **해석기 연결을** 선택합니다`getPost(id:ID!): Post`.

그런 다음 **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다.

그런 다음 Lambda 데이터 소스를 선택합니다. **request mapping template(요청 매핑 템플릿)** 섹션에서 **Invoke And Forward Arguments(인수 호출 및 전달)**을 선택합니다.

`payload` 객체를 수정하여 필드 이름을 추가합니다. 템플릿은 다음과 같아야 합니다.

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "getPost",
        "arguments":  $utils.toJson($context.arguments)
    }
}
```

**response mapping template(요청 매핑 템플릿)** 섹션에서 **Return Lambda Result(Lambda 결과 반환)**를 선택합니다.

이 경우에는 기본 템플릿을 있는 그대로 사용하겠습니다. 이 템플릿은 다음과 같습니다.

```
$utils.toJson($context.result)
```

**저장**을 선택합니다. 이제 첫 번째 해석기를 성공적으로 연결했습니다. 다음과 같이 나머지 필드에 대해 이 작업을 반복합니다.

`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` 요청 매핑 템플릿

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "addPost",
        "arguments":  $utils.toJson($context.arguments)
    }
}
```

`addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` 응답 매핑 템플릿

```
$utils.toJson($context.result)
```

`allPosts: [Post]` 요청 매핑 템플릿

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "allPosts"
    }
}
```

`allPosts: [Post]` 응답 매핑 템플릿

```
$utils.toJson($context.result)
```

`Post.relatedPosts: [Post]` 요청 매핑 템플릿

```
{
    "version": "2017-02-28",
    "operation": "Invoke",
    "payload": {
        "field": "relatedPosts",
        "source":  $utils.toJson($context.source)
    }
}
```

`Post.relatedPosts: [Post]` 응답 매핑 템플릿

```
$utils.toJson($context.result)
```

## GraphQL API 테스트
<a name="testing-your-graphql-api"></a>

Lambda 함수가 GraphQL 해석기에 연결되었으므로 콘솔이나 클라이언트 애플리케이션을 사용하여 뮤테이션 및 쿼리를 실행할 수 있습니다.

 AWS AppSync 콘솔 왼쪽에서 **쿼리**를 선택한 다음 다음 코드를 붙여 넣습니다.

### addPost 변형
<a name="addpost-mutation"></a>

```
mutation addPost {
    addPost(
        id: 6
        author: "Author6"
        title: "Sixth book"
        url: "https://www.amazon.com/"
        content: "This is the book is a tutorial for using GraphQL with AWS AppSync."
    ) {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### getPost 쿼리
<a name="getpost-query"></a>

```
query getPost {
    getPost(id: "2") {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### allPosts 쿼리
<a name="allposts-query"></a>

```
query allPosts {
    allPosts {
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {
            id
            title
        }
    }
}
```

## 오류 반환
<a name="returning-errors"></a>

필드 해상도를 지정하면 오류가 발생할 수 있습니다. AWS AppSync를 사용하면 다음 소스에서 오류가 발생할 수 있습니다.
+ 요청 또는 응답 매핑 템플릿
+ Lambda 함수

### 매핑 템플릿에서
<a name="from-the-mapping-template"></a>

의도적인 오류를 발생시키려면 Velocity 템플릿 언어(VTL) 템플릿의 `$utils.error` 도우미 메서드를 사용하면 됩니다. `errorMessage`, `errorType` 및 선택적 `data` 값을 인수로 사용합니다. `data`는 오류가 발생한 경우 외부 데이터를 클라이언트로 반환할 때 유용합니다. GraphQL 최종 응답에서 `errors`에 `data` 객체가 추가됩니다.

다음 예시는 `Post.relatedPosts: [Post]` 응답 매핑 템플릿에서 이 데이터 객체를 사용하는 방법을 보여줍니다.

```
$utils.error("Failed to fetch relatedPosts", "LambdaFailure", $context.result)
```

다음과 유사한 GraphQL 응답이 산출됩니다.

```
{
    "data": {
        "allPosts": [
            {
                "id": "2",
                "title": "Second book",
                "relatedPosts": null
            },
            ...
        ]
    },
    "errors": [
        {
            "path": [
                "allPosts",
                0,
                "relatedPosts"
            ],
            "errorType": "LambdaFailure",
            "locations": [
                {
                    "line": 5,
                    "column": 5
                }
            ],
            "message": "Failed to fetch relatedPosts",
            "data": [
                {
                  "id": "2",
                  "title": "Second book"
                },
                {
                  "id": "1",
                  "title": "First book"
                }
            ]
        }
    ]
}
```

여기서 `allPosts[0].relatedPosts`는 오류로 인해 *null*이며 `errorMessage`, `errorType` 및 `data`가 `data.errors[0]` 객체 안에 있습니다.

### Lambda 함수에서
<a name="from-the-lam-function"></a>

AWS AppSync는 Lambda 함수에서 발생하는 오류도 이해합니다. Lambda 프로그래밍 모델은 *handled* 오류를 발생시킵니다. Lambda 함수에서 오류가 발생하면 AWS AppSync가 현재 필드를 확인하지 못합니다. Lambda에서 반환되는 오류 메시지만 응답에 설정됩니다. 현재는 Lambda 함수에서 오류를 발생시켜 관련 없는 데이터를 클라이언트에 다시 전달할 수 없습니다.

 **참고**: Lambda 함수에서 *처리되지 않은* 오류가 발생하면 AWS AppSync는 Lambda가 설정한 오류 메시지를 사용합니다.

다음 Lambda 함수는 오류를 발생시킵니다.

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    callback("I fail. Always.");
};
```

다음과 유사한 GraphQL 응답이 반환됩니다.

```
{
    "data": {
        "allPosts": [
            {
                "id": "2",
                "title": "Second book",
                "relatedPosts": null
            },
            ...
        ]
    },
    "errors": [
        {
            "path": [
                "allPosts",
                0,
                "relatedPosts"
            ],
            "errorType": "Lambda:Handled",
            "locations": [
                {
                    "line": 5,
                    "column": 5
                }
            ],
            "message": "I fail. Always."
        }
    ]
}
```

## 고급 사용 사례: 일괄 처리
<a name="advanced-use-case-batching"></a>

이 예제의 Lambda 함수에는 해당 게시물에 대한 관련 게시물 목록을 반환하는 `relatedPosts` 필드가 있습니다. 예제 쿼리에서 Lambda 함수의 `allPosts` 필드 호출은 5개 게시물을 반환합니다. 각각의 반환된 게시물에 대해 `relatedPosts`를 해석하도록 지정했으므로 `relatedPosts` 필드 작업이 5회 호출됩니다.

```
query allPosts {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yields 5 posts
            id
            title
        }
    }
}
```

이 특정 예제에서는 큰 문제가 되지 않을 수 있지만, 이러한 복합적이고 과도한 가져오기로 인해 애플리케이션이 빠르게 손상될 수 있습니다.

동일한 쿼리에서 반환된 관련 `Posts`에 대해 `relatedPosts`를 다시 가져오려 할 경우 호출 횟수가 크게 증가할 것입니다.

```
query allPosts {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yield 5 posts = 5 x 5 Posts
            id
            title
            relatedPosts {  // 5 x 5 Lambda invocations - each yield 5 posts = 25 x 5 Posts
                id
                title
                author
            }
        }
    }
}
```

이 비교적 간단한 쿼리에서 AWS AppSync는 Lambda 함수 1 \$1 5 \$1 25 = 31회를 호출합니다.

이는 상당히 일반적인 문제로 대개 N\$11 문제(이 경우에 N = 5)라고도 하며 이로 인해 애플리케이션의 지연 시간 및 비용이 증가될 수 있습니다.

이 문제를 해결하는 한 가지 방법은 유사한 필드 해석기 요청을 하나로 묶는 것입니다. 이 예제에서는 지정된 단일 게시물에 대한 관련 게시물 목록을 해석하는 Lambda 함수 하나 대신에, 해당 게시물 배치에 대한 관련 게시물 목록을 해석합니다.

예시를 배치위해 `Post.relatedPosts: [Post]` 해석기를 배치 활성화된 해석기로 전환하겠습니다.

 AWS AppSync 콘솔의 오른쪽에서 기존 `Post.relatedPosts: [Post]` 해석기를 선택합니다. 요청 매핑 템플릿을 다음으로 변경합니다.

```
{
    "version": "2017-02-28",
    "operation": "BatchInvoke",
    "payload": {
        "field": "relatedPosts",
        "source":  $utils.toJson($context.source)
    }
}
```

`operation` 필드만 `Invoke`에서 `BatchInvoke`로 변경되었습니다. 이제 페이로드 필드가 템플릿에 지정된 배열이 됩니다. 이 예제에서는 Lambda 함수가 다음을 입력으로 수신합니다.

```
[
    {
        "field": "relatedPosts",
        "source": {
            "id": 1
        }
    },
    {
        "field": "relatedPosts",
        "source": {
            "id": 2
        }
    },
    ...
]
```

`BatchInvoke`가 요청 매핑 템플릿에 지정되었으면 Lambda 함수가 요청 목록을 수신하고 결과 목록을 반환합니다.

특히 결과 목록은 요청 페이로드 항목의 크기 및 순서와 일치해야 AWS AppSync가 그에 따라 결과를 일치시킬 수 있습니다.

이 일괄 처리 예제에서 Lambda 함수는 다음과 같이 결과의 배치를 반환합니다.

```
[
    [{"id":"2","title":"Second book"}, {"id":"3","title":"Third book"}],   // relatedPosts for id=1
    [{"id":"3","title":"Third book"}]                                                             // relatedPosts for id=2
]
```

Node.js의 다음 Lambda 함수는 다음과 같이 `Post.relatedPosts` 필드에 대한 이 일괄 처리 기능을 보여줍니다.

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var posts = {
         "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"},
         "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"},
         "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null },
         "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"},
         "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length);
    // event is now an array
    var field = event[0].field;
    switch(field) {
        case "relatedPosts":
            var results = [];
            // the response MUST contain the same number
            // of entries as the payload array
            for (var i=0; i< event.length; i++) {
                console.log("post {}", JSON.stringify(event[i].source));
                results.push(relatedPosts[event[i].source.id]);
            }
            console.log("results {}", JSON.stringify(results));
            callback(null, results);
            break;
        default:
            callback("Unknown field, unable to resolve" + field, null);
            break;
    }
};
```

### 개별 오류 반환
<a name="returning-individual-errors"></a>

앞의 예에서는 Lambda 함수에서 단일 오류가 반환되거나 매핑 템플릿에서 오류를 일으킬 수 있음을 보았습니다. 일괄 처리된 호출의 경우, Lambda 함수에서 오류가 발생하면 전체 배치가 실패로 플래그 지정됩니다. 이는 데이터 스토어와의 연결 중단 같은 취소 불가능한 오류가 발생하는 특정 시나리오를 잘 설명할 수 있습니다. 하지만 배치의 일부 항목이 성공하고 다른 항목이 실패하는 경우, 오류 및 유효한 데이터를 모두 반환할 수 있습니다. 배치의 원래 크기와 일치하는 요소를 나열하려면 AWS AppSync에 배치 응답이 필요하므로 유효한 데이터를 오류와 구분할 수 있는 데이터 구조를 정의해야 합니다.

예를 들어 Lambda 함수가 관련 게시물의 일괄 처리를 반환해야 하는 경우 각 객체에 선택적 *data*, *errorMessage* 및 *errorType* 필드가 있는 `Response` 객체 목록을 반환하도록 선택할 수 있습니다. *errorMessage* 필드가 있는 경우 오류가 발생했음을 의미합니다.

다음 코드는 Lambda 함수를 업데이트하는 방법을 보여줍니다.

```
exports.handler = (event, context, callback) => {
    console.log("Received event {}", JSON.stringify(event, 3));
    var posts = {
         "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"},
         "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"},
         "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null },
         "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"},
         "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} };

    var relatedPosts = {
        "1": [posts['4']],
        "2": [posts['3'], posts['5']],
        "3": [posts['2'], posts['1']],
        "4": [posts['2'], posts['1']],
        "5": []
    };

    console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length);
    // event is now an array
    var field = event[0].field;
    switch(field) {
        case "relatedPosts":
            var results = [];
            results.push({ 'data': relatedPosts['1'] });
            results.push({ 'data': relatedPosts['2'] });
            results.push({ 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' });
            results.push(null);
            results.push({ 'data': relatedPosts['3'], 'errorMessage': 'Error Happened with last result', 'errorType': 'ERROR' });
            callback(null, results);
            break;
        default:
            callback("Unknown field, unable to resolve" + field, null);
            break;
    }
};
```

이 예의 경우,다음의 응답 매핑 템플릿을 통해 Lambda 함수의 각 항목과 발생하는 모든 오류를 구문 분석할 수 있습니다.

```
#if( $context.result && $context.result.errorMessage )
    $utils.error($context.result.errorMessage, $context.result.errorType, $context.result.data)
#else
    $utils.toJson($context.result.data)
#end
```

이 예에서는 다음과 유사한 GraphQL 응답이 반환됩니다.

```
{
  "data": {
    "allPosts": [
      {
        "id": "1",
        "relatedPostsPartialErrors": [
          {
            "id": "4",
            "title": "Fourth book"
          }
        ]
      },
      {
        "id": "2",
        "relatedPostsPartialErrors": [
          {
            "id": "3",
            "title": "Third book"
          },
          {
            "id": "5",
            "title": "Fifth book"
          }
        ]
      },
      {
        "id": "3",
        "relatedPostsPartialErrors": null
      },
      {
        "id": "4",
        "relatedPostsPartialErrors": null
      },
      {
        "id": "5",
        "relatedPostsPartialErrors": null
      }
    ]
  },
  "errors": [
    {
      "path": [
        "allPosts",
        2,
        "relatedPostsPartialErrors"
      ],
      "errorType": "ERROR",
      "locations": [
        {
          "line": 4,
          "column": 9
        }
      ],
      "message": "Error Happened"
    },
    {
      "path": [
        "allPosts",
        4,
        "relatedPostsPartialErrors"
      ],
      "data": [
        {
          "id": "2",
          "title": "Second book"
        },
        {
          "id": "1",
          "title": "First book"
        }
      ],
      "errorType": "ERROR",
      "locations": [
        {
          "line": 4,
          "column": 9
        }
      ],
      "message": "Error Happened with last result"
    }
  ]
}
```

### 최대 배치 크기 구성
<a name="configure-max-batch-size"></a>

기본적으로를 사용하는 경우 `BatchInvoke` AWS AppSync는 Lambda 함수에 최대 5개 항목의 배치로 요청을 보냅니다. Lambda 해석기의 최대 배치 크기를 구성할 수 있습니다.

해석기에서 최대 배치 크기를 구성하려면 AWS Command Line Interface (AWS CLI)에서 다음 명령을 사용합니다.

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --request-mapping-template "<template>" --response-mapping-template "<template>" --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

**참고**  
요청 매핑 템플릿을 제공할 때는 `BatchInvoke` 작업을 사용하여 일괄 처리를 사용해야 합니다.

다음 명령을 사용하여 직접 Lambda 해석기에서 일괄 처리를 활성화하고 구성할 수도 있습니다.

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

### VTL 템플릿을 사용한 최대 배치 크기 구성
<a name="configure-max-batch-size-vtl"></a>

VTL 요청 내 템플릿이 있는 Lambda 해석기의 경우, 최대 배치 크기는 VTL에서 `BatchInvoke` 작업으로 직접 지정하지 않는 한 효과가 없습니다. 마찬가지로 최상위 뮤테이션을 수행하는 경우 GraphQL 사양에서 병렬 뮤테이션을 순차적으로 실행하도록 요구하기 때문에 뮤테이션에 대한 일괄 처리가 수행되지 않습니다.

예를 들어, 다음과 같은 뮤테이션이 있다고 가정합니다.

```
type Mutation {
    putItem(input: Item): Item
    putItems(inputs: [Item]): [Item]
}
```

첫 번째 뮤테이션을 사용하여 아래 코드 조각과 같이 10개 `Items`를 생성할 수 있습니다.

```
mutation MyMutation {
    v1: putItem($someItem1) {
        id,
        name
    }
    v2: putItem($someItem2) {
        id,
        name
    }
    v3: putItem($someItem3) {
        id,
        name
    } 
    v4: putItem($someItem4) {
        id,
        name
    }
    v5: putItem($someItem5) {
        id,
        name
    }
    v6: putItem($someItem6) {
        id,
        name
    } 
    v7: putItem($someItem7) {
        id,
        name
    }
    v8: putItem($someItem8) {
        id,
        name
    }
    v9: putItem($someItem9) {
        id,
        name
    }
    v10: putItem($someItem10) {
        id,
        name
    }
}
```

이 예제에서는 Lambda 해석기에서 최대 일괄 처리 크기가 10으로 설정되어 있어도 `Items`는 10개의 그룹으로 일괄 처리되지 않습니다. 대신 GraphQL 사양에 따라 순차적으로 실행됩니다.

실제 배치 뮤테이션을 수행하려면 다음 예제를 따라 두 번째 뮤테이션을 사용할 수 있습니다.

```
mutation MyMutation {
    putItems([$someItem1, $someItem2, $someItem3,$someItem4, $someItem5, $someItem6, 
    $someItem7, $someItem8, $someItem9, $someItem10]) {
    id,
    name
    }
}
```

직접 Lambda 해석기를 사용한 일괄 처리 사용에 대한 자세한 내용은 [Direct Lambda 해석기](resolver-mapping-template-reference-lambda.md#direct-lambda-resolvers) 단원을 참조하세요.

# AWS AppSync에서 Amazon OpenSearch Service 해석기 사용
<a name="tutorial-elasticsearch-resolvers"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

AWS AppSync는 VPC 내에 존재하지 않는 경우 자체 AWS 계정에 프로비저닝한 도메인에서 Amazon OpenSearch Service 사용을 지원합니다. 도메인을 프로비저닝한 후에는 도메인을 데이터 원본에 연결할 수 있으며, 이때 쿼리, 변형 및 구독 등 GraphQL 작업을 수행하도록 스키마의 해석기를 구성할 수 있습니다. 이 자습서에서는 몇 가지 일반적인 예제를 살펴봅니다.

자세한 내용은 [OpenSearch용 해석기 매핑 템플릿 참조](resolver-mapping-template-reference-elasticsearch.md#aws-appsync-resolver-mapping-template-reference-elasticsearch) 단원을 참조하세요.

## 원클릭 설치
<a name="one-click-setup"></a>

Amazon OpenSearch AWS Service가 구성된 AppSync에서 GraphQL 엔드포인트를 자동으로 설정하려면 다음 AWS CloudFormation 템플릿을 사용할 수 있습니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/appsynces.yml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/appsynces.yml)

 AWS CloudFormation 배포가 완료되면 [GraphQL 쿼리 및 변형 실행](#tutorial-elasticsearch-resolvers-perform-queries-mutations)으로 직접 건너뛸 수 있습니다.

## 새 OpenSearch Service 도메인 생성
<a name="create-a-new-es-domain"></a>

이 자습서를 시작하기 위해서는 기존의 OpenSearch Service 도메인이 필요합니다. 아직 없는 경우 다음 샘플을 사용할 수 있습니다. OpenSearch Service 도메인을 생성하는 데 최대 15분이 걸릴 수 있으며, 그 후 AWS AppSync 데이터 소스와 통합할 수 있습니다.

```
aws cloudformation create-stack --stack-name AppSyncOpenSearch \
--template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml \
--parameters ParameterKey=OSDomainName,ParameterValue=ddtestdomain ParameterKey=Tier,ParameterValue=development \
--capabilities CAPABILITY_NAMED_IAM
```

 AWS 계정의 미국 서부 2(오레곤) 리전에서 다음 AWS CloudFormation 스택을 시작할 수 있습니다.

 [https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml)

## OpenSearch Service를 위한 데이터 소스 구성
<a name="configure-data-source-for-es"></a>

OpenSearch Service 도메인을 생성한 후 AWS AppSync GraphQL API로 이동하여 **데이터 소스** 탭을 선택합니다. **새로 만들기**를 선택하고 'oss'와 같은 데이터 소스에 사용할 친숙한 이름을 입력합니다. 그런 다음 **데이터 소스 유형**에 대해 **Amazon OpenSearch 도메인**을 선택하고 적절한 리전을 선택하면 OpenSearch Service 도메인이 나열됩니다. 선택한 후 새 역할을 생성할 수 있으며 AWS AppSync는 역할에 적합한 권한을 할당하거나 다음과 같은 인라인 정책이 있는 기존 역할을 선택할 수 있습니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "Stmt1234234",
            "Effect": "Allow",
            "Action": [
                "es:ESHttpDelete",
                "es:ESHttpHead",
                "es:ESHttpGet",
                "es:ESHttpPost",
                "es:ESHttpPut"
            ],
            "Resource": [
                "arn:aws:es:us-east-1:111122223333:domain/democluster/*"
            ]
        }
    ]
}
```

------

또한 해당 역할에 대해 AWS AppSync와의 신뢰 관계를 설정해야 합니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

또한 OpenSearch Service 도메인에는 Amazon OpenSearch Service 콘솔을 통해 수정할 수 있는 자체 **액세스 정책이** 있습니다. 사용자는 OpenSearch Service 도메인에 대한 적정 작업 및 리소스를 사용하여 다음과 비슷한 정책을 추가해야 합니다. **보안 주체**는 AppSync 데이터 소스 역할로, 콘솔에서 이 역할을 생성하도록 할 경우 IAM 콘솔에서 확인할 수 있습니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111122223333:role/service-role/APPSYNC_DATASOURCE_ROLE"
            },
            "Action": [
                "es:ESHttpDelete",
                "es:ESHttpHead",
                "es:ESHttpGet",
                "es:ESHttpPost",
                "es:ESHttpPut"
            ],
            "Resource": "arn:aws:es:us-east-1:111122223333:domain/DOMAIN_NAME/*"
        }
    ]
}
```

------

## 해석기 연결
<a name="connecting-a-resolver"></a>

이제 데이터 소스가 OpenSearch Service 도메인에 연결되었으며, 다음 예제에서처럼 해석기를 사용하여 GraphQL 스키마에 연결할 수 있습니다.

```
 schema {
   query: Query
   mutation: Mutation
 }

 type Query {
   getPost(id: ID!): Post
   allPosts: [Post]
 }

 type Mutation {
   addPost(id: ID!, author: String, title: String, url: String, ups: Int, downs: Int, content: String): AWSJSON
 }

type Post {
  id: ID!
  author: String
  title: String
  url: String
  ups: Int
  downs: Int
  content: String
}
...
```

사용자 정의 `Post` 유형과 `id` 필드가 있습니다. 다음 예제에서는 이 유형을 OpenSearch Service 도메인에 넣는 프로세스(자동화할 수 있음)가 있다고 가정하고, 이 프로세스는 `/post/_doc`의 경로 루트에 매핑되며, 여기서 `post`는 인덱스입니다. 이 루트 경로에서 개별 문서 검색, `/id/post*`를 사용한 와일드카드 검색 또는 `/post/_search` 경로를 사용한 다중 문서 검색을 수행할 수 있습니다. 예를 들어, `User`라는 다른 유형이 있는 경우 `user`라는 새 인덱스로 문서를 색인한 다음 `/user/_search`의 **경로**로 검색을 수행할 수 있습니다.

 AWS AppSync 콘솔의 스키마 편집기에서 `searchPosts` 쿼리를 포함하도록 이전 `Posts` 스키마를 수정합니다.

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  searchPosts: [Post]
}
```

스키마를 저장합니다. 오른쪽에 있는 `searchPosts`에서 **Attach resolver(해석기 연결)**를 선택합니다. **작업 메뉴**에서 **런타임 업데이트**를 선택한 다음 **단위 해석기(VTL만 해당)**를 선택합니다. 그런 다음 OpenSearch Service 데이터 소스를 선택합니다. **request mapping template(요청 매핑 템플릿)** 섹션에서 **Query posts(게시물 쿼리)**의 드롭다운을 선택하여 기본 템플릿을 가져옵니다. `path`를 `/post/_search`로 수정합니다. 이 템플릿은 다음과 같습니다.

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path":"/post/_search",
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "from":0,
            "size":50
        }
    }
}
```

여기서는 앞의 스키마에 `post` 필드 아래에 OpenSearch Service에서 색인된 문서가 있다고 가정합니다. 데이터를 다르게 구조화하는 경우 그에 맞게 업데이트해야 합니다.

**응답 매핑 템플릿** 섹션에서 OpenSearch Service 쿼리로부터 데이터 결과를 다시 가져와서 GraphQL로 변환하려면 적절한 `_source` 필터를 지정해야 합니다. 다음 템플릿을 사용합니다.

```
[
    #foreach($entry in $context.result.hits.hits)
    #if( $velocityCount > 1 ) , #end
    $utils.toJson($entry.get("_source"))
    #end
]
```

## 검색 수정
<a name="modifying-your-searches"></a>

이전 요청 매핑 템플릿은 모든 레코드에 대해 단순 쿼리를 수행합니다. 특정 작성자별로 검색하려 한다고 가정하겠습니다. 또한, 작성자를 GraphQL 쿼리에 정의된 인수로 사용하려 한다고 가정하겠습니다. AWS AppSync 콘솔의 스키마 편집기에서 `allPostsByAuthor` 쿼리를 추가합니다.

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  allPostsByAuthor(author: String!): [Post]
  searchPosts: [Post]
}
```

이제 **해석기 연결**을 선택하고 OpenSearch Service 데이터 소스를 선택하되, **응답 매핑 템플릿**에서 다음 예제를 사용합니다.

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path":"/post/_search",
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "from":0,
            "size":50,
            "query":{
                "match" :{
                    "author": $util.toJson($context.arguments.author)
                }
            }
        }
    }
}
```

`body`가 `author` 필드에 대한 쿼리 용어로 채워져서 클라이언트에서 인수로 전달됩니다. 또한 원할 경우 표준 텍스트 같은 미리 채워진 정보를 사용하거나 다른 [유틸리티](resolver-context-reference.md#aws-appsync-resolver-mapping-template-context-reference)를 사용할 수도 있습니다.

이 해석기를 사용하려는 경우 이전 예와 동일한 정보를 **Response Mapping Template(응답 매핑 템플릿)**에 입력합니다.

## OpenSearch Service에 데이터 추가
<a name="adding-data-to-es"></a>

GraphQL 뮤테이션의 결과로서 OpenSearch Service 도메인에 데이터를 추가해야 할 수 있습니다. 이는 검색 및 다른 목적으로 사용할 때 유용한 메커니즘입니다. GraphQL 구독을 사용하여 데이터를 [실시간으로 만들 수 있으므로](aws-appsync-real-time-data.md), 이는 클라이언트에 OpenSearch Service 도메인의 데이터에 대한 업데이트를 알리는 메커니즘으로 작용합니다.

 AWS AppSync 콘솔의 **스키마** 페이지로 돌아가서 `addPost()` 변형에 대한 **해석기 연결을** 선택합니다. OpenSearch Service 데이터 소스를 다시 선택하고 다음 `Posts` 스키마에 대한 **응답 매핑 템플릿**을 사용합니다.

```
{
    "version":"2017-02-28",
    "operation":"PUT",
    "path": $util.toJson("/post/_doc/$context.arguments.id"),
    "params":{
        "headers":{},
        "queryString":{},
        "body":{
            "id": $util.toJson($context.arguments.id),
            "author": $util.toJson($context.arguments.author),
            "ups": $util.toJson($context.arguments.ups),
            "downs": $util.toJson($context.arguments.downs),
            "url": $util.toJson($context.arguments.url),
            "content": $util.toJson($context.arguments.content),
            "title": $util.toJson($context.arguments.title)
        }
    }
}
```

전과 마찬가지로, 이 코드도 데이터의 구조화 방식을 보여주는 예입니다. 다양한 필드 이름 또는 인덱스가 있으면 해당되는 경우 `path` 및 `body`를 업데이트해야 합니다. 이 예제는 `$context.arguments`를 사용하여 GraphQL 변형 인수에서 템플릿을 채우는 방법도 보여줍니다.

계속 진행하기 전에 다음 응답 매핑 템플릿을 사용하여 뮤테이션 작업 결과 또는 오류 정보를 출력으로 반환합니다.

```
#if($context.error)
    $util.toJson($ctx.error)
#else
    $util.toJson($context.result)
#end
```

## 단일 문서 가져오기
<a name="retrieving-a-single-document"></a>

마지막으로 스키마의 `getPost(id:ID)` 쿼리를 사용하여 개별 문서를 반환하려면 AWS AppSync 콘솔의 스키마 편집기에서이 쿼리를 찾아 **해석기 연결을** 선택합니다. OpenSearch Service 데이터 소스를 다시 선택하고 다음 매핑 템플릿을 사용합니다.

```
{
    "version":"2017-02-28",
    "operation":"GET",
    "path": $util.toJson("post/_doc/$context.arguments.id"),
    "params":{
        "headers":{},
        "queryString":{},
        "body":{}
    }
}
```

위의 `path`에서 빈 본문과 함께 `id` 인수를 사용하므로 이 문은 단일 문서를 반환합니다. 하지만 지금은 목록이 아니라 단일 항목을 반환하려고 하므로 다음과 같은 응답 매핑 템플릿을 사용해야 합니다.

```
$utils.toJson($context.result.get("_source"))
```

## 쿼리 및 변형 수행
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations"></a>

이제 OpenSearch Service 도메인에 대해 GraphQL 작업을 수행할 수 있습니다. AWS AppSync 콘솔의 **쿼리** 탭으로 이동하여 새 레코드를 추가합니다.

```
mutation addPost {
    addPost (
        id:"12345"
        author: "Fred"
        title: "My first book"
        content: "This will be fun to write!"
        url: "publisher website",
        ups: 100,
        downs:20 
       )
}
```

오른쪽에 뮤테이션 결과가 표시됩니다. 마찬가지로, 이제 OpenSearch Service 도메인에 대해 `searchPosts` 쿼리를 실행할 수 있습니다.

```
query searchPosts {
    searchPosts {
        id
        title
        author
        content
    }
}
```

## 모범 사례
<a name="best-practices"></a>
+ OpenSearch Service는 기본 데이터베이스가 아니라 데이터 쿼리용입니다. [GraphQL 해석기 결합](tutorial-combining-graphql-resolvers.md#aws-appsync-tutorial-combining-graphql-resolvers)에서 설명했듯이 OpenSearch Service를 Amazon DynamoDB와 함께 사용할 수도 있습니다.
+  AWS AppSync 서비스 역할이 클러스터에 액세스할 수 있도록 허용해야만 도메인에 대한 액세스 권한을 부여합니다.
+ 최저 비용 클러스터를 제공하는 간단한 개발부터 시작하여, 프로덕션으로 들어가면서 고가용성(HA)을 제공하는 대규모 클러스터로 이동할 수 있습니다.

# AWS AppSync에서 로컬 해석기 사용
<a name="tutorial-local-resolvers"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

AWS AppSync를 사용하면 지원되는 데이터 소스(AWS Lambda, Amazon DynamoDB 또는 Amazon OpenSearch Service)를 사용하여 다양한 작업을 수행할 수 있습니다. 하지만 특정 시나리오에서는 지원되는 데이터 원본을 호출할 필요가 없을 수도 있습니다.

이러한 경우에는 로컬 해석기가 유용합니다. 로컬 해석기는 원격 데이터 원본을 호출하는 것이 아니라 단순히 요청 매핑 템플릿의 결과를 응답 매핑 템플릿으로 **전달**할 뿐입니다. 필드 해석은 AWS AppSync에서 이루어집니다.

로컬 해석기는 여러 사용 사례에서 유용합니다. 가장 널리 알려진 사용 사례는 데이터 원본 호출을 트리거하지 않고 알림을 게시하는 경우입니다. 이 사용 사례를 설명하기 위해 사용자가 서로 간에 페이징할 수 있는 페이징 애플리케이션을 빌드해 보겠습니다. 이 예제에서는 *Subscriptions*를 활용하므로, *Subscriptions*에 대해 잘 모르는 경우 [실시간 데이터](aws-appsync-real-time-data.md) 자습서에 따라 실행할 수도 있습니다.

## 페이징 애플리케이션 생성
<a name="create-the-paging-application"></a>

페이징 애플리케이션에서 클라이언트는 받은 편지함을 구독하고 페이지를 다른 클라이언트로 보낼 수 있습니다. 각 페이지에는 메시지가 포함됩니다. 스키마는 다음과 같습니다.

```
schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}

type Subscription {
    inbox(to: String!): Page
    @aws_subscribe(mutations: ["page"])
}

type Mutation {
    page(body: String!, to: String!): Page!
}

type Page {
    from: String
    to: String!
    body: String!
    sentAt: String!
}

type Query {
    me: String
}
```

`Mutation.page` 필드에 해석기를 연결해 보십시오. **Schema(스키마)** 창의 오른쪽 패널에서 필드 정의 옆에 있는 *Attach Resolver(해석기 연결)*를 클릭합니다. *None(없음)* 유형의 새 데이터 원본을 생성하고 이름을 *PageDataSource*로 지정합니다.

요청 매핑 템플릿 창에서 다음을 입력합니다.

```
{
  "version": "2017-02-28",
  "payload": {
    "body": $util.toJson($context.arguments.body),
    "from": $util.toJson($context.identity.username),
    "to":  $util.toJson($context.arguments.to),
    "sentAt": "$util.time.nowISO8601()"
  }
}
```

응답 매핑 템플릿에서는 기본값인 *Forward the result(결과 전달)*를 선택합니다. 해석기를 저장합니다. 이제 애플리케이션이 준비되었으며 페이징하십시오\$1

## 페이지 전송 및 구독
<a name="send-and-subscribe-to-pages"></a>

클라이언트에서 페이지를 수신하도록 하려면 먼저 클라이언트가 받은 편지함을 구독해야 합니다.

**Queries(쿼리)** 창에서 `inbox` 구독을 실행하십시오.

```
subscription Inbox {
    inbox(to: "Nadia") {
        body
        to
        from
        sentAt
    }
}
```

 *Nadia*는 `Mutation.page` 뮤테이션이 호출될 때마다 페이지를 수신합니다. 변형을 실행하여 변형을 호출해 보겠습니다.

```
mutation Page {
    page(to: "Nadia", body: "Hello, World!") {
        body
        to
        from
        sentAt
    }
}
```

AWS AppSync를 나가지 않고 간단히 페이지를 전송하고 수신해 봄으로써 로컬 해석기 사용을 보여드렸습니다.

# 에서 GraphQL 해석기 결합 AWS AppSync
<a name="tutorial-combining-graphql-resolvers"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

GraphQL 스키마의 해석기와 필드는 매우 뛰어난 유연성으로 1:1 관계를 갖습니다. 데이터 원본이 스키마와 독립적으로 해석기에 구성되어 있으므로, 스키마를 요구에 가장 잘 맞게 짜맞춰서 다양한 데이터 원본을 통해 GraphQL 유형을 해석하거나 조작할 수 있습니다.

다음 예제 시나리오는 스키마에서 데이터 소스를 혼합하고 일치시키는 방법을 보여줍니다. 시작하기 전에 이전 자습서에 설명된 대로 AWS Lambda Amazon DynamoDB 및 Amazon OpenSearch Service에 대한 데이터 소스 및 해석기를 설정하는 방법을 숙지하는 것이 좋습니다.

## 스키마 예제
<a name="example-schema"></a>

다음 스키마에는 `Post` 작업 3개와 `Query` 작업 3개가 정의된 `Mutation` 유형이 있습니다.

```
type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    version: Int!
}

type Query {
    allPost: [Post]
    getPost(id: ID!): Post
    searchPosts: [Post]
}

type Mutation {
    addPost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String
    ): Post
    updatePost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String,
        ups: Int!,
        downs: Int!,
        expectedVersion: Int!
    ): Post
    deletePost(id: ID!): Post
}
```

이 예제에서는 총 6개 해석기를 연결하게 됩니다. 한 가지 가능한 방법은 [DynamoDB 해석기 매핑 템플릿 참조](resolver-mapping-template-reference-dynamodb.md#aws-appsync-resolver-mapping-template-reference-dynamodb)에 설명된 대로 이 모든 것을 `Posts`라고 하는 Amazon DynamoDB 테이블에서 가져와서 `AllPosts`가 스캔을 실행하고 `searchPosts`가 쿼리를 실행하도록 하는 것입니다. 하지만 Lambda 또는 OpenSearch Service를 통해 이러한 GraphQL 쿼리를 해석하는 등 사용자의 비즈니스 요구에 맞는 다른 방법도 있습니다.

## 해석기를 통해 데이터 변경
<a name="alter-data-through-resolvers"></a>

DynamoDB(또는 Amazon Aurora) 같은 데이터베이스의 결과를 클라이언트로 반환할 때 몇 가지 속성을 변경한 상태로 반환해야 할 수도 있습니다. 클라이언트의 타임스탬프 차이 같은 데이터 형식의 포맷팅이나 이전 버전과의 호환성 문제를 처리하기 위한 것이 이유일 수 있습니다. 설명을 돕기 위해, 다음 예제에서 AWS Lambda 함수는 GraphQL 해석기가 호출될 때마다 난수를 할당하여 블로그 게시물에 대한 좋아요/싫어요 평가를 다룹니다.

```
'use strict';
const doc = require('dynamodb-doc');
const dynamo = new doc.DynamoDB();

exports.handler = (event, context, callback) => {
    const payload = {
        TableName: 'Posts',
        Limit: 50,
        Select: 'ALL_ATTRIBUTES',
    };

    dynamo.scan(payload, (err, data) => {
        const result = { data: data.Items.map(item =>{
            item.ups = parseInt(Math.random() * (50 - 10) + 10, 10);
            item.downs = parseInt(Math.random() * (20 - 0) + 0, 10);
            return item;
        }) };
        callback(err, result.data);
    });
};
```

이 함수는 완벽하게 유효한 Lambda 함수로 GraphQL 스키마의 `AllPosts` 필드에 연결하여 모든 결과에서 반환하는 쿼리가 좋아요/싫어요에 대한 난수를 가져오도록 합니다.

## DynamoDB 및 OpenSearch Service
<a name="ddb-and-es"></a>

일부 애플리케이션의 경우 DynamoDB에 대한 단순 조회 쿼리와 변형을 수행할 수 있으며, 백그라운드 프로세스를 통해 문서를 OpenSearch Service로 전송할 수 있습니다. 그런 다음 단순히 `searchPosts` 해석기를 OpenSearch Service 데이터 소스에 연결하고 GraphQL 쿼리를 사용하여 (DynamoDB에서 가져온 데이터의) 검색 결과를 반환할 수 있습니다. 이 기능은 키워드, 퍼지 워드 일치 또는 지역 검색 조회 등 애플리케이션에 고급 검색 작업 추가 시 매우 유용할 수 있습니다. DynamoDB에서 데이터 전송은 ETL 프로세스를 통해 수행하거나 Lambda을 사용하여 DynamoDB에서 스트리밍할 수 있습니다. AWS 계정의 미국 서부 2(오레곤) 리전에서 다음 AWS CloudFormation 스택을 사용하여 전체 예제를 시작할 수 있습니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/multipledatasource/appsyncesdbstream.yml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/multipledatasource/appsyncesdbstream.yml) 

이 예제의 스키마를 통해 다음과 같이 DynamoDB 해석기를 사용하여 게시물을 추가할 수 있습니다.

```
mutation add {
    putPost(author:"Nadia"
        title:"My first post"
        content:"This is some test content"
        url:"https://aws.amazon.com/appsync/"
    ){
        id
        title
    }
}
```

이렇게 하면 DynamoDB에 데이터가 기록되고, Lambda를 통해 Amazon OpenSearch Service로 데이터를 스트리밍합니다. 그러면 다양한 필드로 모든 게시물을 검색할 수 있습니다. 예를 들면 데이터가 Amazon OpenSearch Service에 있으므로 다음과 같이 공백으로만 이루어졌더라도 자유 형식의 텍스트를 사용하여 작성자나 콘텐츠 필드를 검색할 수 있습니다.

```
query searchName{
    searchAuthor(name:"   Nadia   "){
        id
        title
        content
    }
}

query searchContent{
    searchContent(text:"test"){
        id
        title
        content
    }
}
```

데이터가 DynamoDB에 직접 기록되므로 `allPosts{...}` 및 `singlePost{...}` 쿼리를 사용하여 테이블에 대해 효율적인 목록이나 항목 조회 작업을 수행할 수 있습니다. 이 스택은 DynamoDB 스트림에 대해 다음 예제 코드를 사용합니다.

 **참고:**: 이 코드는 예제일 뿐입니다.

```
var AWS = require('aws-sdk');
var path = require('path');
var stream = require('stream');

var esDomain = {
    endpoint: 'https://opensearch-domain-name.REGION.es.amazonaws.com',
    region: 'REGION',
    index: 'id',
    doctype: 'post'
};

var endpoint = new AWS.Endpoint(esDomain.endpoint)
var creds = new AWS.EnvironmentCredentials('AWS');

function postDocumentToES(doc, context) {
    var req = new AWS.HttpRequest(endpoint);

    req.method = 'POST';
    req.path = '/_bulk';
    req.region = esDomain.region;
    req.body = doc;
    req.headers['presigned-expires'] = false;
    req.headers['Host'] = endpoint.host;

    // Sign the request (Sigv4)
    var signer = new AWS.Signers.V4(req, 'es');
    signer.addAuthorization(creds, new Date());

    // Post document to ES
    var send = new AWS.NodeHttpClient();
    send.handleRequest(req, null, function (httpResp) {
        var body = '';
        httpResp.on('data', function (chunk) {
            body += chunk;
        });
        httpResp.on('end', function (chunk) {
            console.log('Successful', body);
            context.succeed();
        });
    }, function (err) {
        console.log('Error: ' + err);
        context.fail();
    });
}

exports.handler = (event, context, callback) => {
    console.log("event => " + JSON.stringify(event));
    var posts = '';

    for (var i = 0; i < event.Records.length; i++) {
        var eventName = event.Records[i].eventName;
        var actionType = '';
        var image;
        var noDoc = false;
        switch (eventName) {
            case 'INSERT':
                actionType = 'create';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'MODIFY':
                actionType = 'update';
                image = event.Records[i].dynamodb.NewImage;
                break;
            case 'REMOVE':
            actionType = 'delete';
                image = event.Records[i].dynamodb.OldImage;
                noDoc = true;
                break;
        }

        if (typeof image !== "undefined") {
            var postData = {};
            for (var key in image) {
                if (image.hasOwnProperty(key)) {
                    if (key === 'postId') {
                        postData['id'] = image[key].S;
                    } else {
                        var val = image[key];
                        if (val.hasOwnProperty('S')) {
                            postData[key] = val.S;
                        } else if (val.hasOwnProperty('N')) {
                            postData[key] = val.N;
                        }
                    }
                }
            }

            var action = {};
            action[actionType] = {};
            action[actionType]._index = 'id';
            action[actionType]._type = 'post';
            action[actionType]._id = postData['id'];
            posts += [
                JSON.stringify(action),
            ].concat(noDoc?[]:[JSON.stringify(postData)]).join('\n') + '\n';
        }
    }
    console.log('posts:',posts);
    postDocumentToES(posts, context);
};
```

DynamoDB 스트림을 사용하여 `id`를 기본 키로 하는 DynamoDB 테이블에 이 부분을 연결하면, 원본에 대한 모든 변경 내용이 OpenSearch Service 도메인으로 스트리밍됩니다. 이 구성에 대한 자세한 내용은 [DynamoDB Streams 설명서](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html)를 참조하십시오.

# AWS AppSync에서 DynamoDB 배치 작업 사용
<a name="tutorial-dynamodb-batch"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

AWS AppSync는 단일 리전에 있는 하나 이상의 테이블에서 Amazon DynamoDB 배치 작업 사용을 지원합니다. 지원되는 작업은 `BatchGetItem`, `BatchPutItem` 및 `BatchDeleteItem`입니다. AWS AppSync의 이러한 기능을 사용하면 다음과 같은 작업을 수행할 수 있습니다.
+ 단일 쿼리에서 키 목록을 전달하고 테이블의 결과 반환
+ 단일 쿼리의 하나 이상의 테이블에서 레코드 읽기
+ 하나 이상의 테이블에 대량으로 레코드 쓰기
+ 관계가 있을 수 있는 여러 테이블에서 조건부로 레코드 쓰기 또는 삭제

DynamoDB in AWS AppSync에서 배치 작업을 사용하는 것은 백엔드 작업 및 테이블 구조에 대한 약간의 추가 사고와 지식이 필요한 고급 기법입니다. 또한 AWS AppSync의 배치 작업은 배치 처리되지 않은 작업과 두 가지 주요 차이점이 있습니다.
+ 데이터 원본 역할에 해석기가 액세스할 모든 테이블에 대한 권한이 있어야 합니다.
+ 해석기에 대한 테이블 사양이 매핑 템플릿의 일부입니다.

## 권한
<a name="permissions"></a>

다른 해석기와 마찬가지로 AWS AppSync에서 데이터 소스를 생성하고 역할을 생성하거나 기존 소스를 사용해야 합니다. 배치 작업에는 DynamoDB 테이블에 대한 여러 권한이 필요하기 때문에 읽기 또는 쓰기 작업에 대해 구성된 역할 권한을 부여해야 합니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME",
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME/*"
            ]
        }
    ]
}
```

------

 **참고**: 역할은 AWS AppSync의 데이터 소스에 연결되며 필드의 해석기는 데이터 소스에 대해 호출됩니다. DynamoDB에 대해 가져오도록 구성된 데이터 소스에는 구성을 간단하기 유지하기 위해 지정된 테이블이 하나뿐입니다. 따라서 단일 해석기에서 여러 테이블에 대해 배치 작업을 수행하는 경우(고급 작업임) 데이터 원본 액세스에 대한 역할을 해석기가 상호 작용하는 모든 테이블에 부여해야 합니다. IAM 정책의 **리소스** 필드에서 부여할 수 있습니다. 배치 호출을 수행하려는 테이블의 구성은 해석기 템플릿에서 수행되는데, 이 내용은 아래에서 설명합니다.

## 데이터 원본
<a name="data-source"></a>

간단하게 설명하기 위해 이 자습서에서 사용되는 모든 해석기에 대해 동일한 데이터 원본을 사용합니다. **데이터 소스** 탭에서 새 DynamoDB 데이터 소스를 생성하고 **BatchTutorial**이라고 이름을 지정합니다. 테이블 이름은 배치 작업을 위한 요청 매핑 템플릿의 일부로 지정되므로 어떤 것이든 지정할 수 있습니다. 테이블의 이름을 `empty`라고 지정하겠습니다.

이 자습서의 경우 다음 인라인 정책이 적용된 역할이 모두 작동합니다.

## 단일 테이블 배치
<a name="single-table-batch"></a>

**주의**  
충돌 감지 및 해결과 함께 사용할 때는 `BatchPutItem` 및 `BatchDeleteItem`가 지원되지 않습니다. 오류가 발생하지 않도록 해당 설정을 비활성화해야 합니다.

이 예에서는 배치 작업을 통해 항목을 추가 및 제거하려는 **Posts**라는 단일 테이블이 있다고 가정합니다. 다음 스키마를 사용하는데, 쿼리를 위해 ID 목록을 전달합니다.

```
type Post {
    id: ID!
    title: String
}

input PostInput {
    id: ID!
    title: String
}

type Query {
    batchGet(ids: [ID]): [Post]
}

type Mutation {
    batchAdd(posts: [PostInput]): [Post]
    batchDelete(ids: [ID]): [Post]
}

schema {
    query: Query
    mutation: Mutation
}
```

다음 **요청 매핑 템플릿**을 사용하여 `batchAdd()` 필드에 해석기를 첨부합니다. 이렇게 하면 GraphQL `input PostInput` 형식의 각 항목을 자동으로 받아 `BatchPutItem` 작업에 필요한 맵을 빌드합니다.

```
#set($postsdata = [])
#foreach($item in ${ctx.args.posts})
    $util.qr($postsdata.add($util.dynamodb.toMapValues($item)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        "Posts": $utils.toJson($postsdata)
    }
}
```

이 경우 **응답 매핑 템플릿**은 간단한 패스스루인데, 테이블 이름이 다음과 같이 컨텍스트 객체에 `..data.Posts`로 추가됩니다.

```
$util.toJson($ctx.result.data.Posts)
```

이제 AWS AppSync 콘솔의 **쿼리** 페이지로 이동하여 다음 **batchAdd** 변형을 실행합니다.

```
mutation add {
    batchAdd(posts:[{
            id: 1 title: "Running in the Park"},{
            id: 2 title: "Playing fetch"
        }]){
            id
            title
    }
}
```

결과가 화면에 출력되어야 하며 DynamoDB 콘솔을 통해 두 값이 **Posts** 테이블에 기록되었는지 독립적으로 확인할 수 있습니다.

다음으로, 다음 **요청 매핑 템플릿**을 사용하여 `batchGet()` 필드에 해석기를 첨부합니다. 이렇게 하면 GraphQL `ids:[]` 형식의 각 항목을 자동으로 받아 `BatchGetItem` 작업에 필요한 맵을 빌드합니다.

```
#set($ids = [])
#foreach($id in ${ctx.args.ids})
    #set($map = {})
    $util.qr($map.put("id", $util.dynamodb.toString($id)))
    $util.qr($ids.add($map))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchGetItem",
    "tables" : {
        "Posts": {
            "keys": $util.toJson($ids),
            "consistentRead": true,
            "projection" : {
                "expression" : "#id, title",
                "expressionNames" : { "#id" : "id"}
                }
        }
    }
}
```

이 경우 **응답 매핑 템플릿**은 다시 간단한 패스스루인데, 테이블 이름이 다시 컨텍스트 객체에 `..data.Posts`로 추가됩니다.

```
$util.toJson($ctx.result.data.Posts)
```

이제 AWS AppSync 콘솔의 **쿼리** 페이지로 돌아가서 다음 **batchGet 쿼리**를 실행합니다.

```
query get {
    batchGet(ids:[1,2,3]){
        id
        title
    }
}
```

앞서 추가한 두 `id` 값에 대한 결과를 반환해야 합니다. `null` 값은 값이 `id`인 `3`에 대해 반환되었습니다. 이는 **Posts** 테이블에 해당 값을 가진 레코드가 아직 없기 때문입니다. 또한 AWS AppSync는 쿼리에 전달된 키와 동일한 순서로 결과를 반환합니다. 이는 AWS AppSync가 사용자를 대신하여 수행하는 추가 기능입니다. `batchGet(ids:[1,3,2)`로 전환하면 순서가 바뀝니다. 또한 어떤 `id`가 `null` 값을 반환하는지 알게 됩니다.

마지막으로, 다음 **요청 매핑 템플릿**을 사용하여 `batchDelete()` 필드에 해석기를 첨부합니다. 이렇게 하면 GraphQL `ids:[]` 형식의 각 항목을 자동으로 받아 `BatchGetItem` 작업에 필요한 맵을 빌드합니다.

```
#set($ids = [])
#foreach($id in ${ctx.args.ids})
    #set($map = {})
    $util.qr($map.put("id", $util.dynamodb.toString($id)))
    $util.qr($ids.add($map))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchDeleteItem",
    "tables" : {
        "Posts": $util.toJson($ids)
    }
}
```

이 경우 **응답 매핑 템플릿**은 다시 간단한 패스스루인데, 테이블 이름이 다시 컨텍스트 객체에 `..data.Posts`로 추가됩니다.

```
$util.toJson($ctx.result.data.Posts)
```

이제 AWS AppSync 콘솔의 **쿼리** 페이지로 돌아가서 다음 **batchDelete** 변형을 실행합니다.

```
mutation delete {
    batchDelete(ids:[1,2]){ id }
}
```

이제 `id` `1` 및 `2`인 레코드가 삭제되어야 합니다. 앞에서 `batchGet()` 쿼리를 다시 실행하면 `null`을 반환해야 합니다.

## 다중 테이블 배치
<a name="multi-table-batch"></a>

**주의**  
충돌 감지 및 해결과 함께 사용할 때는 `BatchPutItem` 및 `BatchDeleteItem`가 지원되지 않습니다. 오류가 발생하지 않도록 해당 설정을 비활성화해야 합니다.

또한AWS AppSync를 사용하면 테이블 간에 배치 작업을 수행할 수 있습니다. 더 복잡한 애플리케이션을 빌드해 보겠습니다. 센서가 애완동물의 위치와 신체 온도를 보고하는 애완동물 건강 앱을 빌드한다고 가정해 보겠습니다. 센서는 배터리로 구동되며 몇 분마다 네트워크에 연결을 시도합니다. 센서가 연결을 설정하면 측정값을 AWS AppSync API로 전송합니다. 그런 다음 트리거가 데이터를 분석해 애완동물 주인에게 대시보드가 제공됩니다. 센서와 백엔드 데이터 스토어 간의 상호 작용에 대해 중점적으로 설명해 보겠습니다.

전제 조건으로 먼저 두 개의 DynamoDB 테이블을 만들어 보겠습니다. **locationReadings**는 센서 위치 판독값을 저장하고 **temperatureReadings**는 센서 온도 판독값을 저장할 것입니다. 두 테이블은 동일한 기본 키 구조를 공유하는데, `sensorId (String)`는 파티션 키이고, `timestamp (String)`는 정렬 키입니다.

다음 GraphQL 스키마를 사용해 보겠습니다.

```
type Mutation {
    # Register a batch of readings
    recordReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
    # Delete a batch of readings
    deleteReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
}

type Query {
    # Retrieve all possible readings recorded by a sensor at a specific time
    getReadings(sensorId: ID!, timestamp: String!): [SensorReading]
}

type RecordResult {
    temperatureReadings: [TemperatureReading]
    locationReadings: [LocationReading]
}

interface SensorReading {
    sensorId: ID!
    timestamp: String!
}

# Sensor reading representing the sensor temperature (in Fahrenheit)
type TemperatureReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    value: Float
}

# Sensor reading representing the sensor location (lat,long)
type LocationReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    lat: Float
    long: Float
}

input TemperatureReadingInput {
    sensorId: ID!
    timestamp: String
    value: Float
}

input LocationReadingInput {
    sensorId: ID!
    timestamp: String
    lat: Float
    long: Float
}
```

### BatchPutItem - 센서 판독값 기록
<a name="batchputitem-recording-sensor-readings"></a>

센서는 인터넷에 연결되면 판독값을 전송할 수 있어야 합니다. GraphQL 필드 `Mutation.recordReadings`가 센서가 판독값을 전송하는 데 사용할 API입니다. API를 실행하기 위해 해석기를 연결해 보겠습니다.

`Mutation.recordReadings` 필드 옆의 **첨부**를 선택합니다. 다음 화면에서 자습서 시작 부분에서 생성한 것과 같은 `BatchTutorial` 데이터 원본을 선택합니다.

다음 요청 매핑 템플릿을 추가해 보겠습니다.

 **요청 매핑 템플릿** 

```
## Convert tempReadings arguments to DynamoDB objects
#set($tempReadings = [])
#foreach($reading in ${ctx.args.tempReadings})
    $util.qr($tempReadings.add($util.dynamodb.toMapValues($reading)))
#end

## Convert locReadings arguments to DynamoDB objects
#set($locReadings = [])
#foreach($reading in ${ctx.args.locReadings})
    $util.qr($locReadings.add($util.dynamodb.toMapValues($reading)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        "locationReadings": $utils.toJson($locReadings),
        "temperatureReadings": $utils.toJson($tempReadings)
    }
}
```

보시다시피 `BatchPutItem` 작업을 통해 여러 테이블을 지정할 수 있습니다.

다음 응답 매핑 템플릿을 사용해 보겠습니다.

 **응답 매핑 템플릿** 

```
## If there was an error with the invocation
## there might have been partial results
#if($ctx.error)
    ## Append a GraphQL error for that field in the GraphQL response
    $utils.appendError($ctx.error.message, $ctx.error.message)
#end
## Also returns data for the field in the GraphQL response
$utils.toJson($ctx.result.data)
```

배치 작업을 사용하면 호출 시 오류 및 결과가 둘 다 반환될 수 있습니다. 이러한 경우 몇 가지 오류 처리를 작업을 자유롭게 수행할 수 있습니다.

 **참고**: `$utils.appendError()` 사용은 `$util.error()`와 유사하지만 매핑 템플릿 평가를 중단하지 않는다는 큰 차이점이 있습니다. 대신 필드에 오류가 있다는 신호를 보내지만 템플릿이 계속해서 평가되도록 하고 이어서 호출자에게 데이터를 반환합니다. 애플리케이션이 일부 결과를 반환해야 하는 경우에는 `$utils.appendError()`를 사용하는 것이 좋습니다.

해석기를 저장하고 AWS AppSync 콘솔의 **쿼리** 페이지로 이동합니다. 센서 판독값을 몇 개 전송해 보겠습니다\$1

다음 변형을 실행합니다.

```
mutation sendReadings {
  recordReadings(
    tempReadings: [
      {sensorId: 1, value: 85.5, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, value: 85.7, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, value: 85.8, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, value: 84.2, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, value: 81.5, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]
    locReadings: [
      {sensorId: 1, lat: 47.615063, long: -122.333551, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, lat: 47.615163, long: -122.333552, timestamp: "2018-02-01T17:21:06.000+08:00"}
      {sensorId: 1, lat: 47.615263, long: -122.333553, timestamp: "2018-02-01T17:21:07.000+08:00"}
      {sensorId: 1, lat: 47.615363, long: -122.333554, timestamp: "2018-02-01T17:21:08.000+08:00"}
      {sensorId: 1, lat: 47.615463, long: -122.333555, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

하나의 변형으로 센서 판독값 10개를 전송했고, 판독값은 테이블 2개로 분할되어 있었습니다. DynamoDB 콘솔을 사용하여 데이터가 **locationReadings** 및 **temperatureReadings** 테이블 모두에 표시되는지 확인합니다.

### BatchDeleteItem - 센서 판독값 삭제
<a name="batchdeleteitem-deleting-sensor-readings"></a>

마찬가지로 센서 판독값의 배치를 삭제해야 합니다. 이를 위해 `Mutation.deleteReadings` GraphQL 필드를 사용해 보겠습니다. `Mutation.recordReadings` 필드 옆의 **첨부**를 선택합니다. 다음 화면에서 자습서 시작 부분에서 생성한 것과 같은 `BatchTutorial` 데이터 원본을 선택합니다.

다음 요청 매핑 템플릿을 사용해 보겠습니다.

 **요청 매핑 템플릿** 

```
## Convert tempReadings arguments to DynamoDB primary keys
#set($tempReadings = [])
#foreach($reading in ${ctx.args.tempReadings})
    #set($pkey = {})
    $util.qr($pkey.put("sensorId", $reading.sensorId))
    $util.qr($pkey.put("timestamp", $reading.timestamp))
    $util.qr($tempReadings.add($util.dynamodb.toMapValues($pkey)))
#end

## Convert locReadings arguments to DynamoDB primary keys
#set($locReadings = [])
#foreach($reading in ${ctx.args.locReadings})
    #set($pkey = {})
    $util.qr($pkey.put("sensorId", $reading.sensorId))
    $util.qr($pkey.put("timestamp", $reading.timestamp))
    $util.qr($locReadings.add($util.dynamodb.toMapValues($pkey)))
#end

{
    "version" : "2018-05-29",
    "operation" : "BatchDeleteItem",
    "tables" : {
        "locationReadings": $utils.toJson($locReadings),
        "temperatureReadings": $utils.toJson($tempReadings)
    }
}
```

이 응답 매핑 템플릿은 `Mutation.recordReadings`에 사용한 것과 동일합니다.

 **응답 매핑 템플릿** 

```
## If there was an error with the invocation
## there might have been partial results
#if($ctx.error)
    ## Append a GraphQL error for that field in the GraphQL response
    $utils.appendError($ctx.error.message, $ctx.error.message)
#end
## Also return data for the field in the GraphQL response
$utils.toJson($ctx.result.data)
```

해석기를 저장하고 AWS AppSync 콘솔의 **쿼리** 페이지로 이동합니다. 이제 센서 판독값 두 개를 삭제해 보겠습니다\$1

다음 변형을 실행합니다.

```
mutation deleteReadings {
  # Let's delete the first two readings we recorded
  deleteReadings(
    tempReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]
    locReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

DynamoDB 콘솔을 사용하여 판독값 2개가 **locationReadings** 및 **temperatureReadings** 테이블에서 삭제되었는지 확인합니다.

### BatchGetItem - 판독값 검색
<a name="batchgetitem-retrieve-readings"></a>

애완동물 건강 앱의 또 다른 일반 작업은 특정 시점에 센서의 판독값을 검색하는 것입니다. 스키마의 `Query.getReadings` GraphQL 필드에 해석기를 연결해 보겠습니다. **연결**을 선택하고 다음 화면에서 자습서 시작 부분에서 생성한 것과 같은 `BatchTutorial` 데이터 원본을 선택합니다.

다음 요청 매핑 템플릿을 추가해 보겠습니다.

 **요청 매핑 템플릿** 

```
## Build a single DynamoDB primary key,
## as both locationReadings and tempReadings tables
## share the same primary key structure
#set($pkey = {})
$util.qr($pkey.put("sensorId", $ctx.args.sensorId))
$util.qr($pkey.put("timestamp", $ctx.args.timestamp))

{
    "version" : "2018-05-29",
    "operation" : "BatchGetItem",
    "tables" : {
        "locationReadings": {
            "keys": [$util.dynamodb.toMapValuesJson($pkey)],
            "consistentRead": true
        },
        "temperatureReadings": {
            "keys": [$util.dynamodb.toMapValuesJson($pkey)],
            "consistentRead": true
        }
    }
}
```

이제 **BatchGetItem** 작업을 사용합니다.

`SensorReading` 목록을 반환하도록 선택했으므로 응답 매핑 템플릿이 약간 다릅니다. 호출 결과를 원하는 모양으로 매핑해 보겠습니다.

 **응답 매핑 템플릿** 

```
## Merge locationReadings and temperatureReadings
## into a single list
## __typename needed as schema uses an interface
#set($sensorReadings = [])

#foreach($locReading in $ctx.result.data.locationReadings)
    $util.qr($locReading.put("__typename", "LocationReading"))
    $util.qr($sensorReadings.add($locReading))
#end

#foreach($tempReading in $ctx.result.data.temperatureReadings)
    $util.qr($tempReading.put("__typename", "TemperatureReading"))
    $util.qr($sensorReadings.add($tempReading))
#end

$util.toJson($sensorReadings)
```

해석기를 저장하고 AWS AppSync 콘솔의 **쿼리** 페이지로 이동합니다. 이제, 센서 판독값을 검색해 보겠습니다\$1

다음 쿼리를 실행합니다.

```
query getReadingsForSensorAndTime {
  # Let's retrieve the very first two readings
  getReadings(sensorId: 1, timestamp: "2018-02-01T17:21:06.000+08:00") {
    sensorId
    timestamp
    ...on TemperatureReading {
      value
    }
    ...on LocationReading {
      lat
      long
    }
  }
}
```

 AWS AppSync을 사용하여 DynamoDB 배치 작업을 사용하는 방법을 보여드렸습니다.

## 오류 처리
<a name="error-handling"></a>

 AWS AppSync에서 데이터 소스 작업은 때때로 부분적인 결과를 반환할 수 있습니다. 일부 결과는 작업의 출력이 일부 데이터와 오류로 구성된 경우를 나타내는 데 사용되는 용어입니다. 오류는 본질적으로 애플리케이션별로 처리되므로 AWS AppSync에서는 응답 매핑 템플릿에서 오류를 처리할 수 있는 기회를 제공합니다. 해석기 간접 호출 오류(있는 경우)는 컨텍스트에서 `$ctx.error`로 확인할 수 있습니다. 호출 오류에는 항상 `$ctx.error.message` 및 `$ctx.error.type` 속성으로 액세스할 수 있는 메시지와 유형이 포함되어 있습니다. 응답 매핑 템플릿 호출 중 다음과 같은 3가지 방법으로 일부 결과를 처리할 수 있습니다.

1. 데이터를 반환하여 호출 오류를 가립니다.

1. 응답 매핑 템플릿 평가를 중단해 오류를 나타냅니다(`$util.error(...)` 사용). 이 경우에는 데이터가 반환되지 않습니다.

1. 오류를 추가하고(`$util.appendError(...)` 사용) 데이터도 반환합니다.

DynamoDB 배치 작업을 사용해 위의 3가지 방법을 각각 시연해 보겠습니다\$1

### DynamoDB 배치 작업
<a name="dynamodb-batch-operations"></a>

DynamoDB 배치 작업을 사용하면 배치를 부분적으로 완료할 수 있습니다. 즉, 요청된 항목 또는 키 중 일부가 처리되지 않은 상태로 남아 있을 수 있습니다. AWS AppSync가 배치를 완료할 수 없는 경우 처리되지 않은 항목과 호출 오류가 컨텍스트에 설정됩니다.

이 자습서 이전 단원에서 사용한 `Query.getReadings` 작업의 `BatchGetItem` 필드 구성을 사용하여 오류 처리를 구현하겠습니다. 이때, `Query.getReadings` 필드를 실행하는 동안 `temperatureReadings` DynamoDB 테이블에 프로비저닝된 처리량이 부족하다고 가정해 보겠습니다. DynamoDB는 배치의 나머지 요소를 처리하기 위해 AWS AppSync에서 두 번째 시도에서 **ProvisionedThroughputExceededException**을 발생시켰습니다.

다음 JSON은 DynamoDB 배치 호출 이후, 응답 매핑 템플릿이 평가되기 전 직렬화된 컨텍스트를 나타냅니다.

```
{
  "arguments": {
    "sensorId": "1",
    "timestamp": "2018-02-01T17:21:05.000+08:00"
  },
  "source": null,
  "result": {
    "data": {
      "temperatureReadings": [
        null
      ],
      "locationReadings": [
        {
          "lat": 47.615063,
          "long": -122.333551,
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ]
    },
    "unprocessedKeys": {
      "temperatureReadings": [
        {
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ],
      "locationReadings": []
    }
  },
  "error": {
    "type": "DynamoDB:ProvisionedThroughputExceededException",
    "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
  },
  "outErrors": []
}
```

컨텍스트에 대해서 알아야 할 몇 가지가 있습니다.
+ 호출 오류가 `$ctx.error` by AWS AppSync의 컨텍스트에 설정되었고 오류 유형이 **DynamoDB:ProvisionedThroughputExceededException**으로 설정되었습니다.
+ 오류가 있더라도 결과는 `$ctx.result.data`에서 테이블당 매핑됩니다.
+ 처리되지 않고 남아 있는 키는 `$ctx.result.data.unprocessedKeys`에서 확인할 수 있습니다. 여기서 AWS AppSync는 테이블 처리량이 부족하여 키(sensorId:1, timestamp:2018-02-01T17:21:05.000\$108:00)로 항목을 검색할 수 없습니다.

 **참고**: `BatchPutItem`의 경우 `$ctx.result.data.unprocessedItems`입니다. `BatchDeleteItem`의 경우 `$ctx.result.data.unprocessedKeys`입니다.

이 오류를 세 가지 다른 방법으로 처리해 보겠습니다.

#### 1. 호출 오류 가리기
<a name="swallowing-the-invocation-error"></a>

호출 오류를 처리하지 않고 데이터를 반환하면 오류를 효과적으로 가려 주어진 GraphQL 필드에 대한 결과가 항상 성공입니다.

여기서 작성한 응답 매핑 템플릿은 알기 쉽고 결과 데이터에만 중점을 두고 있습니다.

응답 매핑 템플릿:

```
$util.toJson($ctx.result.data)
```

GraphQL 응답:

```
{
  "data": {
    "getReadings": [
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "lat": 47.615063,
        "long": -122.333551
      },
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  }
}
```

데이터에 대해서만 실행되었으므로 오류 응답에 오류가 추가되지 않습니다.

#### 2. 템플릿 실행 중단을 위한 오류 발생
<a name="raising-an-error-to-abort-the-template-execution"></a>

클라이언트의 관점에서 부분 실패를 전체 실패로 간주해야 하는 경우 데이터가 반환되지 않도록 템플릿 실행을 중단할 수 있습니다. `$util.error(...)` 유틸리티 메서드가 정확하게 이러한 동작을 수행합니다.

응답 매핑 템플릿:

```
## there was an error let's mark the entire field
## as failed and do not return any data back in the response
#if ($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end

$util.toJson($ctx.result.data)
```

GraphQL 응답:

```
{
  "data": {
    "getReadings": null
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

일부 결과가 DynamoDB 배치 작업에서 반환될 수 있긴 하지만 오류를 일으키기로 선택합니다. 그러면 `getReadings` GraphQL 필드는 null이고 GraphQL 응답 *오류* 블록에 오류가 추가됩니다.

#### 3. 오류를 추가하고 데이터 및 오류를 둘 다 반환
<a name="appending-an-error-to-return-both-data-and-errors"></a>

특정한 경우 사용자 경험을 개선하기 위해 애플리케이션에서는 일부 결과를 반환하고 클라이언트에게 처리되지 않은 항목이 있음을 알릴 수 있습니다. 그러면 클라이언트는 재시도를 수행하거나 오류를 최종 사용자에게 다시 번역할지 결정할 수 있습니다. `$util.appendError(...)`는 애플리케이션 디자이너가 템플릿 평가를 방해하지 않고 컨텍스트에 오류를 추가하도록 하여 이러한 동작을 가능하게 하는 유틸리티 메서드입니다. 템플릿을 평가한 후 AWS AppSync는 모든 컨텍스트 오류를 GraphQL 응답의 오류 블록에 추가하여 처리합니다.

응답 매핑 템플릿:

```
#if ($ctx.error)
    ## pass the unprocessed keys back to the caller via the `errorInfo` field
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end

$util.toJson($ctx.result.data)
```

GraphQL 응답의 오류 블록 내에서 호출 오류 및 처리되지 않은 키 요소를 전달합니다. 아래 응답에 표시된 것처럼 `getReadings` 필드 역시 **locationReadings** 테이블의 일부 데이터를 반환합니다.

GraphQL 응답:

```
{
  "data": {
    "getReadings": [
      null,
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

# AWS AppSync에서 DynamoDB 트랜잭션 수행
<a name="tutorial-dynamodb-transact"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

AWS AppSync는 단일 리전에 있는 하나 이상의 테이블에서 Amazon DynamoDB 트랜잭션 작업 사용을 지원합니다. 지원되는 작업은 `TransactGetItems`, `TransactWriteItems`입니다. AWS AppSync에서 이러한 기능을 사용하여 다음과 같은 작업을 수행할 수 있습니다.
+ 단일 쿼리에서 키 목록을 전달하고 테이블의 결과 반환
+ 단일 쿼리의 하나 이상의 테이블에서 레코드 읽기
+ 트랜잭션의 레코드를 하나 이상의 테이블에 전부 또는 전무 방식으로 기록
+ 일부 조건이 충족되면 트랜잭션 실행

## 권한
<a name="permissions"></a>

다른 해석기와 마찬가지로 AWS AppSync에서 데이터 소스를 생성하고 역할을 생성하거나 기존 소스를 사용해야 합니다. 트랜잭션 작업에는 DynamoDB 테이블에 대한 여러 권한이 필요하기 때문에 읽기 또는 쓰기 작업에 대해 구성된 역할 권한을 부여해야 합니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME",
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME/*"
            ]
        }
    ]
}
```

------

 **참고**: 역할은 AWS AppSync의 데이터 소스에 연결되며 필드의 해석기는 데이터 소스에 대해 호출됩니다. DynamoDB에 대해 가져오도록 구성된 데이터 소스에는 구성을 간단하기 유지하기 위해 지정된 테이블이 하나뿐입니다. 따라서 단일 해석기에서 여러 테이블에 대해 트랜잭션 작업을 수행하는 경우(고급 작업임) 데이터 원본 액세스에 대한 역할을 해석기가 상호 작용하는 모든 테이블에 부여해야 합니다. IAM 정책의 **리소스** 필드에서 부여할 수 있습니다. 테이블에 대한 트랜잭션 호출의 구성은 해석기 템플릿에서 수행되는데, 이 내용은 아래에서 설명합니다.

## 데이터 원본
<a name="data-source"></a>

간단하게 설명하기 위해 이 자습서에서 사용되는 모든 해석기에 대해 동일한 데이터 원본을 사용합니다. **데이터 소스** 탭에서 새 DynamoDB 데이터 소스를 생성하고 **TransactTutorial**이라고 이름을 지정합니다. 테이블 이름은 트랜잭션 작업을 위한 요청 매핑 템플릿의 일부로 지정되므로 어떤 것이든 지정할 수 있습니다. 테이블의 이름을 `empty`라고 지정하겠습니다.

`accountNumber`를 파티션 키로 사용하는 **savingAccounts** 및 **checkingAccounts**라는 두 개의 테이블과 `transactionId`를 파티션 키로 사용하는 **transactionHistory** 테이블이 있습니다.

이 자습서의 경우 다음 인라인 정책이 적용된 역할이 모두 작동합니다. `region` 및 `accountId`를 리전 및 계정 ID로 바꿉니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory/*"
            ]
        }
    ]
}
```

------

## 트랜잭션
<a name="transactions"></a>

이 예에서 컨텍스트는 전형적인 은행 거래이며 여기서 다음을 위해 `TransactWriteItems`를 사용합니다.
+ 저축 계좌에서 당좌 예금 계좌로 돈을 이체
+ 각 트랜잭션에 대한 새 트랜잭션 레코드 생성

그런 다음 `TransactGetItems`를 사용하여 저축 계좌와 당좌 예금 계좌의 세부 정보를 검색합니다.

**주의**  
충돌 감지 및 해결과 함께 사용할 때는 `TransactWriteItems`가 지원되지 않습니다. 오류가 발생하지 않도록 해당 설정을 비활성화해야 합니다.

GraphQL 스키마는 다음과 같이 정의합니다.

```
type SavingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type CheckingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type TransactionHistory {
    transactionId: ID!
    from: String
    to: String
    amount: Float
}

type TransactionResult {
    savingAccounts: [SavingAccount]
    checkingAccounts: [CheckingAccount]
    transactionHistory: [TransactionHistory]
}

input SavingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input CheckingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input TransactionInput {
    savingAccountNumber: String!
    checkingAccountNumber: String!
    amount: Float!
}

type Query {
    getAccounts(savingAccountNumbers: [String], checkingAccountNumbers: [String]): TransactionResult
}

type Mutation {
    populateAccounts(savingAccounts: [SavingAccountInput], checkingAccounts: [CheckingAccountInput]): TransactionResult
    transferMoney(transactions: [TransactionInput]): TransactionResult
}

schema {
    query: Query
    mutation: Mutation
}
```

### TransactWriteItems - 계좌 정보 채우기
<a name="transactwriteitems-populate-accounts"></a>

계좌 간에 돈을 이체하려면 테이블에 세부 정보를 채워 넣어야 합니다. 이를 위해 GraphQL 작업 `Mutation.populateAccounts`를 사용하겠습니다.

스키마 섹션에서 `Mutation.populateAccounts` 작업 옆에 있는 **연결**을 클릭합니다. VTL 유닛 해석기로 이동한 다음 동일한 `TransactTutorial` 데이터 소스를 선택합니다.

이제 다음 요청 매핑 템플릿을 사용합니다.

 **요청 매핑 템플릿** 

```
#set($savingAccountTransactPutItems = [])
#set($index = 0)
#foreach($savingAccount in ${ctx.args.savingAccounts})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($savingAccount.accountNumber)))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("username", $util.dynamodb.toString($savingAccount.username)))
    $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($savingAccount.balance)))
    #set($index = $index + 1)
    #set($savingAccountTransactPutItem = {"table": "savingAccounts",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($savingAccountTransactPutItems.add($savingAccountTransactPutItem))
#end

#set($checkingAccountTransactPutItems = [])
#set($index = 0)
#foreach($checkingAccount in ${ctx.args.checkingAccounts})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($checkingAccount.accountNumber)))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("username", $util.dynamodb.toString($checkingAccount.username)))
    $util.qr($attributeValues.put("balance", $util.dynamodb.toNumber($checkingAccount.balance)))
    #set($index = $index + 1)
    #set($checkingAccountTransactPutItem = {"table": "checkingAccounts",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($checkingAccountTransactPutItems.add($checkingAccountTransactPutItem))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountTransactPutItems))
$util.qr($transactItems.addAll($checkingAccountTransactPutItems))

{
    "version" : "2018-05-29",
    "operation" : "TransactWriteItems",
    "transactItems" : $util.toJson($transactItems)
}
```

그리고 다음 응답 매핑 템플릿을 사용합니다.

 **응답 매핑 템플릿** 

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..5])
    $util.qr($checkingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))

$util.toJson($transactionResult)
```

해석기를 저장하고 AWS AppSync 콘솔의 **쿼리** 섹션으로 이동하여 계정을 채웁니다.

다음 변형을 실행합니다.

```
mutation populateAccounts {
  populateAccounts (
    savingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 100},
      {accountNumber: "2", username: "Amy", balance: 90},
      {accountNumber: "3", username: "Lily", balance: 80},
    ]
    checkingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 70},
      {accountNumber: "2", username: "Amy", balance: 60},
      {accountNumber: "3", username: "Lily", balance: 50},
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
  }
}
```

하나의 변형으로 3개의 저축 계좌와 3개의 당좌 예금 계좌 정보를 채웠습니다.

DynamoDB 콘솔을 사용하여 데이터가 **savingAccounts** 및 **checkingAccounts** 테이블 모두에 표시되는지 확인합니다.

### TransactWriteItems - 송금
<a name="transactwriteitems-transfer-money"></a>

다음 **요청 매핑 템플릿**을 사용하여 `transferMoney` 뮤테이션에 해석기를 첨부합니다. `amounts`, `savingAccountNumbers` 및 `checkingAccountNumbers`의 값은 동일합니다.

```
#set($amounts = [])
#foreach($transaction in ${ctx.args.transactions})
    #set($attributeValueMap = {})
    $util.qr($attributeValueMap.put(":amount", $util.dynamodb.toNumber($transaction.amount)))
    $util.qr($amounts.add($attributeValueMap))
#end

#set($savingAccountTransactUpdateItems = [])
#set($index = 0)
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.savingAccountNumber)))
    #set($update = {})
    $util.qr($update.put("expression", "SET balance = balance - :amount"))
    $util.qr($update.put("expressionValues", $amounts[$index]))
    #set($index = $index + 1)
    #set($savingAccountTransactUpdateItem = {"table": "savingAccounts",
        "operation": "UpdateItem",
        "key": $keyMap,
        "update": $update})
    $util.qr($savingAccountTransactUpdateItems.add($savingAccountTransactUpdateItem))
#end

#set($checkingAccountTransactUpdateItems = [])
#set($index = 0)
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("accountNumber", $util.dynamodb.toString($transaction.checkingAccountNumber)))
    #set($update = {})
    $util.qr($update.put("expression", "SET balance = balance + :amount"))
    $util.qr($update.put("expressionValues", $amounts[$index]))
    #set($index = $index + 1)
    #set($checkingAccountTransactUpdateItem = {"table": "checkingAccounts",
        "operation": "UpdateItem",
        "key": $keyMap,
        "update": $update})
    $util.qr($checkingAccountTransactUpdateItems.add($checkingAccountTransactUpdateItem))
#end

#set($transactionHistoryTransactPutItems = [])
#foreach($transaction in ${ctx.args.transactions})
    #set($keyMap = {})
    $util.qr($keyMap.put("transactionId", $util.dynamodb.toString(${utils.autoId()})))
    #set($attributeValues = {})
    $util.qr($attributeValues.put("from", $util.dynamodb.toString($transaction.savingAccountNumber)))
    $util.qr($attributeValues.put("to", $util.dynamodb.toString($transaction.checkingAccountNumber)))
    $util.qr($attributeValues.put("amount", $util.dynamodb.toNumber($transaction.amount)))
    #set($transactionHistoryTransactPutItem = {"table": "transactionHistory",
        "operation": "PutItem",
        "key": $keyMap,
        "attributeValues": $attributeValues})
    $util.qr($transactionHistoryTransactPutItems.add($transactionHistoryTransactPutItem))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountTransactUpdateItems))
$util.qr($transactItems.addAll($checkingAccountTransactUpdateItems))
$util.qr($transactItems.addAll($transactionHistoryTransactPutItems))

{
    "version" : "2018-05-29",
    "operation" : "TransactWriteItems",
    "transactItems" : $util.toJson($transactItems)
}
```

한 번의 `TransactWriteItems` 작업으로 3건의 은행 거래를 하겠습니다. 다음 **응답 매핑 템플릿**을 사용합니다.

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..5])
    $util.qr($checkingAccounts.add(${ctx.result.keys[$index]}))
#end

#set($transactionHistory = [])
#foreach($index in [6..8])
    $util.qr($transactionHistory.add(${ctx.result.keys[$index]}))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))
$util.qr($transactionResult.put('transactionHistory', $transactionHistory))

$util.toJson($transactionResult)
```

이제 AWS AppSync 콘솔의 **쿼리** 섹션으로 이동하여 다음과 같이 **transferMoney** 뮤테이션을 실행합니다.

```
mutation write {
  transferMoney(
    transactions: [
      {savingAccountNumber: "1", checkingAccountNumber: "1", amount: 7.5},
      {savingAccountNumber: "2", checkingAccountNumber: "2", amount: 6.0},
      {savingAccountNumber: "3", checkingAccountNumber: "3", amount: 3.3}
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
    transactionHistory {
      transactionId
    }
  }
}
```

하나의 변형으로 2건의 은행 거래를 보냈습니다. DynamoDB 콘솔을 사용하여 데이터가 **savingAccounts**, **checkingAccounts** 및 **transactionHistory** 테이블에 표시되는지 확인합니다.

### TransactGetItems - 계좌 검색
<a name="transactgetitems-retrieve-accounts"></a>

한 번의 트랜잭션 요청으로 저축 계좌와 당좌 예금 계좌의 세부 정보를 검색하기 위해 스키마의 `Query.getAccounts` GraphQL 작업에 해석기를 연결하겠습니다. **연결**을 선택하고 VTL 유닛 해석기로 이동한 다음 화면에서 자습서 시작 부분에서 생성한 것과 같은 `TransactTutorial` 데이터 소스를 선택합니다. 다음과 같이 템플릿을 구성합니다.

 **요청 매핑 템플릿** 

```
#set($savingAccountsTransactGets = [])
#foreach($savingAccountNumber in ${ctx.args.savingAccountNumbers})
    #set($savingAccountKey = {})
    $util.qr($savingAccountKey.put("accountNumber", $util.dynamodb.toString($savingAccountNumber)))
    #set($savingAccountTransactGet = {"table": "savingAccounts", "key": $savingAccountKey})
    $util.qr($savingAccountsTransactGets.add($savingAccountTransactGet))
#end

#set($checkingAccountsTransactGets = [])
#foreach($checkingAccountNumber in ${ctx.args.checkingAccountNumbers})
    #set($checkingAccountKey = {})
    $util.qr($checkingAccountKey.put("accountNumber", $util.dynamodb.toString($checkingAccountNumber)))
    #set($checkingAccountTransactGet = {"table": "checkingAccounts", "key": $checkingAccountKey})
    $util.qr($checkingAccountsTransactGets.add($checkingAccountTransactGet))
#end

#set($transactItems = [])
$util.qr($transactItems.addAll($savingAccountsTransactGets))
$util.qr($transactItems.addAll($checkingAccountsTransactGets))

{
    "version" : "2018-05-29",
    "operation" : "TransactGetItems",
    "transactItems" : $util.toJson($transactItems)
}
```

 **응답 매핑 템플릿** 

```
#if ($ctx.error)
    $util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.cancellationReasons)
#end

#set($savingAccounts = [])
#foreach($index in [0..2])
    $util.qr($savingAccounts.add(${ctx.result.items[$index]}))
#end

#set($checkingAccounts = [])
#foreach($index in [3..4])
    $util.qr($checkingAccounts.add($ctx.result.items[$index]))
#end

#set($transactionResult = {})
$util.qr($transactionResult.put('savingAccounts', $savingAccounts))
$util.qr($transactionResult.put('checkingAccounts', $checkingAccounts))

$util.toJson($transactionResult)
```

해석기를 저장하고 AWS AppSync 콘솔의 **쿼리** 섹션으로 이동합니다. 저축 계좌와 당좌 예금 계좌를 검색하려면 다음 쿼리를 실행합니다.

```
query getAccounts {
  getAccounts(
    savingAccountNumbers: ["1", "2", "3"],
    checkingAccountNumbers: ["1", "2"]
  ) {
    savingAccounts {
      accountNumber
      username
      balance
    }
    checkingAccounts {
      accountNumber
      username
      balance
    }
  }
}
```

 AWS AppSync를 사용하여 DynamoDB 트랜잭션을 사용하는 방법을 보여드렸습니다.

# AWS AppSync에서 HTTP 해석기 사용
<a name="tutorial-http-resolvers"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

AWS AppSync를 사용하면 지원되는 데이터 소스(즉 AWS Lambda, Amazon DynamoDB, Amazon OpenSearch Service 또는 Amazon Aurora)를 사용하여 GraphQL 필드를 해결하기 위한 임의의 HTTP 엔드포인트 외에도 다양한 작업을 수행할 수 있습니다. HTTP 엔드포인트가 사용 가능해진 후에는 데이터 원본에 연결할 수 있습니다. 쿼리, 변형 및 구독 등 GraphQL 작업을 수행하도록 스키마의 해석기를 구성할 수 있습니다. 이 자습서에서는 몇 가지 일반적인 예제를 살펴봅니다.

이 자습서에서는 AWS AppSync GraphQL 엔드포인트에 REST API(Amazon API Gateway 및 Lambda를 사용하여 생성)를 사용합니다.

## 원클릭 설치
<a name="one-click-setup"></a>

HTTP 엔드포인트가 구성된 AWS AppSync에서 GraphQL 엔드포인트를 자동으로 설정하려면(Amazon API Gateway 및 Lambda 사용) 다음 AWS CloudFormation 템플릿을 사용할 수 있습니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-full.yaml)

## REST API 만들기
<a name="creating-a-rest-api"></a>

다음 AWS CloudFormation 템플릿을 사용하여이 자습서에서 작동하는 REST 엔드포인트를 설정할 수 있습니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml)

 AWS CloudFormation 스택은 다음 단계를 수행합니다.

1. 마이크로서비스에 대한 비즈니스 로직이 포함된 Lambda 함수를 설정합니다.

1. 다음 엔드포인트/메서드/콘텐츠 유형 조합으로 API Gateway REST API를 설정합니다.


****  

| API 리소스 경로 | HTTP 메서드 | 지원되는 콘텐츠 유형 | 
| --- | --- | --- | 
|  /v1/사용자  |  POST  |  application/json  | 
|  /v1/사용자  |  GET  |  application/json  | 
|  /v1/사용자/1  |  GET  |  application/json  | 
|  /v1/사용자/1  |  PUT  |  application/json  | 
|  /v1/사용자/1  |  DELETE  |  application/json  | 

## GraphQL API 생성
<a name="creating-your-graphql-api"></a>

 AWS AppSync에서 GraphQL API를 생성하려면:
+  AWS AppSync 콘솔을 열고 **API 생성을** 선택합니다.
+ API에 `UserData`를 입력합니다.
+ **사용자 지정 스키마**를 선택합니다.
+ **생성(Create)**을 선택합니다.

 AWS AppSync 콘솔은 API 키 인증 모드를 사용하여 새 GraphQL API를 생성합니다. 콘솔에서 GraphQL API의 나머지 부분을 설정하고 이 자습서의 나머지 부분에서 이 API에 대한 쿼리를 실행할 수 있습니다.

## GraphQL 스키마 생성
<a name="creating-a-graphql-schema"></a>

GraphQL API가 마련되었으면 GraphQL 스키마를 생성해 보십시오. AWS AppSync 콘솔의 스키마 편집기에서 스키마가 다음 스키마와 일치하는지 확인합니다.

```
schema {
    query: Query
    mutation: Mutation
}

type Mutation {
    addUser(userInput: UserInput!): User
    deleteUser(id: ID!): User
}

type Query {
    getUser(id: ID): User
    listUser: [User!]!
}

type User {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}

input UserInput {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}
```

## HTTP 데이터 원본 구성
<a name="configure-your-http-data-source"></a>

HTTP 데이터 원본을 구성하려면 다음을 수행하십시오.
+ **DataSources(데이터 원본)** 탭에서 **New(새로 만들기)**를 선택한 후, 데이터 원본에 대해 친숙한 이름(예: `HTTP`)을 입력합니다.
+ **Data source type(데이터 원본 형식)**에서 **HTTP**를 선택합니다.
+ 엔드포인트를 생성된 API Gateway 엔드포인트로 설정합니다. 단계 이름을 엔드포인트의 일부로 포함하지 않았는지 확인합니다.

 **참고:** 현재 AWS AppSync는 퍼블릭 엔드포인트만 지원합니다.

 **참고:** AWS AppSync 서비스에서 인식하는 인증 기관에 대한 자세한 내용은 [HTTPS 엔드포인트에 AWS AppSync 대해에서 인식하는 인증 기관(CA)을 참조하세요](http-cert-authorities.md#aws-appsync-http-certificate-authorities).

## 해석기 구성
<a name="configuring-resolvers"></a>

이 단계에서는 http 데이터 원본을 **getUser** 쿼리에 연결합니다.

 해석기를 설정하려면:
+ **스키마** 탭을 선택합니다.
+ 오른쪽에 있는 **Data types(데이터 형식)** 창의 **쿼리** 형식에서 **getUser** 필드를 찾은 다음 **연결**을 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **HTTP**를 선택합니다.
+ **Configure the request mapping template(요청 매핑 템플릿 구성)**에 다음 코드를 붙여 넣습니다.

```
{
    "version": "2018-05-29",
    "method": "GET",
    "params": {
        "headers": {
            "Content-Type": "application/json"
        }
    },
    "resourcePath": $util.toJson("/v1/users/${ctx.args.id}")
}
```
+ **Configure the response mapping template(응답 매핑 템플릿 구성)**에 다음 코드를 붙여 넣습니다.

```
## return the body
#if($ctx.result.statusCode == 200)
    ##if response is 200
    $ctx.result.body
#else
    ##if response is not 200, append the response to error block.
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```
+ **쿼리** 탭을 선택한 후, 다음 쿼리를 실행합니다.

```
query GetUser{
    getUser(id:1){
        id
        username
    }
}
```

그러면 다음과 같은 응답이 반환됩니다.

```
{
    "data": {
        "getUser": {
            "id": "1",
            "username": "nadia"
        }
    }
}
```
+ **스키마** 탭을 선택합니다.
+ 오른쪽에 있는 **Data types(데이터 형식)** 창의 **변형**에서 **addUser** 필드를 찾은 다음 **연결**을 선택합니다.
+ **Data source name(데이터 원본 이름)**에서 **HTTP**를 선택합니다.
+ **Configure the request mapping template(요청 매핑 템플릿 구성)**에 다음 코드를 붙여 넣습니다.

```
{
    "version": "2018-05-29",
    "method": "POST",
    "resourcePath": "/v1/users",
    "params":{
      "headers":{
        "Content-Type": "application/json",
      },
      "body": $util.toJson($ctx.args.userInput)
    }
}
```
+ **Configure the response mapping template(응답 매핑 템플릿 구성)**에 다음 코드를 붙여 넣습니다.

```
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
#end
## if the response status code is not 200, then return an error. Else return the body **
#if($ctx.result.statusCode == 200)
    ## If response is 200, return the body.
    $ctx.result.body
#else
    ## If response is not 200, append the response to error block.
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```
+ **쿼리** 탭을 선택한 후, 다음 쿼리를 실행합니다.

```
mutation addUser{
    addUser(userInput:{
        id:"2",
        username:"shaggy"
    }){
        id
        username
    }
}
```

그러면 다음과 같은 응답이 반환됩니다.

```
{
    "data": {
        "getUser": {
        "id": "2",
        "username": "shaggy"
        }
    }
}
```

## AWS 서비스 호출
<a name="invoking-aws-services"></a>

HTTP 해석기를 사용하여 AWS 서비스에 대한 GraphQL API 인터페이스를 설정할 수 있습니다. 가 보낸 사람을 AWS 식별할 수 있도록에 대한 HTTP 요청은 [서명 버전 4 프로세스](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)로 서명해야 AWS 합니다. AWS AppSync는 IAM 역할을 HTTP 데이터 소스와 연결할 때 사용자를 대신하여 서명을 계산합니다.

HTTP 해석기를 사용하여 AWS 서비스를 호출하기 위한 두 가지 추가 구성 요소를 제공합니다.
+  AWS 서비스 APIs를 호출할 권한이 있는 IAM 역할
+ 데이터 원본의 서명 구성

예를 들어 HTTP 해석기를 사용하여 [ListGraphqlApis 작업을](https://docs.aws.amazon.com/appsync/latest/APIReference/API_ListGraphqlApis.html) 호출하려면 먼저 다음 정책이 연결된 상태에서 AWS AppSync가 수임하는 [IAM 역할을 생성합니다](attaching-a-data-source.md#aws-appsync-getting-started-build-a-schema-from-scratch).

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "appsync:ListGraphqlApis"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
```

------

그런 다음 AWS AppSync용 HTTP 데이터 소스를 생성합니다. 이 예에서는 미국 서부(오레곤) 리전에서 AWS AppSync를 호출합니다. 서명 리전과 서비스 이름이 포함된 `http.json`이라는 파일에서 다음 HTTP 구성을 설정합니다.

```
{
    "endpoint": "https://appsync.us-west-2.amazonaws.com/",
    "authorizationConfig": {
        "authorizationType": "AWS_IAM",
        "awsIamConfig": {
            "signingRegion": "us-west-2",
            "signingServiceName": "appsync"
        }
    }
}
```

그런 다음 AWS CLI 를 사용하여 다음과 같이 연결된 역할이 있는 데이터 소스를 생성합니다.

```
aws appsync create-data-source --api-id <API-ID> \
                               --name AWSAppSync \
                               --type HTTP \
                               --http-config file:///http.json \
                               --service-role-arn <ROLE-ARN>
```

스키마의 필드에 해석기를 연결할 때 다음 요청 매핑 템플릿을 사용하여 AWS AppSync를 호출합니다.

```
{
    "version": "2018-05-29",
    "method": "GET",
    "resourcePath": "/v1/apis"
}
```

이 데이터 소스에 대해 GraphQL 쿼리를 실행하면 AWS AppSync는 사용자가 제공한 역할을 사용하여 요청에 서명하고 요청에 서명을 포함합니다. 쿼리는 해당 AWS 리전의 계정에 있는 AWS AppSync GraphQL APIs 목록을 반환합니다.

# 에서 Aurora Serverless v2 사용 AWS AppSync
<a name="tutorial-rds-resolvers"></a>

를 사용하여 GraphQL API를 Aurora Serverless 데이터베이스에 연결합니다 AWS AppSync. 이러한 통합 시 GraphQL 쿼리, 변형 및 구독을 통해 SQL 문을 실행할 수 있으므로 관계형 데이터와 유연하게 상호 작용할 수 있습니다.

**참고**  
이 자습서에서는 `US-EAST-1` 리전을 사용합니다.

**이점**
+ GraphQL과 관계형 데이터베이스 간의 원활한 통합
+ GraphQL 인터페이스를 통해 SQL 작업을 수행하는 기능
+ Aurora Serverless v2를 사용한 서버리스 확장성
+  AWS Secrets Manager를 통한 보안 데이터 액세스
+ 입력 삭제를 통한 SQL 인젝션 방지
+ 필터링 및 범위 작업을 포함한 유연한 쿼리 기능

**일반 사용 사례**
+ 관계형 데이터 요구 사항을 사용하여 확장 가능한 애플리케이션 구축
+ GraphQL 유연성과 SQL 데이터베이스 기능이 모두 필요한 API 생성
+ GraphQL 변형 및 쿼리를 통한 데이터 작업 관리
+ 보안 데이터베이스 액세스 패턴 구현

이 자습서에서는 다음을 배웁니다.
+ Aurora Serverless v2 클러스터 설정
+ 데이터 API 기능 활성화
+ 데이터베이스 구조 생성 및 구성
+ 데이터베이스 작업에 대한 GraphQL 스키마 정의
+ 쿼리 및 변형에 대한 해석기 구현
+ 적절한 입력 삭제를 통해 데이터 액세스 보호
+ GraphQL 인터페이스를 통해 다양한 데이터베이스 작업 실행

**Topics**
+ [데이터베이스 클러스터 설정](#create-cluster)
+ [데이터 API 활성화](#enable-data-api)
+ [데이터베이스 및 테이블 생성](#create-database-and-table)
+ [GraphQL 스키마](#graphql-schema)
+ [API를 데이터베이스 작업에 연결](#configuring-resolvers)
+ [API를 통해 데이터 수정](#run-mutations)
+ [데이터 검색](#run-queries)
+ [데이터 액세스 보호](#input-sanitization)

## 데이터베이스 클러스터 설정
<a name="create-cluster"></a>

Amazon RDS 데이터 소스를에 추가 AWS AppSync하기 전에 먼저 Aurora Serverless v2 클러스터에서 데이터 API를 활성화하고 **를 사용하여 보안 암호를 구성**해야 합니다*AWS Secrets Manager*. Aurora Serverless v2 클러스터를 생성하기 위해 AWS CLI를 사용할 수 있습니다.

```
aws rds create-db-cluster \
    --db-cluster-identifier appsync-tutorial \
    --engine aurora-mysql \
    --engine-version 8.0 \
    --serverless-v2-scaling-configuration MinCapacity=0,MaxCapacity=1 \
    --master-username USERNAME \
    --master-user-password COMPLEX_PASSWORD \
    --enable-http-endpoint
```

그러면 클러스터에 대한 ARN이 반환됩니다.

클러스터를 생성한 후 다음 명령을 사용하여 Aurora Serverless v2 인스턴스를 추가해야 합니다.

```
aws rds create-db-instance \
    --db-cluster-identifier appsync-tutorial \
    --db-instance-identifier appsync-tutorial-instance-1 \
    --db-instance-class db.serverless \
    --engine aurora-mysql
```

**참고**  
이러한 엔드포인트를 활성화하는 데 시간이 걸립니다. 클러스터의 **연결 및 보안** 탭에 있는 Amazon RDS 콘솔에서 상태를 확인할 수 있습니다. 다음 AWS CLI 명령을 사용하여 클러스터의 상태를 확인할 수도 있습니다.  

```
aws rds describe-db-clusters \
    --db-cluster-identifier appsync-tutorial \
    --query "DBClusters[0].Status"
```

 AWS Secrets Manager 콘솔을 사용하여 *보안* 암호를 생성하거나 AWS CLI 이전 단계의 `USERNAME` 및를 사용하여 다음과 같은 입력 파일을 사용하여 보안 암호를 생성할 수 `COMPLEX_PASSWORD` 있습니다.

```
{
    "username": "USERNAME",
    "password": "COMPLEX_PASSWORD"
}
```

이를 파라미터로 AWS CLI에 전달합니다.

```
aws secretsmanager create-secret --name HttpRDSSecret --secret-string file://creds.json --region us-east-1
```

그러면 암호에 대한 ARN이 반환됩니다.

 데이터 원본을 생성할 때 AppSync 콘솔에서 나중에 사용할 수 있도록 암호화 Aurora Serverless 클러스터의 **ARN을 적어 둡니다**.

## 데이터 API 활성화
<a name="enable-data-api"></a>

[RDS 문서의 지침에 따라](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) 클러스터에 대한 데이터 API를 활성화할 수 있습니다. AppSync 데이터 소스로 추가하기 전에 데이터 API를 활성화해야 합니다.

## 데이터베이스 및 테이블 생성
<a name="create-database-and-table"></a>

데이터 API를 활성화하면 AWS CLI에서 데이터 API가 `aws rds-data execute-statement` 명령과 작동하는지 확인할 수 있습니다. 그러면 AppSync API에 추가하기 전에 Aurora Serverless 클러스터가 올바르게 구성되도록 할 수 있습니다. 먼저 다음과 같이 `--sql` 파라미터를 사용하여 *TESTDB*라는 데이터베이스를 생성합니다.

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1"  \
--region us-east-1 --sql "create DATABASE TESTDB"
```

오류 없이 실행되면 *create table* 명령을 사용하여 테이블을 추가합니다.

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
 --schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \
 --region us-east-1 \
 --sql "create table Pets(id varchar(200), type varchar(200), price float)" --database "TESTDB"
```

모든 것이 문제 없이 실행되면 AppSync API에서 해당 클러스터를 데이터 소스로 추가하는 단계로 진행할 수 있습니다.

## GraphQL 스키마
<a name="graphql-schema"></a>

Aurora Serverless 데이터 API가 테이블과 함께 실행 중이므로 변형 및 구독을 수행하기 위해 GraphQL 스키마를 생성하고 해석기를 연결해 보겠습니다. AWS AppSync 콘솔에서 새 API를 생성하고 **스키마** 페이지로 이동하여 다음을 입력합니다.

```
type Mutation {
    createPet(input: CreatePetInput!): Pet
    updatePet(input: UpdatePetInput!): Pet
    deletePet(input: DeletePetInput!): Pet
}

input CreatePetInput {
    type: PetType
    price: Float!
}

input UpdatePetInput {
id: ID!
    type: PetType
    price: Float!
}

input DeletePetInput {
    id: ID!
}

type Pet {
    id: ID!
    type: PetType
    price: Float
}

enum PetType {
    dog
    cat
    fish
    bird
    gecko
}

type Query {
    getPet(id: ID!): Pet
    listPets: [Pet]
    listPetsByPriceRange(min: Float, max: Float): [Pet]
}

schema {
    query: Query
    mutation: Mutation
}
```

 스키마를 **저장**하고 **데이터 원본** 페이지로 이동해 새 데이터 원본을 생성합니다. 데이터 원본 유형으로 **관계형 데이터베이스**를 선택하고 기억하기 쉬운 이름을 입력합니다. 마지막 단계에서 생성한 데이터베이스 이름과 해당 데이터베이스를 생성한 **클러스터 ARN**을 사용합니다. **역할**의 경우 AppSync에서 새 역할을 생성하거나 아래와 유사한 정책을 사용해 역할을 하나 생성하도록 할 수 있습니다.

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "rds-data:BatchExecuteStatement",
                "rds-data:BeginTransaction",
                "rds-data:CommitTransaction",
                "rds-data:ExecuteStatement",
                "rds-data:RollbackTransaction"
            ],
            "Resource": [
                "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster",
                "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
            "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret",
            "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret:*"
            ]
        }
    ]
}
```

------

이 정책에는 역할 액세스 권한을 부여하는 **명령문**이 2개 있습니다. 첫 번째 **리소스**는 Aurora Serverless 클러스터이고 두 번째 리소스는 AWS Secrets Manager ARN입니다. **생성**을 클릭하기 전에 AppSync 데이터 소스 구성에서 두 ARN을 **모두** 제공해야 합니다.

이를 파라미터로에 전달합니다 AWS CLI.

```
aws secretsmanager create-secret \
  --name HttpRDSSecret \
  --secret-string file://creds.json \
  --region us-east-1
```

그러면 암호에 대한 ARN이 반환됩니다. AWS AppSync 콘솔에서 데이터 소스를 생성할 때 나중에 사용할 수 있도록 Aurora Serverless 클러스터의 ARN과 보안 암호를 기록해 둡니다.

### 데이터베이스 구조 구축
<a name="create-database-and-table"></a>

데이터 API를 활성화하면 AWS CLI에서 데이터 API가 `aws rds-data execute-statement` 명령과 작동하는지 확인할 수 있습니다. 이렇게 하면 AWS AppSync API에 추가하기 전에 Aurora Serverless v2 클러스터가 올바르게 구성됩니다. 먼저 다음과 같이 `--sql` 파라미터를 사용하여 *TESTDB*라는 데이터베이스를 생성합니다.

```
aws rds-data execute-statement \
                --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial" \
                --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret"  \
                --region us-east-1 \
                --sql "create DATABASE TESTDB"
```

오류 없이 실행되면 다음 *create table* 명령을 사용하여 테이블을 추가합니다.

```
aws rds-data execute-statement \
      --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:http-endpoint-test" \
      --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:testHttp2-AmNvc1" \
      --region us-east-1 \
      --sql "create table Pets(id varchar(200), type varchar(200), price float)" \
      --database "TESTDB"
```

### API 인터페이스 설계
<a name="graphql-schema"></a>

Aurora Serverless v2 데이터 API가 테이블과 함께 실행된 후에는 변형 및 구독을 수행하기 위해 GraphQL 스키마를 생성하고 해석기를 연결합니다. AWS AppSync 콘솔에서 새 API를 생성하고 콘솔의 **스키마** 페이지로 이동하여 다음을 입력합니다.

```
type Mutation {
        createPet(input: CreatePetInput!): Pet
        updatePet(input: UpdatePetInput!): Pet
        deletePet(input: DeletePetInput!): Pet
    }
    
    input CreatePetInput {
        type: PetType
        price: Float!
    }
    
    input UpdatePetInput {
        id: ID!
        type: PetType
        price: Float!
    }
    
    input DeletePetInput {
        id: ID!
    }
    
    type Pet {
        id: ID!
        type: PetType
        price: Float
    }
    
    enum PetType {
        dog
        cat
        fish
        bird
        gecko
    }
    
    type Query {
        getPet(id: ID!): Pet
        listPets: [Pet]
        listPetsByPriceRange(min: Float, max: Float): [Pet]
    }
    
    schema {
        query: Query
        mutation: Mutation
    }
```

 스키마를 **저장**하고 **데이터 원본** 페이지로 이동해 새 데이터 원본을 생성합니다. **데이터 소스** 유형으로 **관계형 데이터베이스**를 선택하고 기억하기 쉬운 이름을 입력합니다. 마지막 단계에서 생성한 데이터베이스 이름과 해당 데이터베이스를 생성한 **클러스터 ARN**을 사용합니다. **역할**의 경우 새 역할을 AWS AppSync 생성하거나 다음과 유사한 정책을 사용하여 역할을 생성할 수 있습니다.

------
#### [ JSON ]

****  

```
{
        "Version":"2012-10-17",		 	 	 
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "rds-data:BatchExecuteStatement",
                    "rds-data:BeginTransaction",
                    "rds-data:CommitTransaction",
                    "rds-data:ExecuteStatement",
                    "rds-data:RollbackTransaction"
                ],
                "Resource": [
                    "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster",
                    "arn:aws:rds:us-east-1:111122223333:cluster:mydbcluster:*"
                ]
            },
            {
                "Effect": "Allow",
                "Action": [
                    "secretsmanager:GetSecretValue"
                ],
                "Resource": [
                "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret",
                "arn:aws:secretsmanager:us-east-1:111122223333:secret:mysecret:*"
                ]
            }
        ]
    }
```

------

이 정책에는 역할 액세스 권한을 부여하는 **명령문**이 2개 있습니다. 첫 번째 **리소스**는 Aurora Serverless v2 클러스터이고 두 번째 리소스는 AWS Secrets Manager ARN입니다. **생성을** 클릭하기 전에 AWS AppSync 데이터 소스 구성에 **두** ARNs을 모두 제공해야 합니다.

## API를 데이터베이스 작업에 연결
<a name="configuring-resolvers"></a>

이제 유효한 GraphQL 스키마와 RDS 데이터 소스가 있으므로 해석기를 스키마에 대한 GraphQL 필드에 연결할 수 있습니다. API는 다음과 같은 기능을 제공합니다.

1. *Mutation.createPet* 필드를 통해 애완 동물 생성

1. *Mutation.updatePet* 필드를 통해 애완 동물 업데이트

1. *Mutation.deletePet* 필드를 통해 애완 동물 삭제

1. *Query.getPet* 필드를 통해 단일 애완 동물 가져오기

1. *Query.listPets* 필드를 통해 모든 애완 동물 나열

1. *Query.listPetsByPriceRange* 필드를 통해 가격 범위 내 애완 동물 나열

### Mutation.createPet
<a name="mutation-createpet"></a>

 AWS AppSync 콘솔의 스키마 편집기에서 오른쪽에서 용 **해석기 연결을** 선택합니다`createPet(input: CreatePetInput!): Pet`. RDS 데이터 소스를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
#set($id=$utils.autoId())
{
"version": "2018-05-29",
    "statements": [
        "insert into Pets VALUES (:ID, :TYPE, :PRICE)",
        "select * from Pets WHERE id = :ID"
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

시스템이 **설명문** 배열의 순서에 따라 SQL 문을 순차적으로 실행합니다. 결과 역시 동일한 순서로 반환됩니다. 이것은 변형이므로, *삽입* 후에 *select* 문을 실행하여 커밋된 값을 검색하여 GraphQL 응답 매핑 템플릿을 채웁니다.

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

*명령문*에 SQL 쿼리가 2개 있기 때문에 `$utils.rds.toJsonString($ctx.result))[1][0])`을 사용하여 데이터베이스에서 반환되는 매트릭스에서 두 번째 결과를 지정해야 합니다.

### Mutation.updatePet
<a name="mutation-updatepet"></a>

 AWS AppSync 콘솔의 스키마 편집기에서에 대한 **해석기 연결을** 선택합니다`updatePet(input: UpdatePetInput!): Pet`. **RDS 데이터 소스**를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("update Pets set type=:TYPE, price=:PRICE WHERE id=:ID"),
        $util.toJson("select * from Pets WHERE id = :ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

### Mutation.deletePet
<a name="mutation-deletepet"></a>

 AWS AppSync 콘솔의 스키마 편집기에서에 대한 **해석기 연결을** 선택합니다`deletePet(input: DeletePetInput!): Pet`. **RDS 데이터 소스**를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("select * from Pets WHERE id=:ID"),
        $util.toJson("delete from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id"
    }
}
```

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.getPet
<a name="query-getpet"></a>

이제 스키마에 대한 변형이 생성되었으므로 개별 항목을 가져와 나열하고 SQL 필터링을 적용하는 방법을 보여주기 위해 쿼리 3개를 연결합니다. AWS AppSync 콘솔의 **스키마 편집기**에서에 대한 **해석기 연결을** 선택합니다`getPet(id: ID!): Pet`. **RDS 데이터 소스**를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
{
"version": "2018-05-29",
        "statements": [
            $util.toJson("select * from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.id"
    }
}
```

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.listPets
<a name="query-listpets"></a>

 AWS AppSync 콘솔의 스키마 편집기에서 오른쪽에서 용 **해석기 연결을** 선택합니다`getPet(id: ID!): Pet`. **RDS 데이터 소스**를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
{
    "version": "2018-05-29",
    "statements": [
        "select * from Pets"
    ]
}
```

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

### Query.listPetsByPriceRange
<a name="query-listpetsbypricerange"></a>

 AWS AppSync 콘솔의 스키마 편집기에서 오른쪽에서 용 **해석기 연결을** 선택합니다`getPet(id: ID!): Pet`. **RDS 데이터 소스**를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.max),
        ":MIN": $util.toJson($ctx.args.min)
    }
}
```

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

## API를 통해 데이터 수정
<a name="run-mutations"></a>

SQL 문을 사용하여 모든 해석기를 구성했고 Serverless Aurora 데이터 API에 GraphQL API를 연결했으므로 변형 및 쿼리 수행을 시작할 수 있습니다. AWS AppSync 콘솔에서 **쿼리** 탭을 선택하고 다음을 입력하여 Pet를 생성합니다.

```
mutation add {
    createPet(input : { type:fish, price:10.0 }){
        id
        type
        price
    }
}
```

응답에는 다음과 같이 *id*, *유형* 및 *가격*이 포함되어 있어야 합니다.

```
{
  "data": {
    "createPet": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "type": "fish",
      "price": "10.0"
    }
  }
}
```

*updatePet* 변형을 실행하여 항목을 수정할 수 있습니다.

```
mutation update {
    updatePet(input : {
        id: ID_PLACEHOLDER,
        type:bird,
        price:50.0
    }){
        id
        type
        price
    }
}
```

참고로 앞서 *createPet* 작업에서 반환된 *id*를 사용했습니다. 해석기에서 `$util.autoId()`를 사용했기 때문에 이 ID는 레코드에 대해 고유한 값이 될 것입니다. 레코드는 다음과 같이 유사한 방식으로 삭제할 수 있습니다.

```
mutation delete {
    deletePet(input : {id:ID_PLACEHOLDER}){
        id
        type
        price
    }
}
```

첫 번째 변형을 사용해 *가격*에 대한 값을 다르게 해 레코드를 몇 개 생성한 다음 쿼리를 실행합니다.

## 데이터 검색
<a name="run-queries"></a>

콘솔의 **쿼리** 탭에서 다음 문을 사용해 생성된 레코드를 모두 나열합니다.

```
query allpets {
    listPets {
        id
        type
        price
    }
}
```

다음 GraphQL 쿼리를 사용하여 *Query.listPetsByPriceRange*에 대한 매핑 템플릿에 `where price > :MIN and price < :MAX`가 있는 SQL *WHERE* 조건자를 활용합니다.

```
query petsByPriceRange {
    listPetsByPriceRange(min:1, max:11) {
        id
        type
        price
    }
}
```

*가격*이 \$11 이상이거나 \$110 미만인 레코드만 표시되어야 합니다. 마지막으로, 다음과 같이 개별 레코드를 검색하는 쿼리를 수행할 수 있습니다.

```
query onePet {
    getPet(id:ID_PLACEHOLDER){
        id
        type
        price
    }
}
```

## 데이터 액세스 보호
<a name="input-sanitization"></a>

SQL 인젝션은 데이터베이스 애플리케이션의 보안 취약성입니다. 공격자가 사용자 입력 필드를 통해 악성 SQL 코드를 삽입할 때 발생합니다. 이는 데이터베이스 데이터에 대한 무단 액세스를 허용할 수 있습니다. SQL 인젝션 공격으로부터 보호하기 위해 `variableMap`을 사용하여 처리하기 전에 모든 사용자 입력을 주의 깊게 검증하고 삭제하는 것이 좋습니다. 변수 맵을 사용하지 않는 경우 GraphQL 작업의 인수를 삭제해야 합니다. 폐기 방법 중 하나는 데이터 API에 대해 SQL 문을 실행하기 전에 요청 매핑 템플릿에 입력 관련 확인 단계를 제공하는 것입니다. `listPetsByPriceRange` 예제의 요청 매핑 템플릿을 수정하는 방법을 살펴보겠습니다. 전적으로 사용자 입력에만 의존하는 대신 다음과 같이 수행할 수 있습니다.

```
#set($validMaxPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.maxPrice))

#set($validMinPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.minPrice))


#if (!$validMaxPrice || !$validMinPrice)
    $util.error("Provided price input is not valid.")
#end
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.maxPrice),
        ":MIN": $util.toJson($ctx.args.minPrice)
    }
}
```

데이터 API에 대해 해석기를 실행할 때 무단 입력을 방지하기 위한 또 다른 방법은 저장 프로시저 및 파라미터화된 입력과 함께 준비된 명령문을 사용하는 것입니다. 예를 들어, `listPets`에 대한 해석기에서 준비된 명령문으로 *select*를 실행하는 다음 프로시저를 정의합니다.

```
CREATE PROCEDURE listPets (IN type_param VARCHAR(200))
  BEGIN
     PREPARE stmt FROM 'SELECT * FROM Pets where type=?';
     SET @type = type_param;
     EXECUTE stmt USING @type;
     DEALLOCATE PREPARE stmt;
  END
```

Aurora Serverless v2 인스턴스에서 이것을 생성합니다.

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:xxxxxxxxxxxx:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:xxxxxxxxxxxx:secret:httpendpoint-xxxxxx"  \
--region us-east-1  --database "DB_NAME" \
--sql "CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END"
```

이제 저장 프로시저만 호출하면 되므로 결과적으로 listPets에 대한 해석기 코드가 간단해졌습니다. 최소한 문자열 입력에서는 작은 따옴표를 [이스케이프](#escaped) 처리해야 합니다.

```
#set ($validType = $util.isString($ctx.args.type) && !$util.isNullOrBlank($ctx.args.type))
#if (!$validType)
    $util.error("Input for 'type' is not valid.", "ValidationError")
#end

{
    "version": "2018-05-29",
    "statements": [
        "CALL listPets(:type)"
    ]
    "variableMap": {
        ":type": $util.toJson($ctx.args.type.replace("'", "''"))
    }
}
```

### 이스케이프 문자열 사용
<a name="escaped"></a>

작은 따옴표는 SQL 문에서 문자열 리터럴의 시작과 끝을 나타냅니다(예: `'some string value'`). 문자열 내에서 하나 이상의 작은 따옴표 문자(`'`)가 있는 문자열 값을 사용할 수 있게 하려면 각 문자열 값을 두 개의 작은 따옴표(`''`)로 바꿔야 합니다. 예를 들어 입력 문자열이 `Nadia's dog`인 경우 SQL 문에 대해 이스케이프 처리합니다.

```
update Pets set type='Nadia''s dog' WHERE id='1'
```

# AWS AppSync에서 파이프라인 해석기 사용
<a name="tutorial-pipeline-resolvers"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

AWS AppSync는 단위 해석기를 통해 GraphQL 필드를 단일 데이터 소스에 연결하는 간단한 방법을 제공합니다. 그러나 단일 작업을 실행하는 것만으로는 충분하지 않을 수 있습니다. 파이프라인 해석기는 데이터 원본에 대해 작업을 순차적으로 실행하는 기능을 제공합니다. API에서 함수를 생성하고 파이프라인 해석기에 해당 작업을 연결합니다. 각 함수 실행 결과는 실행할 함수가 없을 때까지 다음으로 전송됩니다. 파이프라인 해석기를 사용하면 이제 AWS AppSync에서 직접 더 복잡한 워크플로를 구축할 수 있습니다. 이 자습서에서는 사용자가 사진을 게시하고, 친구들이 공유한 사진을 볼 수 있는 간단한 사진 보기 앱을 빌드합니다.

## 원클릭 설치
<a name="one-click-setup"></a>

모든 해석기가 구성되고 필요한 AWS 리소스가 포함된 GraphQL 엔드포인트를 AWS AppSync에서 자동으로 설정하려면 다음 템플릿을 사용할 AWS CloudFormation 수 있습니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-full.yaml)

이 스택은 계정에 다음 리소스를 생성합니다.
+ 계정의 리소스에 액세스하기 위한 AWS AppSync의 IAM 역할
+ DynamoDB 테이블 2개
+ Amazon Cognito 사용자 풀 1개
+ Amazon Cognito 사용자 풀 그룹 2개
+ Amazon Cognito 사용자 풀 사용자 3명
+ 1 AWS AppSync API

 AWS CloudFormation 스택 생성 프로세스가 끝나면 생성된 세 명의 Amazon Cognito 사용자 각각에 대해 하나의 이메일을 받게 됩니다. 각 이메일에는 AWS AppSync 콘솔에 Amazon Cognito 사용자로 로그인하는 데 사용하는 임시 암호가 포함되어 있습니다. 자습서의 나머지 부분에서 사용할 수 있도록 암호를 저장해 둡니다.

## 수동 설정
<a name="manual-setup"></a>

 AWS AppSync 콘솔을 통해 step-by-step 프로세스를 수동으로 진행하려면 아래 설정 프로세스를 따르세요.

### 비 AWS AppSync 리소스 설정
<a name="setting-up-your-non-aws-appsync-resources"></a>

API는 DynamoDB 테이블 2개 즉, 사진을 저장하는 **사진** 테이블과 사용자 간의 관계를 저장하는 **친구** 테이블과 통신합니다. API는 인증 유형으로 Amazon Cognito 사용자 풀을 사용하도록 구성되어 있습니다. 다음 CloudFormation 스택은 계정에서 이러한 리소스를 설정합니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-resources-only.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/pipeline/pipeline-resolvers-resources-only.yaml)

 AWS CloudFormation 스택 생성 프로세스가 끝나면 생성된 세 명의 Amazon Cognito 사용자 각각에 대해 하나의 이메일을 받게 됩니다. 각 이메일에는 AWS AppSync 콘솔에 Amazon Cognito 사용자로 로그인하는 데 사용하는 임시 암호가 포함되어 있습니다. 자습서의 나머지 부분에서 사용할 수 있도록 암호를 저장해 둡니다.

### GraphQL API 생성
<a name="creating-your-graphql-api"></a>

 AWS AppSync에서 GraphQL API를 생성하려면:

1.  AWS AppSync 콘솔을 열고 **스크래치에서 빌드**를 선택한 다음 **시작**을 선택합니다.

1. API의 이름을 `AppSyncTutorial-PicturesViewer`로 설정합니다.

1. **생성(Create)**을 선택합니다.

 AWS AppSync 콘솔은 API 키 인증 모드를 사용하여 새 GraphQL API를 생성합니다. 콘솔에서 GraphQL API의 나머지 부분을 설정하고 이 자습서의 나머지 부분에서 이 API에 대한 쿼리를 실행할 수 있습니다.

### GraphQL API 구성
<a name="configuring-the-graphql-api"></a>

방금 생성한 Amazon Cognito 사용자 풀로 AWS AppSync API를 구성해야 합니다.

1. **설정** 탭을 선택합니다.

1. **Authorization Type(권한 부여 유형)** 섹션에서 *Amazon Cognito User Pool(Amazon Cognito 사용자 풀)*을 선택합니다.

1. **사용자 풀 구성**에서 *AWS 리전*으로 **US-WEST-2**를 선택합니다.

1. **AppSyncTutorial-UserPool** 사용자 풀을 선택합니다.

1. *기본 작업*으로 **거부**를 선택합니다.

1. **AppId client regex** 필드는 비워둡니다.

1. **저장**을 선택합니다.

이제 API가 인증 유형으로 Amazon Cognito 사용자 풀을 사용하도록 설정되어 있습니다.

### DynamoDB 테이블에 대한 데이터 소스 구성
<a name="configuring-data-sources-for-the-ddb-tables"></a>

DynamoDB 테이블이 생성되면 콘솔에서 AWS AppSync GraphQL API로 이동하여 **데이터 소스** 탭을 선택합니다. 이제 방금 생성한 각 DynamoDB 테이블에 대해 AWS AppSync에서 데이터 소스를 생성합니다.

1. **데이터 원본** 탭을 선택합니다.

1. **새로 만들기**를 선택하여 새 데이터 원본을 생성합니다.

1. 데이터 원본 이름에 `PicturesDynamoDBTable`을 입력합니다.

1. Data Source Type(데이터 원본 유형)으로 **Amazon DynamoDB table(Amazon DynamoDB 테이블)**을 선택합니다.

1. 리전으로 **US-WEST-2**를 선택합니다.

1. 테이블 목록에서 **AppSyncTutorial-Pictures** DynamoDB 테이블을 선택합니다.

1. **새 역할 생성 또는 기존 역할 사용** 섹션에서 **기존 역할**을 선택합니다.

1. CloudFormation 템플릿에서 방금 생성한 역할을 선택합니다. *ResourceNamePrefix*를 변경하지 않은 경우 역할 이름은 **AppSyncTutorial-DynamoDBRole**이어야 합니다.

1. **생성(Create)**을 선택합니다.

**친구** 테이블에 대해 동일한 프로세스를 반복합니다. CloudFormation 스택을 생성할 때 *ResourceNamePrefix* 파라미터를 변경하지 않은 경우 DynamoDB 테이블의 이름은 **AppSyncTutorial-Friends**가 되어야 합니다.

### GraphQL 스키마 생성
<a name="creating-the-graphql-schema"></a>

이제 데이터 소스가 DynamoDB 테이블에 연결되었으며 GraphQL 스키마를 생성해 보겠습니다. AWS AppSync 콘솔의 스키마 편집기에서 스키마가 다음 스키마와 일치하는지 확인합니다.

```
schema {
    query: Query
    mutation: Mutation
}

type Mutation {
    createPicture(input: CreatePictureInput!): Picture!
    @aws_auth(cognito_groups: ["Admins"])
    createFriendship(id: ID!, target: ID!): Boolean
    @aws_auth(cognito_groups: ["Admins"])
}

type Query {
    getPicturesByOwner(id: ID!): [Picture]
    @aws_auth(cognito_groups: ["Admins", "Viewers"])
}

type Picture {
    id: ID!
    owner: ID!
    src: String
}

input CreatePictureInput {
    owner: ID!
    src: String!
}
```

스키마를 저장하려면 **Save Schema(스키마 저장)**를 선택합니다.

일부 스키마 필드에는 *@aws\$1auth* 명령으로 주석이 달려 있습니다. API 기본 작업 구성이 *DENY*로 설정되어 있기 때문에 API에서는 *@aws\$1auth* 명령 내에 언급된 그룹의 구성원이 아닌 사용자는 모두 거부합니다. API를 보호하는 방법에 대한 자세한 내용은 [보안](security-authz.md#aws-appsync-security) 페이지를 참조하십시오. 이 경우에는 관리자 사용자만 Mutation.createPicture 및 Mutation.createFriendship 필드에 대한 액세스 권한을 가지고 있으며, Admins 또는 Viewers 그룹의 구성원인 사용자는 Query.getPicturesByOwner 필드에 액세스할 수 있습니다. 다른 모든 사용자는 액세스 권한이 없습니다.

### 해석기 구성
<a name="configuring-resolvers"></a>

이제 유효한 GraphQL 스키마와 데이터 원본이 2개 있으므로 해석기를 스키마에 대한 GraphQL 필드에 연결할 수 있습니다. API는 다음과 같은 기능을 제공합니다.
+ *Mutation.createPicture* 필드를 통해 사진 생성
+ *Mutation.createFriendship* 필드를 통해 친구 관계 생성
+ *Query.getPicture* 필드를 통해 사진 가져오기

#### Mutation.createPicture
<a name="mutation-createpicture"></a>

 AWS AppSync 콘솔의 스키마 편집기에서 오른쪽에서 용 **해석기 연결을** 선택합니다`createPicture(input: CreatePictureInput!): Picture!`. DynamoDB*PicturesDynamoDBTable* 데이터 소스를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
#set($id = $util.autoId())

{
    "version" : "2018-05-29",

    "operation" : "PutItem",

    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($id),
        "owner": $util.dynamodb.toDynamoDBJson($ctx.args.input.owner)
    },

    "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
```

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
#if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($ctx.result)
```

사진 생성 기능이 완료되었습니다. 임의로 생성되는 UUID를 사진 ID로, Cognito 사용자 이름을 사진의 소유자로 사용하여 **사진** 테이블에 사진을 저장합니다.

#### Mutation.createFriendship
<a name="mutation-createfriendship"></a>

 AWS AppSync 콘솔의 스키마 편집기에서 오른쪽에서 용 **해석기 연결을** 선택합니다`createFriendship(id: ID!, target: ID!): Boolean`. DynamoDB**FriendsDynamoDBTable** 데이터 소스를 선택합니다. **요청 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
#set($userToFriendFriendship = { "userId" : "$ctx.args.id", "friendId": "$ctx.args.target" })
#set($friendToUserFriendship = { "userId" : "$ctx.args.target", "friendId": "$ctx.args.id" })
#set($friendsItems = [$util.dynamodb.toMapValues($userToFriendFriendship), $util.dynamodb.toMapValues($friendToUserFriendship)])

{
    "version" : "2018-05-29",
    "operation" : "BatchPutItem",
    "tables" : {
        ## Replace 'AppSyncTutorial-' default below with the ResourceNamePrefix you provided in the CloudFormation template
        "AppSyncTutorial-Friends": $util.toJson($friendsItems)
    }
}
```

중요: **BatchPutItem** 요청 템플릿에 DynamoDB 테이블의 정확한 이름이 있어야 합니다. 기본 테이블 이름은 *AppSyncTutorial-Friends*입니다. 잘못된 테이블 이름을 사용하면 AppSync에서 제공된 역할을 수행하려고 할 때 오류가 발생합니다.

이 자습서에서는 간단한 설명을 위해 친구 관계 요청이 승인되어 친구 관계 항목이 **AppSyncTutorialFriends** 테이블에 직접 저장된 것처럼 진행합니다.

효율적으로, 관계는 양방향이기 때문에 각 친구 관계에 대해 항목 2개를 저장합니다. 다대다 관계를 나타내기 위한 Amazon DynamoDB 모범 사례에 대한 자세한 내용은 [DynamoDB 모범 사례](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html)를 참조하세요.

**응답 매핑 템플릿** 섹션에 다음 템플릿을 추가합니다.

```
#if($ctx.error)
    $util.error($ctx.error.message, $ctx.error.type)
#end
true
```

참고: 요청 템플릿에 올바른 테이블 이름이 포함되어 있는지 확인합니다. 기본 이름은 *AppSyncTutorial-Friends*인데, CloudFormation **ResourceNamePrefix** 파라미터를 변경한 경우에는 테이블 이름이 약간 다를 수 있습니다.

#### Query.getPicturesByOwner
<a name="query-getpicturesbyowner"></a>

이제, 친구 관계 및 사진이 있으므로 사용자가 친구의 사진을 볼 수 있는 기능을 제공해야 합니다. 이러한 요구 사항을 충족하기 위해 먼저 요청자가 소유자의 친구인지 확인하고 마지막으로 사진을 쿼리해야 합니다.

이 기능에는 두 가지 데이터 원본 작업이 필요하기 때문에 함수 2개를 생성해 보겠습니다. 첫 번째 함수 **isFriend**는 요청자와 소유자가 친구인지 확인합니다. 두 번째 함수 **getPicturesByOwner**는 제공된 소유자 ID를 사용해 요청된 사진을 가져옵니다. 아래에서 *Query.getPicturesByOwner* 필드에 대해 제안된 해석기에 대한 실행 흐름을 살펴보겠습니다.

1. Before 매핑 템플릿: 컨텍스트와 필드 입력 파라미터를 준비합니다.

1. isFriend 함수: 요청자가 사진의 소유자인지 확인합니다. 소유자가 아닌 경우 친구 테이블에 대해 DynamoDB GetItem 작업을 수행하여 요청자와 소유자 사용자가 친구인지 확인합니다.

1. getPicturesByOwner 함수: *owner-index* 글로벌 보조 인덱스에 대한 DynamoDB 쿼리 작업을 사용하여 사진 테이블에서 사진을 검색합니다.

1. After 매핑 템플릿: DynamoDB 속성이 예상되는 GraphQL 유형 필드에 정확하게 매핑되도록 사진 결과를 매핑합니다.

먼저 함수를 생성해 보겠습니다.

##### isFriend 함수
<a name="isfriend-function"></a>

1. **Functions(함수)** 탭을 선택합니다.

1. **Create Function(함수 생성)**을 선택하여 함수를 생성합니다.

1. 데이터 원본 이름에 `FriendsDynamoDBTable`을 입력합니다.

1. 함수 이름으로 *isFriend*를 입력합니다.

1. 요청 매핑 템플릿의 텍스트 영역 내에서 다음 템플릿을 붙여 넣습니다.

   ```
   #set($ownerId = $ctx.prev.result.owner)
   #set($callerId = $ctx.prev.result.callerId)
   
   ## if the owner is the caller, no need to make the check
   #if($ownerId == $callerId)
       #return($ctx.prev.result)
   #end
   
   {
       "version" : "2018-05-29",
   
       "operation" : "GetItem",
   
       "key" : {
           "userId" : $util.dynamodb.toDynamoDBJson($callerId),
           "friendId" : $util.dynamodb.toDynamoDBJson($ownerId)
       }
   }
   ```

1. 응답 매핑 템플릿의 텍스트 영역 내에서 다음 템플릿을 붙여 넣습니다.

   ```
   #if($ctx.error)
       $util.error("Unable to retrieve friend mapping message: ${ctx.error.message}", $ctx.error.type)
   #end
   
   ## if the users aren't friends
   #if(!$ctx.result)
       $util.unauthorized()
   #end
   
   $util.toJson($ctx.prev.result)
   ```

1. **함수 생성**을 선택합니다.

결과: **isFriend** 함수를 생성했습니다.

##### getPicturesByOwner 함수
<a name="getpicturesbyowner-function"></a>

1. **Functions(함수)** 탭을 선택합니다.

1. **Create Function(함수 생성)**을 선택하여 함수를 생성합니다.

1. 데이터 원본 이름에 `PicturesDynamoDBTable`을 입력합니다.

1. 함수 이름으로 `getPicturesByOwner`를 입력합니다.

1. 요청 매핑 템플릿의 텍스트 영역 내에서 다음 템플릿을 붙여 넣습니다.

   ```
   {
       "version" : "2018-05-29",
   
       "operation" : "Query",
   
       "query" : {
           "expression": "#owner = :owner",
           "expressionNames": {
               "#owner" : "owner"
           },
           "expressionValues" : {
               ":owner" : $util.dynamodb.toDynamoDBJson($ctx.prev.result.owner)
           }
       },
   
       "index": "owner-index"
   }
   ```

1. 응답 매핑 템플릿의 텍스트 영역 내에서 다음 템플릿을 붙여 넣습니다.

   ```
   #if($ctx.error)
       $util.error($ctx.error.message, $ctx.error.type)
   #end
   
   $util.toJson($ctx.result)
   ```

1. **함수 생성**을 선택합니다.

결과: **getPicturesByOwner** 함수를 생성했습니다. 이제 함수를 생성했으므로 *Query.getPicturesByOwner* 필드에 파이프라인 해석기를 연결합니다.

 AWS AppSync 콘솔의 스키마 편집기에서 오른쪽에서 용 **해석기 연결을** 선택합니다`Query.getPicturesByOwner(id: ID!): [Picture]`. 다음 페이지에서는 데이터 원본 드롭다운 목록에 아래에 표시되는 **Convert to pipeline resolver(파이프라인 해석기로 변환)** 링크를 선택합니다. Before 매핑 템플릿에 다음을 사용합니다.

```
#set($result = { "owner": $ctx.args.id, "callerId": $ctx.identity.username })
$util.toJson($result)
```

**after mapping template(사후 매핑 템플릿)** 섹션에서 다음을 사용합니다.

```
#foreach($picture in $ctx.result.items)
    ## prepend "src://" to picture.src property
    #set($picture['src'] = "src://${picture['src']}")
#end
$util.toJson($ctx.result.items)
```

**Create Resolver(해석기 생성)**를 선택합니다. 이제 첫 번째 파이프라인 해석기를 성공적으로 연결했습니다. 동일한 페이지에서 이전에 생성한 함수 2개를 추가합니다. 함수 섹션에서 **Add A Function(함수 추가)**를 선택하고 첫 번째 함수의 이름을 선택하거나 **isFriend**를 입력합니다. **getPicturesByOwner** 함수에 대해 동일한 프로세스를 수행하여 두 번째 함수를 추가합니다. **isFriend** 함수가 목록 맨 위에 나타나고 그 다음에 **getPicturesByOwner** 함수가 표시되는지 확인합니다. 위쪽 및 아래쪽 화살표를 사용하여 파이프라이니 내 함수의 실행 순서를 다시 정렬할 수 있습니다.

이제, 파이프라인 해석기가 생성되었고, 함수를 연결했으므로 새로 생성된 GraphQL API를 테스트해보겠습니다.

## GraphQL API 테스트
<a name="testing-your-graphql-api"></a>

먼저, 생성한 관리자 사용자를 사용하여 몇 가지 변형을 실행해 사진과 친구 관계를 채워야 합니다. AWS AppSync 콘솔 왼쪽에서 **쿼리** 탭을 선택합니다.

### createPicture 변형
<a name="createpicture-mutation"></a>

1.  AWS AppSync 콘솔에서 **쿼리** 탭을 선택합니다.

1. **Login With User Pools(사용자 풀로 로그인)**를 선택합니다.

1. 모달에서 CloudFormation 스택에서 생성한 Cognito 샘플 클라이언트 ID(예: 37solo6mmhh7k4v63cqdfgdg5d)를 입력합니다.

1. 파라미터로 CloudFormation 스택에 전달한 사용자 이름을 입력합니다. 기본값은 **nadia**입니다.

1. CloudFormation 스택에 파라미터로 입력한 이메일로 전송된 임시 암호를 사용합니다(예: *UserPoolUserEmail*).

1. 로그인을 선택합니다. 버튼이 **Logout nadia**로 바뀌거나 CloudFormation 스택을 생성할 때 사용자 이름(즉, *UserPoolUsername*)으로 바뀌어야 합니다.

몇 가지 *createPicture* 변경을 전송해 사진 테이블을 채워 보겠습니다. 콘솔 내에서 다음 GraphQL 쿼리를 실행합니다.

```
mutation {
  createPicture(input:{
    owner: "nadia"
    src: "nadia.jpg"
  }) {
    id
    owner
    src
  }
}
```

응답은 아래와 같아야 합니다.

```
{
  "data": {
    "createPicture": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "owner": "nadia",
      "src": "nadia.jpg"
    }
  }
}
```

사진을 몇 개 더 추가해 보겠습니다.

```
mutation {
  createPicture(input:{
    owner: "shaggy"
    src: "shaggy.jpg"
  }) {
    id
    owner
    src
  }
}
```

```
mutation {
  createPicture(input:{
    owner: "rex"
    src: "rex.jpg"
  }) {
    id
    owner
    src
  }
}
```

**nadia**를 관리자 사용자로 사용하여 사진을 3개 더 추가했습니다.

### createFriendship 변형
<a name="createfriendship-mutation"></a>

친구 관계 항목을 추가해 보겠습니다. 콘솔에서 다음 변형을 실행합니다.

참고: 관리자 사용자로 로그인한 상태여야 합니다(기본 관리자 사용자: **nadia**).

```
mutation {
  createFriendship(id: "nadia", target: "shaggy")
}
```

응답이 다음과 같아야 합니다.

```
{
  "data": {
    "createFriendship": true
  }
}
```

 **nadia** 및 **shaggy**는 친구이지만 **rex**는 그 누구와도 친구가 아닙니다.

### getPicturesByOwner 쿼리
<a name="getpicturesbyowner-query"></a>

이 단계에서는 Cognito 사용자 풀과 이 자습서 초반에 설정한 자격 증명을 사용하여 **nadia**로 로그인합니다. **nadia**로 **shaggy**가 소유하고 있는 사진을 검색합니다.

```
query {
    getPicturesByOwner(id: "shaggy") {
        id
        owner
        src
    }
}
```

**nadia**와 **shaggy**는 친구이기 때문에 이 쿼리는 해당하는 사진을 반환해야 합니다.

```
{
  "data": {
    "getPicturesByOwner": [
      {
        "id": "05a16fba-cc29-41ee-a8d5-4e791f4f1079",
        "owner": "shaggy",
        "src": "src://shaggy.jpg"
      }
    ]
  }
}
```

마찬가지로, **nadia**가 자신의 사진을 검색하려고 하는 경우에도 성공합니다. 파이프라인 해석기는 이 경우 **isFriend** GetItem 작업 실행을 피하도록 최적화되어 있습니다. 다음 쿼리를 시도해 보십시오.

```
query {
    getPicturesByOwner(id: "nadia") {
        id
        owner
        src
    }
}
```

(**Settings(설정)** 창에서) API에 대한 로깅을 활성화한 경우 디버그 수준을 **ALL**로 설정하고 동일한 쿼리를 다시 실행하면 필드 실행에 대한 로그가 반환됩니다. 로그를 보면 **isFriend** 함수가 **요청 매핑 템플릿** 단계에서 초기에 반환되었는지 여부를 확인할 수 있습니다.

```
{
  "errors": [],
  "mappingTemplateType": "Request Mapping",
  "path": "[getPicturesByOwner]",
  "resolverArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/types/Query/fields/getPicturesByOwner",
  "functionArn": "arn:aws:appsync:us-west-2:XXXX:apis/XXXX/functions/o2f42p2jrfdl3dw7s6xub2csdfs",
  "functionName": "isFriend",
  "earlyReturnedValue": {
    "owner": "nadia",
    "callerId": "nadia"
  },
  "context": {
    "arguments": {
      "id": "nadia"
    },
    "prev": {
      "result": {
        "owner": "nadia",
        "callerId": "nadia"
      }
    },
    "stash": {},
    "outErrors": []
  },
  "fieldInError": false
}
```

*earlyReturnedValue* 키는 *\$1return* 명령으로 반환된 데이터를 나타냅니다.

마지막으로, **rex**가 **Viewers** Cognito UserPool의 구성원이긴 하지만 **rex**는 그 누구와도 친구가 아니기 때문에 **shaggy** 또는 **nadia**가 소유한 어떤 사진에도 액세스할 수 없습니다. 콘솔에서 **rex**로 로그인해 다음 쿼리를 실행해 보십시오.

```
query {
    getPicturesByOwner(id: "nadia") {
        id
        owner
        src
    }
}
```

다음과 같은 권한 없음 오류가 발생합니다.

```
{
  "data": {
    "getPicturesByOwner": null
  },
  "errors": [
    {
      "path": [
        "getPicturesByOwner"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 9,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access getPicturesByOwner on type Query"
    }
  ]
}
```

지금까지 파이프라인 해석기를 사용하여 복잡한 권한 부여를 성공적으로 구현해 보았습니다.

# 에서 버전이 지정된 데이터 소스에 Delta Sync 작업 사용 AWS AppSync
<a name="tutorial-delta-sync"></a>

**참고**  
이제 우리는 주로 APPSYNC\$1JS 런타임과 해당 문서를 지원합니다. [여기](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)에서 APPSYNC\$1JS 런타임과 해당 안내서를 사용해 보세요.

 AWS AppSync의 클라이언트 애플리케이션은 GraphQL 응답을 모바일/웹 애플리케이션의 디스크에 로컬로 캐싱하여 데이터를 저장합니다. 버전이 지정된 데이터 원본 및 `Sync` 작업을 통해 고객은 해석기 하나로 동기화 프로세스를 수행할 수 있습니다. 따라서 클라이언트가 수 많은 레코드가 있을 수 있는 기본 쿼리 하나의 결과로 로컬 캐시를 하이드레이션한 다음 마지막 쿼리 이후 변경된 데이터만 수신할 수 있습니다(*델타 업데이트*). 클라이언트가 최초 요청을 사용한 캐시의 기본 하이드레이션과 다른 요청의 증분 업데이트를 수행하도록 해 클라이언트 애플리케이션에서 백엔드로 컴퓨팅을 이전할 수 있습니다. 이는 온라인과 오프라인 상태 간에 자주 전환하는 클라이언트 애플리케이션에 훨씬 더 효율적입니다.

Delta Sync를 구현하기 위해 `Sync` 쿼리는 버전이 지정된 데이터 원본에 대한 `Sync` 작업을 사용합니다. AWS AppSync 변형이 버전이 지정된 데이터 소스의 항목을 변경하면 해당 변경에 대한 레코드도 *Delta* 테이블에 저장됩니다. 다른 버전이 지정된 데이터 소스에 대해 다른 *Delta* 테이블(예: 유형당 하나, 도메인 영역당 하나)을 사용하거나 API에 대해 단일 *Delta* 테이블을 사용하도록 선택할 수 있습니다. AWS AppSync는 기본 키의 충돌을 방지하기 위해 여러 APIs에 대해 단일 *Delta* 테이블을 사용하지 않도록 권장합니다.

또한 Delta Sync 클라이언트는 구독을 인수로 수신할 수 있고, 그런 다음 오프라인-온라인 전환 간에 구독 다시 연결 및 쓰기를 조정합니다. Delta Sync는 지수 백오프, 여러 네트워크 오류 시나리오 간에 지터를 사용한 재시도 및 대기열에 이벤트 저장을 비롯하여 구독을 자동으로 재개해 이를 수행합니다. 그런 다음 대기열의 모든 이벤트를 병합하여 최종적으로 구독을 정상적으로 처리하기 전에 적절한 델타 또는 기본 쿼리가 실행됩니다.

Amplify DataStore를 포함한 클라이언트 구성 옵션에 대한 문서는 [Amplify Framework 웹사이트](https://aws-amplify.github.io/)를 참조하십시오. 이 문서에서는 최적의 데이터 액세스를 위해 Delta Sync 클라이언트와 작동하도록 버전이 지정된 DynamoDB 데이터 원본과 `Sync` 작업을 설정하는 방법을 설명합니다.

## 원클릭 설치
<a name="one-click-setup"></a>

모든 해석기가 구성되고 필요한 AWS 리소스가 있는 GraphQL 엔드포인트를 AWS AppSync에서 자동으로 설정하려면 다음 AWS CloudFormation 템플릿을 사용합니다.

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/deltasync/deltasync-v2-full.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/deltasync/deltasync-v2-full.yaml) 

이 스택은 계정에 다음 리소스를 생성합니다.
+ DynamoDB 테이블 2개(기본 및 델타)
+ API 키가 있는 1 AWS AppSync API
+ DynamoDB 테이블에 대한 정책이 연결된 IAM 역할 1개

클라이언트가 오프라인 상태였을 때 누락된 이벤트 저널로 작동하는 두 번째 테이블로 동기화 쿼리를 분할하는 데 테이블 2개가 사용됩니다. 델타 테이블에 대해 쿼리를 효율적으로 유지하기 위해 [Amazon DynamoDB TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html)을 사용하면 필요에 따라 이벤트를 자동으로 정리됩니다. TTL 시간은 데이터 원본의 필요에 맞게 구성할 수 있습니다(1시간, 1일 등).

## 스키마
<a name="schema"></a>

Delta Sync를 시연하기 위해 샘플 애플리케이션은 DynamoDB의 *Base* 및 *Delta* 테이블이 지원하는 *Posts* 스키마를 생성합니다. AWS AppSync는 두 테이블 모두에 변형을 자동으로 기록합니다. 동기화 쿼리는 *기본* 또는 *델타* 테이블에서 레코드를 적절하게 가져오고 재연결 로직에서 클라이언트가 이러한 테이블을 어떻게 활용하는지 보여주도록 단일 구독이 정의됩니다.

```
input CreatePostInput {
    author: String!
    title: String!
    content: String!
    url: String
    ups: Int
    downs: Int
    _version: Int
}

interface Connection {
  nextToken: String
  startedAt: AWSTimestamp!
}

type Mutation {
    createPost(input: CreatePostInput!): Post
    updatePost(input: UpdatePostInput!): Post
    deletePost(input: DeletePostInput!): Post
}

type Post {
    id: ID!
    author: String!
    title: String!
    content: String!
    url: AWSURL
    ups: Int
    downs: Int
    _version: Int
    _deleted: Boolean
    _lastChangedAt: AWSTimestamp!
}

type PostConnection implements Connection {
    items: [Post!]!
    nextToken: String
    startedAt: AWSTimestamp!
}

type Query {
    getPost(id: ID!): Post
    syncPosts(limit: Int, nextToken: String, lastSync: AWSTimestamp): PostConnection!
}

type Subscription {
    onCreatePost: Post
        @aws_subscribe(mutations: ["createPost"])
    onUpdatePost: Post
        @aws_subscribe(mutations: ["updatePost"])
    onDeletePost: Post
        @aws_subscribe(mutations: ["deletePost"])
}

input DeletePostInput {
    id: ID!
    _version: Int!
}

input UpdatePostInput {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    _version: Int!
}

schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}
```

GraphQL 스키마가 표준이긴 하지만 계속 진행하기 전에 첫째, 모든 변형이 자동으로 먼저 *기본* 테이블에 기록한 다음 *델타* 테이블에 기록합니다. *기본* 테이블은 상태에 대한 중앙 출처인 반면에 *델타* 테이블은 저널입니다. `lastSync: AWSTimestamp`를 전달하지 않으면 `syncPosts` 쿼리가 *기본* 테이블에 대해 실행되어 캐시를 하이드레이트하고, 클라이언트가 *델타* 테이블에서 구성한 TTL 시간보다 오랫동안 오프라인 상태일 때 에지 케이스에 대한 *글로벌 캐치업 프로세스*로 주기적으로 실행됩니다. `lastSync: AWSTimestamp`를 전달하지 않으면 `syncPosts` 쿼리는 *델타* 테이블에 대해 실행되고 클라이언트가 마지막 오프라인 상태였던 이후 변경된 이벤트를 검색하는 데 사용됩니다. Amplify 클라이언트는 자동으로 `lastSync: AWSTimestamp` 값을 전달하고 디스크에 적절하게 계속 씁니다.

*Post*의 *\$1deleted* 필드는 **삭제** 작업에 사용됩니다. 클라이언트가 오프라인 상태이고 레코드가 *기본* 테이블에서 제거된 경우 이 속성은 클라이언트에 동기화를 수행하여 로컬 캐시에서 항목을 제거하라고 알립니다. 클라이언트가 장기간 오프라인 상태로 유지되고 Delta Sync 쿼리를 사용하여 클라이언트가 이 값을 검색하기 전에 항목이 제거되면 (클라이언트에서 구성 가능한) 기본 쿼리의 전역 캐치업 이벤트가 실행되어 캐시에서 항목을 제거합니다. 존재하는 항목을 삭제하는 동기화 쿼리를 실행할 때 값만 반환하기 때문에 이 필드는 선택 사항으로 표시됩니다.

## 변형
<a name="mutations"></a>

모든 변형에 대해 AWS AppSync는 *기본* 테이블에서 표준 Create/Update/Delete 작업을 수행하고 *델타* 테이블에 변경 사항을 자동으로 기록합니다. 데이터 원본에서 `DeltaSyncTableTTL` 값을 수정하여 레코드를 보관할 시간을 줄이거나 늘릴 수 있습니다. 데이터의 속도가 빠른 조직의 경우 이 시간을 짧게 유지하는 것이 합리적일 수 있습니다. 또는 클라이언트가 장기간 오프라인 상태인 경우에는 이 시간을 늘리는 것이 좋을 수 있습니다.

## 동기화 쿼리
<a name="sync-queries"></a>

*기본 쿼리*는 `lastSync` 값이 지정되지 않은 DynamoDB 동기화 작업입니다. 기본 쿼리는 시작 시 그 이후에는 정기적으로만 실행되기 때문에 이는 다수의 조직에 해당하는 내용입니다.

*델타 쿼리*는 `lastSync` 값이 지정된 DynamoDB 동기화 작업입니다. (기본 쿼리 주기가 실행되지 않는 한) *델타 쿼리*는 클라이언트의 상태가 오프라인에서 다시 온라인으로 전환될 때마다 실행됩니다. 클라이언트는 데이터를 동기화하기 위해 마지막으로 쿼리를 실행한 시간을 자동으로 추적합니다.

델타 쿼리가 실행되면 쿼리의 해석기에서 `ds_pk` 및 `ds_sk`를 사용하여 클라이언트가 마지막으로 동기화를 수행한 이후 변경된 레코드에 대해서만 쿼리합니다. 클라이언트는 적절한 GraphQL 응답을 저장합니다.

동기화 쿼리 실행에 대한 자세한 내용은 [동기화 작업 설명서](aws-appsync-conflict-detection-and-sync-sync-operations.md)를 참조하십시오.

## 예제
<a name="example"></a>

먼저 `createPost` 변형을 호출하여 항목을 만듭니다.

```
mutation create {
  createPost(input: {author: "Nadia", title: "My First Post", content: "Hello World"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

이 변형의 반환 값은 다음과 같습니다.

```
{
  "data": {
    "createPost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "My First Post",
      "content": "Hello World",
      "_version": 1,
      "_lastChangedAt": 1574469356331,
      "_deleted": null
    }
  }
}
```

*기본* 테이블의 내용을 검사하면 다음과 같은 레코드를 볼 수 있습니다.

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

*델타* 테이블의 내용을 검사하면 다음과 같은 레코드를 볼 수 있습니다.

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_ttl": {
    "N": "1574472956"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:35:56.331:81d36bbb-1579-4efe-92b8-2e3f679f628b:1"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

이제 `syncPosts` 쿼리와 같이 클라이언트가 로컬 데이터 저장소를 하이드레이트하기 위해 실행하는 *기본* 쿼리를 시뮬레이션할 수 있습니다:

```
query baseQuery {
  syncPosts(limit: 100, lastSync: null, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
      _lastChangedAt
    }
    startedAt
    nextToken
  }
}
```

이 *기본* 쿼리의 반환 값은 다음과 같습니다.

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "My First Post",
          "content": "Hello World",
          "_version": 1,
          "_lastChangedAt": 1574469356331
        }
      ],
      "startedAt": 1574469602238,
      "nextToken": null
    }
  }
}
```

나중에 `startedAt` 값을 저장하여 *델타* 쿼리를 시뮬레이션하겠지만 먼저 테이블을 변경해야 합니다. `updatePost` 변형을 사용하여 기존 게시물을 수정하겠습니다.

```
mutation updatePost {
  updatePost(input: {id: "81d36bbb-1579-4efe-92b8-2e3f679f628b", _version: 1, title: "Actually this is my Second Post"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

이 변형의 반환 값은 다음과 같습니다.

```
{
  "data": {
    "updatePost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "Actually this is my Second Post",
      "content": "Hello World",
      "_version": 2,
      "_lastChangedAt": 1574469851417,
      "_deleted": null
    }
  }
}
```

이제 *기본* 테이블의 내용을 검사하면 업데이트된 항목을 볼 수 있습니다.

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

이제 *델타* 테이블의 내용을 검사하면 두 개의 레코드를 볼 수 있습니다.

1. 항목이 생성되었을 때의 레코드

1. 항목이 업데이트되었을 때에 대한 레코드.

새 항목은 다음과 같습니다.

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_ttl": {
    "N": "1574473451"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:44:11.417:81d36bbb-1579-4efe-92b8-2e3f679f628b:2"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

이제 *델타* 쿼리를 시뮬레이션하여 클라이언트가 오프라인 상태일 때 발생한 수정 사항을 검색할 수 있습니다. *Base* 쿼리에서 반환된 `startedAt` 값을 사용하여 요청해 보겠습니다.

```
query delta {
  syncPosts(limit: 100, lastSync: 1574469602238, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
    }
    startedAt
    nextToken
  }
}
```

이 *델타* 쿼리의 반환 값은 다음과 같습니다.

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "Actually this is my Second Post",
          "content": "Hello World",
          "_version": 2
        }
      ],
      "startedAt": 1574470400808,
      "nextToken": null
    }
  }
}
```