

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

# AWS AppSync용 JavaScript 해석기 자습서
<a name="tutorials-js"></a>

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

**Topics**
+ [DynamoDB JavaScript 해석기를 사용하여 간단한 사후 애플리케이션 생성](tutorial-dynamodb-resolvers-js.md)
+ [AWS Lambda 해석기 사용](tutorial-lambda-resolvers-js.md)
+ [로컬 해석기 사용](tutorial-local-resolvers-js.md)
+ [GraphQL 해석기 결합](tutorial-combining-graphql-resolvers-js.md)
+ [OpenSearch Service 해석기 사용](tutorial-elasticsearch-resolvers-js.md)
+ [DynamoDB 트랜잭션 수행](tutorial-dynamodb-transact-js.md)
+ [DynamoDB 배치 작업 사용](tutorial-dynamodb-batch-js.md)
+ [HTTP 해석기 사용](tutorial-http-resolvers-js.md)
+ [Aurora PostgreSQL 및 데이터 API 사용](aurora-serverless-tutorial-js.md)

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

이 자습서에서는 Amazon DynamoDB 테이블을 로 가져오 AWS AppSync 고 연결하여 자체 애플리케이션에서 활용할 수 있는 JavaScript 파이프라인 해석기를 사용하여 완전한 기능을 갖춘 GraphQL API를 빌드합니다.

 AWS AppSync 콘솔을 사용하여 Amazon DynamoDB 리소스를 프로비저닝하고, 해석기를 생성하고, 데이터 소스에 연결합니다. 또한 GraphQL 명령문을 통해 Amazon DynamoDB 데이터베이스를 읽고 쓸 수 있으며 실시간 데이터를 구독할 수 있습니다.

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

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

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

1. AppSync 콘솔을 열고 **API 생성**을 선택합니다.

1. **처음부터 디자인**을 선택하고 **다음**을 선택합니다.

1. API `PostTutorialAPI`의 이름을 지정한 후 **다음**을 선택합니다. 나머지 옵션은 기본값으로 설정한 채 검토 페이지로 건너뛰고 `Create`를 선택합니다.

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

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

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

**스키마에 데이터를 추가하려면**

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

1. `Post` 객체를 추가하고 가져오기 위한 `Post` 유형과 작업 `addPost`를 정의하는 스키마를 만들 것입니다. **스키마** 창에서 파일 콘텐츠를 다음 코드로 바꿉니다.

   ```
   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. **스키마 저장(Save Schema)**을 선택합니다.

## Amazon DynamoDB 테이블 설정
<a name="configure-dynamodb"></a>

 AWS AppSync 콘솔은 Amazon DynamoDB 테이블에 자체 AWS 리소스를 저장하는 데 필요한 리소스를 프로비저닝하는 데 도움이 될 수 있습니다. 이 단계에서는 Amazon DynamoDB 테이블을 생성하여 게시물을 저장합니다. 또한 나중에 사용할 [보조 인덱스](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html)도 설정합니다.

**Amazon DynamoDB 테이블을 생성하려면**

1. **스키마** 페이지에서 **리소스 생성**을 선택합니다.

1. **기존 유형 사용**을 선택하고 `Post` 유형을 선택합니다.

1. **보조 인덱스** 섹션에서 **인덱스 추가**를 선택합니다.

1. 인덱스 이름을 `author-index`로 지정합니다.

1. `Primary key`를 `author`로 설정하고 `Sort` 키를 `None`으로 설정합니다.

1. **GraphQL 자동 생성**을 비활성화합니다. 이 예에서는 해석기를 직접 생성합니다.

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

이제 `PostTable`이라는 새 데이터 소스가 생겼습니다. 사이드 탭의 **데이터 소스**를 방문하면 이를 확인할 수 있습니다. 이 데이터 소스를 사용하여 쿼리와 뮤테이션을 Amazon DynamoDB 테이블에 연결합니다.

## addPost 해석기 설정(Amazon DynamoDB PutItem)
<a name="configure-addpost"></a>

이제 AWS AppSync 가 Amazon DynamoDB 테이블을 인식했으므로 해석기를 정의하여 개별 쿼리 및 변형에 연결할 수 있습니다. 생성한 첫 번째 해석기는 JavaScript를 사용하는 `addPost` 파이프라인 해석기로, 이 해석기를 사용하면 Amazon DynamoDB 테이블에 게시물을 작성할 수 있습니다. 파이프라인 해석기에는 다음 구성 요소가 있습니다.
+ 해석기를 연결할 GraphQL 스키마의 위치. 이 경우 `createPost` 형식의 `Mutation` 필드에서 해석기를 설정합니다. 이 해석기는 호출자가 뮤테이션 `{ addPost(...){...} }`를 호출하면 간접적으로 호출됩니다.
+ 이 해석기에 사용할 데이터 원본. 이 경우 앞서 정의한 DynamoDB 데이터 소스를 사용하려고 합니다. 따라서 `post-table-for-tutorial` DynamoDB 테이블에 항목을 추가할 수 있습니다.
+ 요청 핸들러. 요청 핸들러는 호출자의 수신 요청을 처리하고 DynamoDB에 대해 AWS AppSync 를 수행하기 위한 지침으로 변환하는 함수입니다.
+ 응답 핸들러. 응답 핸들러의 작업은 DynamoDB의 응답을 처리하여 GraphQL에 필요한 것으로 변환하는 것입니다. 이러한 템플릿은 DynamoDB의 데이터 모양과 GraphQL의 `Post` 형식이 다른 경우 유용하지만 이 경우 모양이 서로 동일하기 때문에 데이터를 그냥 전달하면 됩니다.

**해석기를 설정하려면**

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

1. **해석기** 창에서 `Mutation` 유형 아래의 `addPost` 필드를 찾은 다음 **연결**을 선택합니다.

1. 데이터 소스를 선택한 다음 **생성**을 선택합니다.

1. 코드 편집기에서 코드를 다음 코드 조각으로 바꿉니다.

   ```
   import { util } from '@aws-appsync/utils'
   import * as ddb from '@aws-appsync/utils/dynamodb'
   
   export function request(ctx) {
   	const item = { ...ctx.arguments, ups: 1, downs: 0, version: 1 }
   	const key = { id: ctx.args.id ?? util.autoId() }
   	return ddb.put({ key, item })
   }
   
   export function response(ctx) {
   	return ctx.result
   }
   ```

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

**참고**  
이 코드에서는 DynamoDB 요청을 쉽게 생성할 수 있는 DynamoDB 모듈 유틸리티를 사용합니다.

AWS AppSync 는 새 게시물의 ID를 생성하는 데 `util.autoId()`사용되는 라는 자동 ID 생성을 위한 유틸리티와 함께 제공됩니다. ID를 지정하지 않으면 유틸리티에서 ID를 자동으로 생성합니다.

```
const key = { id: ctx.args.id ?? util.autoId() }
```

JavaScript에 사용할 수 있는 유틸리티에 대한 자세한 내용은 [해석기 및 함수를 위한 JavaScript 런타임 기능](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html)을 참조하세요.

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

이제 해석기가 구성되었으므로 수신되는 `addPost` 변형을 Amazon DynamoDB `PutItem` 작업으로 변환할 AWS AppSync 수 있습니다. 이제 변형을 실행해 테이블에 데이터를 입력할 수 있습니다.

**작업을 실행하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다.

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

1. **실행**(주황색 재생 버튼)을 선택한 다음 `addPost`를 선택합니다. 새로 생성한 게시물의 결과가 **쿼리** 창 오른쪽에 있는 **결과** 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

   ```
   {
     "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
       }
     }
   }
   ```

다음 설명은 어떤 일이 발생했는지 보여줍니다.

1. AWS AppSync 가 `addPost` 변형 요청을 받았습니다.

1. AWS AppSync 는 해석기의 요청 핸들러를 실행합니다. `ddb.put` 함수는 다음과 같은 `PutItem` 요청을 생성합니다.

   ```
   {
     operation: 'PutItem',
     key: { id: { S: '123' } },
     attributeValues: {
       downs: { N: 0 },
       author: { S: 'AUTHORNAME' },
       ups: { N: 1 },
       title: { S: 'Our first post!' },
       version: { N: 1 },
       content: { S: 'This is our first post.' },
       url: { S: 'https://aws.amazon.com/appsync/' }
     }
   }
   ```

1. AWS AppSync 는이 값을 사용하여 Amazon DynamoDB `PutItem` 요청을 생성하고 실행합니다.

1. 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
   }
   ```

1. 응답 핸들러는 결과를 즉시 반환합니다(`return ctx.result`).

1. 최종 결과는 GraphQL 응답에서 확인할 수 있습니다.

## getPost 해석기 설정(Amazon DynamoDB GetItem)
<a name="configure-getpost"></a>

이제 Amazon DynamoDB 테이블에 데이터를 추가할 수 있으므로 DynamoDB 테이블에서 데이터를 검색할 수 있도록 쿼리를 설정해야 합니다. 이렇게 하기 위해 다른 해석기를 설정합니다.

**해석기를 추가하려면**

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

1. 오른쪽의 **해석기** 창에서 `Query` 유형 아래의 `getPost` 필드를 찾은 다음 **연결**을 선택합니다.

1. 데이터 소스를 선택한 다음 **생성**을 선택합니다.

1. 코드 편집기에서 코드를 다음 코드 조각으로 바꿉니다.

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb'
   	
   export function request(ctx) {
   	return ddb.get({ key: { id: ctx.args.id } })
   }
   
   export const response = (ctx) => ctx.result
   ```

1. 해석기를 저장합니다.

**참고**  
이 해석기에서는 응답 핸들러에 화살표 함수 표현식을 사용합니다.

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

해석기가 설정되었으므로 수신 `getPost` 쿼리를 Amazon DynamoDB `GetItem` 작업으로 변환하는 방법을 AWS AppSync 알아봅니다. 이제 앞에서 생성한 게시물을 검색하는 쿼리를 실행할 수 있습니다.

**쿼리를 실행하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에서 다음 코드를 추가하고 게시물을 만든 후 복사한 ID를 사용합니다.

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

1. **실행**(주황색 재생 버튼)을 선택한 다음 `getPost`를 선택합니다. 새로 생성한 게시물의 결과가 **쿼리** 창 오른쪽에 있는 **결과** 창에 나타나야 합니다.

1. Amazon 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
       }
     }
   }
   ```

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

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

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

```
import * as ddb from '@aws-appsync/utils/dynamodb'

export function request(ctx) {
	return ddb.get({
		key: { id: ctx.args.id },
		projection: ['author', 'id', 'title'],
	})
}

export const response = (ctx) => ctx.result
```

`getPost`와 함께 [selectionSetList](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference-js.html#aws-appsync-resolver-context-reference-info-js)를 사용하여 `expression`을 나타낼 수도 있습니다.

```
import * as ddb from '@aws-appsync/utils/dynamodb'

export function request(ctx) {
	const projection = ctx.info.selectionSetList.map((field) => field.replace('/', '.'))
	return ddb.get({ key: { id: ctx.args.id }, projection })
}

export const response = (ctx) => ctx.result
```

## updatePost 뮤테이션 생성(Amazon DynamoDB UpdateItem)
<a name="configure-updatepost"></a>

이제 Amazon DynamoDB에서 `Post` 객체를 생성하고 검색할 수 있습니다. 다음으로, 객체 업데이트를 위한 새 변형을 설정합니다. 모든 필드를 지정해야 하는 `addPost` 뮤테이션에 비해 이 뮤테이션을 사용하면 변경하려는 필드만 지정할 수 있습니다. 또한 수정하려는 버전을 지정할 수 있는 새 `expectedVersion` 인수도 도입되었습니다. 객체의 최신 버전을 수정하고 있는지 확인하는 조건을 설정해야 합니다. 이 작업은 `UpdateItem` Amazon DynamoDB operation.sc를 사용하여 수행합니다.

**해석기를 업데이트하려면**

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

1. **스키마** 창에서 다음과 같이 `Mutation` 형식을 수정하여 새로운 `updatePost` 변형을 추가할 수 있습니다.

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

1. **스키마 저장(Save Schema)**을 선택합니다.

1. 오른쪽의 **해석기** 창에서 `Mutation` 유형에 새로 생성된 `updatePost` 필드를 찾은 다음 **연결**을 선택합니다. 아래 코드 조각을 사용하여 새 해석기를 만듭니다.

   ```
   import { util } from '@aws-appsync/utils';
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { id, expectedVersion, ...rest } = ctx.args;
     const values = Object.entries(rest).reduce((obj, [key, value]) => {
       obj[key] = value ?? ddb.operations.remove();
       return obj;
     }, {});
   
     return ddb.update({
       key: { id },
       condition: { version: { eq: expectedVersion } },
       update: { ...values, version: ddb.operations.increment(1) },
     });
   }
   
   export function response(ctx) {
     const { error, result } = ctx;
     if (error) {
       util.appendError(error.message, error.type);
     }
     return result;
   ```

1. 변경한 내용을 모두 저장합니다.

이 해석기는 `ddb.update`를 사용하여 Amazon DynamoDB `UpdateItem` 요청을 생성합니다. 전체 항목을 쓰는 대신 특정 속성을 업데이트하도록 Amazon DynamoDB에 요청합니다. Amazon DynamoDB 업데이트 표현식을 사용하여 요청합니다.

`ddb.update` 함수는 키와 업데이트 객체를 인수로 사용합니다. 그런 다음 들어오는 인수의 값을 확인합니다. 값이 `null`로 설정되면 DynamoDB `remove` 작업을 사용하여 DynamoDB 항목에서 값을 제거해야 한다는 신호를 보냅니다.

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

조건 표현식에 대한 자세한 내용은 [조건 표현식](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-condition-expressions) 설명서를 참조하세요.

`UpdateItem` 요청에 대한 자세한 내용은 [UpdateItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-updateitem) 설명서 및 [DynamoDB 모듈](https://docs.aws.amazon.com/appsync/latest/devguide/built-in-modules-js.html) 설명서를 참조하세요.

업데이트 표현식을 작성하는 방법에 대한 자세한 내용은 [DynamoDB UpdateExpressions 문서](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)를 참조하십시오.

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

새 해석기를 사용해 `Post` 객체를 업데이트해 보겠습니다.

**객체를 업데이트하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

   ```
   mutation updatePost {
     updatePost(
       id:123
       title: "An empty story"
       content: null
       expectedVersion: 1
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `updatePost`를 선택합니다.

1. Amazon 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": 2
       }
     }
   }
   ```

이 요청에서는 AWS AppSync 및 Amazon DynamoDB에 `title` 및 `content` 필드만 업데이트하도록 요청했습니다. `version` 필드를 증분하는 것을 제외하고 다른 모든 필드는 그대로 둡니다. `title` 속성을 새 값으로 설정하고 게시물에서 `content` 속성을 제거했습니다. `author`, `url`, `ups` 및 `downs` 필드는 그대로 남아 있습니다. 뮤테이션 요청을 다시 실행해도 요청은 정확하게 그대로 유지됩니다. 다음과 유사한 응답이 나타납니다.

```
{
  "data": {
    "updatePost": null
  },
  "errors": [
    {
      "path": [
        "updatePost"
      ],
      "data": null,
      "errorType": "DynamoDB:ConditionalCheckFailedException",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 1RR3QN5F35CS8IV5VR4OQO9NNBVV4KQNSO5AEMVJF66Q9ASUAAJG)"
    }
  ]
}
```

조건 표현식이 `false`로 평가되었으므로 요청에 실패합니다.

1. 요청을 처음 실행했을 때 Amazon DynamoDB에 있는 게시물의 `version` 필드 값은 `1`이었고, 이는 `expectedVersion` 인수와 일치했습니다. 요청에 성공하자 Amazon DynamoDB의 `version` 필드가 `2`로 증가했습니다.

1. 요청을 두 번째로 실행했을 때 Amazon DynamoDB에 있는 게시물의 `version` 필드 값은 `2`이었고, 이는 `expectedVersion` 인수와 일치하지 않았습니다.

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

## 투표 뮤테이션 생성(Amazon DynamoDB UpdateItem)
<a name="configure-vote-mutations"></a>

`Post` 유형에는 추천과 비추천을 기록할 수 있는 `ups` 및 `downs` 필드가 포함되어 있습니다. 하지만 현재로서는 API를 통해 아무것도 할 수 없습니다. 게시물을 추천 및 비추천하도록 하는 뮤테이션을 추가해 보겠습니다.

**뮤테이션을 추가하려면**

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

1. **스키마** 창에서 `Mutation` 유형을 수정하고 `DIRECTION` 열거형을 추가하여 새 투표 뮤테이션을 추가합니다.

   ```
   type Mutation {
       vote(id: ID!, direction: DIRECTION!): Post
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       addPost(
           id: ID,
           author: String!,
           title: String!,
           content: String!,
           url: String!
       ): Post!
   }
   
   enum DIRECTION {
     UP
     DOWN
   }
   ```

1. **스키마 저장(Save Schema)**을 선택합니다.

1. 오른쪽의 **해석기** 창에서 `Mutation` 유형에 새로 생성된 `vote` 필드를 찾은 다음 **연결**을 선택합니다. 코드를 생성하고 다음 코드 조각으로 대체하여 새 해석기를 만듭니다.

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const field = ctx.args.direction === 'UP' ? 'ups' : 'downs';
     return ddb.update({
       key: { id: ctx.args.id },
       update: {
         [field]: ddb.operations.increment(1),
         version: ddb.operations.increment(1),
       },
     });
   }
   
   export const response = (ctx) => ctx.result;
   ```

1. 변경한 내용을 모두 저장합니다.

### 게시물을 추천 또는 비추천하는 API 직접 호출
<a name="call-api-vote"></a>

이제 새 해석기가 설정되었으므로 수신 `upvotePost` 또는 `downvote` 변형을 Amazon DynamoDB `UpdateItem` 작업으로 변환하는 방법을 AWS AppSync 알아봅니다. 이제 앞서 생성한 게시물을 추천 또는 비추천하는 변형을 실행할 수 있습니다.

**뮤테이션을 추가하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

   ```
   mutation votePost {
     vote(id:123, direction: UP) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `votePost`를 선택합니다.

1. Amazon DynamoDB에서 업데이트된 게시물이 **쿼리** 창 오른쪽에 있는 **결과** 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

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

1. **실행**을 몇 번 더 선택합니다. 쿼리를 실행할 때마다 `ups` 및 `version` 필드가 `1`씩 증가하는 것이 보여야 합니다.

1. 쿼리를 변경하여 다른 `DIRECTION`으로 호출합니다.

   ```
   mutation votePost {
     vote(id:123, direction: DOWN) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `votePost`를 선택합니다.

   이번에는 쿼리를 실행할 때마다 `downs` 및 `version` 필드가 `1`씩 증가하는 것이 보여야 합니다.

## deletePost 해석기 설정(Amazon DynamoDB DeleteItem)
<a name="configure-deletepost"></a>

다음으로 게시물을 삭제하기 위한 뮤테이션을 생성해야 합니다. 이 작업은 `DeleteItem` Amazon DynamoDB 작업을 사용하여 수행합니다.

**뮤테이션을 추가하려면**

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

1. **스키마** 창에서 다음과 같이 `Mutation` 유형을 수정하여 새로운 `deletePost` 뮤테이션을 추가합니다.

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

1. 이번에는 `expectedVersion` 필드를 선택 사항으로 설정했습니다. 다음으로 **스키마 저장**을 선택합니다.

1. 오른쪽의 **해석기** 창에서 `Mutation` 유형에 새로 생성된 `delete` 필드를 찾은 다음 **연결**을 선택합니다. 다음 코드를 사용하여 새 해석기를 생성합니다.

   ```
   import { util } from '@aws-appsync/utils'
   
   import { util } from '@aws-appsync/utils';
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     let condition = null;
     if (ctx.args.expectedVersion) {
       condition = {
         or: [
           { id: { attributeExists: false } },
           { version: { eq: ctx.args.expectedVersion } },
         ],
       };
     }
     return ddb.remove({ key: { id: ctx.args.id }, condition });
   }
   
   export function response(ctx) {
     const { error, result } = ctx;
     if (error) {
       util.appendError(error.message, error.type);
     }
     return result;
   }
   ```
**참고**  
참고: `expectedVersion` 인수는 선택적 인수입니다. 호출자가 요청에서 `expectedVersion` 인수를 설정하면, 요청 핸들러에서는 항목이 이미 삭제된 경우 또는 Amazon DynamoDB에 있는 `version` 속성이 `expectedVersion`과 정확하게 일치하는 경우, `DeleteItem` 요청이 완료되게 하는 조건을 추가합니다. 그냥 두면 `DeleteItem` 요청에 아무런 조건 표현식도 지정되지 않습니다. 이는 `version`의 값이나 해당 항목이 Amazon DynamoDB에 존재하는지 여부에 관계없이 완료됩니다.  
항목을 삭제하고 있더라도 항목이 아직 삭제되지 않은 경우 삭제된 항목을 반환할 수 있습니다.

`DeleteItem` 요청에 대한 자세한 내용은 [DeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-deleteitem) 설명서를 참조하세요.

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

해석기가 설정되었으므로 들어오는 `delete` 변형을 Amazon DynamoDB `DeleteItem` 작업으로 변환하는 방법을 AWS AppSync 알아봅니다. 이제 변형을 실행해 테이블에 데이터를 삭제할 수 있습니다.

**뮤테이션을 추가하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

   ```
   mutation deletePost {
     deletePost(id:123) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `deletePost`를 선택합니다.

1. Amazon DynamoDB에서 해당 게시물이 삭제됩니다. 는 Amazon DynamoDB에서 삭제된 항목의 값을 AWS AppSync 반환합니다.이 값은 **쿼리** 창 오른쪽에 있는 **결과** 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

   ```
   {
     "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
       }
     }
   }
   ```

1. `deletePost`에 대한 호출이 Amazon DynamoDB에서 실제로 삭제된 항목인 경우에만 값이 반환됩니다. **실행**을 다시 선택합니다.

1. 호출에는 계속 성공하지만 반환되는 값은 없습니다.

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

1. 이제 게시물을 삭제해 보는데, 이번에는 `expectedValue`를 지정해 보겠습니다. 지금까지 작업한 게시물 하나를 방금 삭제했기 때문에 먼저 새 게시물을 생성해야 합니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다.

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

1. **실행**(주황색 재생 버튼)을 선택한 다음 `addPost`를 선택합니다.

1. 새로 생성한 게시물의 결과가 **쿼리** 창 오른쪽에 있는 **결과** 창에 나타나야 합니다. 곧 필요할 것이기 때문에 새로 생성한 객체의 `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
       }
     }
   }
   ```

1. 이제 **expectedVersion**의 값이 잘못된 게시물을 삭제해 보겠습니다. **쿼리** 창에 다음 뮤테이션을 추가합니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

   ```
   mutation deletePost {
     deletePost(
       id:123
       expectedVersion: 9999
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `deletePost`를 선택합니다. 다음 결과가 반환됩니다.

   ```
   {
     "data": {
       "deletePost": null
     },
     "errors": [
       {
         "path": [
           "deletePost"
         ],
         "data": null,
         "errorType": "DynamoDB:ConditionalCheckFailedException",
         "errorInfo": null,
         "locations": [
           {
             "line": 2,
             "column": 3,
             "sourceName": null
           }
         ],
         "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 7083O037M1FTFRK038A4CI9H43VV4KQNSO5AEMVJF66Q9ASUAAJG)"
       }
     ]
   }
   ```

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

   ```
   mutation deletePost {
     deletePost(
       id:123
       expectedVersion: 1
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `deletePost`를 선택합니다.

   이번에는 요청에 성공하고 Amazon 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
       }
     }
   }
   ```

1. **실행**을 다시 선택합니다. 호출에 계속해서 성공하지만 이번에는 게시물이 이미 Amazon DynamoDB에서 삭제되었으므로 반환되는 값이 없습니다.

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

## allPost 해석기 설정(Amazon DynamoDB 스캔)
<a name="configure-allpost"></a>

지금까지 API는 살펴보고자 하는 각 게시물의 `id`를 아는 경우에만 유용했습니다. 테이블의 게시물을 모두 반환하는 새 해석기를 추가해 보겠습니다.

**뮤테이션을 추가하려면**

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

1. **스키마** 창에서 다음과 같이 `Query` 형식을 수정하여 새로운 `allPost` 쿼리를 추가할 수 있습니다.

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

1. 새로운 `PaginationPosts` 형식을 다음과 같이 추가합니다.

   ```
   type PaginatedPosts {
       posts: [Post!]!
       nextToken: String
   }
   ```

1. **스키마 저장(Save Schema)**을 선택합니다.

1. 오른쪽의 **해석기** 창에서 `Query` 유형에 새로 생성된 `allPost` 필드를 찾은 다음 **연결**을 선택합니다. 다음 코드를 사용하여 새 해석기를 생성합니다.

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken } = ctx.arguments;
     return ddb.scan({ limit, nextToken });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

   이 해석기의 요청 핸들러에는 두 개의 선택적 인수가 필요합니다.
   + `limit` - 단일 호출에서 반환할 최대 항목 수를 지정합니다.
   + `nextToken` - 다음 결과 세트를 검색하는 데 사용됩니다(`nextToken` 값의 출처는 나중에 설명).

1. 해석기의 모든 변경 사항을 저장합니다.

`Scan` 요청에 대한 자세한 내용은 [스캔](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-scan) 참조 문서를 참조하세요.

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

해석기가 설정되었으므로 수신 `allPost` 쿼리를 Amazon DynamoDB `Scan` 작업으로 변환하는 방법을 AWS AppSync 알아봅니다. 이제 테이블을 스캔해 게시물을 모두 검색할 수 있습니다. 지금까지 작업했던 데이터를 모두 삭제했기 때문에 이 요청을 수행하기 전에 몇 가지 데이터로 테이블을 채워야 합니다.

**데이터를 추가 및 쿼리하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다.

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

1. **실행**(주황색 재생 버튼)을 선택합니다.

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

   ```
   query allPost {
     allPost(limit: 5) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `allPost`를 선택합니다.

   처음 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": "<token>"
       }
     }
   }
   ```

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

   ```
   query allPost {
     allPost(
       limit: 5
       nextToken: "<token>"
     ) {
       posts {
         id
         author
       }
       nextToken
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `allPost`를 선택합니다.

   나머지 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 해석기 설정(Amazon DynamoDB 쿼리)
<a name="configure-query"></a>

Amazon DynamoDB에서 모든 게시물을 스캔하는 것 이외에 Amazon DynamoDB를 쿼리해 특정 작성자가 생성한 게시물을 검색할 수 있습니다. 앞서 이미 생성한 Amazon DynamoDB 테이블에는 특정 작성자가 생성한 게시물을 모두 검색하는 `Query` 작업에 사용할 수 있는 `author-index`라는 `GlobalSecondaryIndex`가 있습니다.

**쿼리를 추가하려면**

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

1. **스키마** 창에서 다음과 같이 `Query` 형식을 수정하여 새로운 `allPostsByAuthor` 쿼리를 추가할 수 있습니다.

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

   참고: 여기서는 `allPost` 쿼리에 사용한 것과 동일한 `PaginatedPosts` 유형을 사용합니다.

1. **스키마 저장(Save Schema)**을 선택합니다.

1. 오른쪽의 **해석기** 창에서 `Query` 유형에 새로 생성된 `allPostsByAuthor` 필드를 찾은 다음 **연결**을 선택합니다. 아래 코드 조각을 사용하여 해석기를 만듭니다.

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken, author } = ctx.arguments;
     return ddb.query({
       index: 'author-index',
       query: { author: { eq: author } },
       limit,
       nextToken,
     });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

   `allPost` 해석기와 마찬가지로 이 해석기에는 두 개의 선택적 인수가 있습니다.
   + `limit` - 단일 호출에서 반환할 최대 항목 수를 지정합니다.
   + `nextToken` - 다음 결과 세트를 검색합니다(`nextToken`의 값은 이전 호출에서 가져올 수 있음).

1. 해석기의 모든 변경 사항을 저장합니다.

`Query` 요청에 대한 자세한 내용은 [쿼리](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-query) 참조 문서를 참조하세요.

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

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

하지만 그 전에, 지금까지 모든 게시물의 작성자가 동일하므로 테이블을 몇 개의 게시물로 더 채워 보겠습니다.

**데이터를 추가하고 쿼리하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다.

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

1. **실행**(주황색 재생 버튼)을 선택한 다음 `addPost`를 선택합니다.

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

   ```
   query allPostsByAuthor {
     allPostsByAuthor(author: "Nadia") {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `allPostsByAuthor`를 선택합니다. `Nadia`가 작성한 모든 게시물이 **쿼리** 창 오른쪽에 있는 **결과** 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

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

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

1. **쿼리** 창에 다음 쿼리를 추가합니다.

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "AUTHORNAME"
       limit: 5
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `allPostsByAuthor`를 선택합니다. `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": "<token>"
       }
     }
   }
   ```

1. 다음과 같이 이전 쿼리에서 반환된 값으로 `nextToken` 인수를 업데이트합니다.

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "AUTHORNAME"
       limit: 5
       nextToken: "<token>"
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `allPostsByAuthor`를 선택합니다. `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` 유형은 플랫 키/값 객체였습니다. 또한 해석기를 사용하여 집합, 목록 및 맵 등과 같은 복잡한 객체를 모델링할 수 있습니다. 태그를 포함하도록 `Post` 형식을 업데이트해 보겠습니다. 게시물에는 태그가 0개 이상 있을 수 있는데, 태그는 DynamoDB에 문자열 집합으로 저장됩니다. 또한 태그를 추가 및 제거하는 몇 가지 변형과 특정 태그가 지정된 게시물을 스캔하는 새 쿼리를 설정해 볼 것입니다.

**데이터를 설정하려면**

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

1. **스키마** 창에서 다음과 같이 `Post` 형식을 수정하여 새로운 `tags` 필드를 추가할 수 있습니다.

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

1. **스키마** 창에서 다음과 같이 `Query` 형식을 수정하여 새로운 `allPostsByTag` 쿼리를 추가할 수 있습니다.

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

1. **스키마** 창에서 다음과 같이 `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!
   }
   ```

1. **스키마 저장(Save Schema)**을 선택합니다.

1. 오른쪽의 **해석기** 창에서 `Query` 유형에 새로 생성된 `allPostsByTag` 필드를 찾은 다음 **연결**을 선택합니다. 아래 코드 조각을 사용하여 해석기를 만듭니다.

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken, tag } = ctx.arguments;
     return ddb.scan({ limit, nextToken, filter: { tags: { contains: tag } } });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

1. 해석기의 모든 변경 사항을 저장합니다.

1. 이제 아래 코드 조각을 사용하여 `Mutation` 필드 `addTag`에 대해 동일한 작업을 수행합니다:
**참고**  
DynamoDB 유틸리티는 현재 집합 작업을 지원하지 않지만, 직접 요청을 작성하여 집합과 상호 작용할 수 있습니다.

   ```
   import { util } from '@aws-appsync/utils'
   
   export function request(ctx) {
   	const { id, tag } = ctx.arguments
   	const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 })
   	expressionValues[':tags'] = util.dynamodb.toStringSet([tag])
   
   	return {
   		operation: 'UpdateItem',
   		key: util.dynamodb.toMapValues({ id }),
   		update: {
   			expression: `ADD tags :tags, version :plusOne`,
   			expressionValues,
   		},
   	}
   }
   
   export const response = (ctx) => ctx.result
   ```

1. 해석기의 모든 변경 사항을 저장합니다.

1. 아래 코드 조각을 사용하여 `Mutation` 필드 `removeTag`에 대해 이 작업을 한 번 더 반복합니다.

   ```
   import { util } from '@aws-appsync/utils';
   	
   export function request(ctx) {
   	  const { id, tag } = ctx.arguments;
   	  const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 });
   	  expressionValues[':tags'] = util.dynamodb.toStringSet([tag]);
   	
   	  return {
   	    operation: 'UpdateItem',
   	    key: util.dynamodb.toMapValues({ id }),
   	    update: {
   	      expression: `DELETE tags :tags ADD version :plusOne`,
   	      expressionValues,
   	    },
   	  };
   	}
   	
   	export const response = (ctx) => ctx.resultexport
   ```

1. 해석기의 모든 변경 사항을 저장합니다.

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

이제 해석기를 설정했으므로 들어오는 , `addTag` `removeTag`및 `allPostsByTag` 요청을 DynamoDB `UpdateItem` 및 `Scan` 작업으로 변환하는 방법을 AWS AppSync 알아봅니다. 실행해 보기 위해 이전에 생성한 게시물 중 선택해 보겠습니다. 예를 들어, `Nadia`에서 작성한 게시물을 사용해 보겠습니다.

**태그를 사용하려면**

1. API에서 **쿼리** 탭을 선택합니다.

1. **쿼리** 창에 다음 쿼리를 추가합니다.

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "Nadia"
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `allPostsByAuthor`를 선택합니다.

1. Nadia의 모든 게시물이 **쿼리** 창 오른쪽에 있는 **결과** 창에 나타나야 합니다. 예를 들면 다음과 같아야 합니다.

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

1. *세상에서 가장 귀여운 강아지*라는 제목이 붙은 게시물을 사용해 봅시다. 나중에 사용할 것이기 때문에 이 게시물의 `id`를 기록해 둡니다. 이제 `dog` 태그를 추가해 보겠습니다.

1. **쿼리** 창에 다음 뮤테이션을 추가합니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

   ```
   mutation addTag {
     addTag(id:10 tag: "dog") {
       id
       title
       tags
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `addTag`를 선택합니다. 다음과 같이 새 태그로 게시물이 업데이트됩니다.

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

1. 태그를 더 추가할 수 있습니다. `tag` 인수를 `puppy`로 변경하도록 뮤테이션을 업데이트합니다.

   ```
   mutation addTag {
     addTag(id:10 tag: "puppy") {
       id
       title
       tags
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `addTag`를 선택합니다. 다음과 같이 새 태그로 게시물이 업데이트됩니다.

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

1. 태그를 삭제할 수도 있습니다. **쿼리** 창에 다음 뮤테이션을 추가합니다. 또한 앞서 적어둔 값을 갖도록 `id` 인수를 업데이트해야 합니다.

   ```
   mutation removeTag {
     removeTag(id:10 tag: "puppy") {
       id
       title
       tags
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `removeTag`를 선택합니다. 게시물이 업데이트되고 `puppy` 태그가 삭제됩니다.

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

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

   ```
   query allPostsByTag {
     allPostsByTag(tag: "dog") {
       posts {
         id
         title
         tags
       }
       nextToken
     }
   }
   ```

1. **실행**(주황색 재생 버튼)을 선택한 다음 `allPostsByTag`를 선택합니다. `dog` 태그가 지정된 게시물이 다음과 같이 모두 반환됩니다.

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

## 결론
<a name="conclusion-dynamodb-tutorial-js"></a>

이 자습서에서는 AWS AppSync 및 GraphQL을 사용하여 DynamoDB의 `Post` 객체를 조작할 수 있는 API를 구축했습니다.

정리하려면 콘솔에서 AWS AppSync GraphQL API를 삭제하면 됩니다.

DynamoDB 테이블과 연결된 역할을 삭제하려면 **데이터 소스** 테이블에서 데이터 소스를 선택하고 **편집**을 클릭합니다. **새 역할 생성 또는 기존 역할 사용**에서 역할 값을 기록해 둡니다. IAM 콘솔로 이동하여 역할을 삭제합니다.

DynamoDB 테이블을 삭제하려면 데이터 소스 목록에서 테이블 이름을 클릭합니다. 그러면 테이블을 삭제할 수 있는 DynamoDB 콘솔로 이동합니다.

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

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

## 용 Powertools AWS Lambda
<a name="powertools-graphql"></a>

Powertools for AWS Lambda GraphQL 이벤트 핸들러는 Lambda 함수에서 GraphQL 이벤트의 라우팅 및 처리를 간소화합니다. 이는 Python 및 Typescript에서 사용할 수 있습니다. AWS Lambda 설명서용 Powertools의 GraphQL API 이벤트 핸들러에 대한 자세한 내용은 다음 참조를 참조하세요.
+ [Powertools for AWS Lambda GraphQL 이벤트 핸들러(Python) ](https://docs.aws.amazon.com/powertools/python/latest/core/event_handler/appsync/)
+ [Powertools for AWS Lambda GraphQL 이벤트 핸들러(Typescript)](https://docs.aws.amazon.com/powertools/typescript/latest/features/event-handler/appsync-graphql/) 

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

다음 예는 블로그 게시물 애플리케이션의 일부로 블로그 게시물에 대해 다양한 연산을 수행하는 `Node.js`(런타임: Node.js 18.x)로 작성된 Lambda 함수를 보여줍니다. 단, 코드는 확장명이.mis인 파일 이름에 저장해야 합니다.

```
export const handler = async (event) => {
console.log('Received event {}', JSON.stringify(event, 3))

  const 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', },
  }

  const 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.')
  let result
  switch (event.field) {
case 'getPost':
      return posts[event.arguments.id]
    case 'allPosts':
      return Object.values(posts)
    case 'addPost':
      // return the arguments back
return event.arguments
    case 'addPostErrorWithData':
      result = posts[event.arguments.id]
      // attached additional error information to the post
      result.errorMessage = 'Error with the mutation, data has changed'
      result.errorType = 'MUTATION_ERROR'
return result
    case 'relatedPosts':
      return relatedPosts[event.source.id]
    default:
      throw new Error('Unknown field, unable to resolve ' + event.field)
  }
}
```

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

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

 AWS 관리 콘솔을 사용하여이 Lambda 함수를 생성합니다.

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

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

**데이터 소스 생성**을 선택하고 친숙한 **데이터 소스 이름**(예: **Lambda**)을 입력한 다음 **데이터 소스 유형**에서 **AWS Lambda 함수**를 선택합니다. **리전**에서 함수와 동일한 리전을 선택합니다. **함수 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-js"></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-js"></a>

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

 AWS AppSync JavaScript(`APPSYNC_JS`) 런타임을 사용하고 Lambda 함수와 상호 작용하는 해석기를 생성합니다. JavaScript를 사용하여 AWS AppSync 해석기 및 함수를 작성하는 방법에 대한 자세한 내용은 [해석기 및 함수에 대한 JavaScript 런타임 기능을](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html) 참조하세요.

Lambda 매핑 템플릿에 대한 자세한 내용은 [Lambda용 JavaScript 해석기 함수 참조](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-lambda-js.html)를 참조하세요.

이 단계에서는 `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` 필드 옆에 있는 **연결을** 선택합니다. Lambda 데이터 소스를 선택합니다. 그런 다음, 다음 코드를 제공합니다.

```
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  return ctx.result;
}
```

이 해석기 코드는 Lambda 함수를 호출할 때 필드 이름, 인수 목록, 소스 객체에 대한 컨텍스트를 Lambda 함수에 전달합니다. **저장**을 선택합니다.

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

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

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

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

### addPost 변형
<a name="addpost-mutation-js"></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-js"></a>

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

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

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

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

필드 해상도를 지정하면 오류가 발생할 수 있습니다. AWS AppSync를 사용하면 다음 소스에서 오류가 발생할 수 있습니다.
+ 해석기 응답 핸들러
+ Lambda 함수

### 해석기 응답 핸들러에서
<a name="from-the-resolver-response-handler-js"></a>

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

다음 예시는 `Post.relatedPosts: [Post]` 해석기 응답 핸들러에서 이를 사용하는 방법을 보여줍니다.

```
// the Post.relatedPosts response handler
export function response(ctx) {
    util.error("Failed to fetch relatedPosts", "LambdaFailure", ctx.result)
    return ctx.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-js"></a>

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

**참고**  
Lambda 함수가 *unhandled* 오류를 발생시키는 경우 AWS AppSync는 Lambda에서 설정한 오류 메시지를 사용합니다.

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

```
export const handler = async (event) => {
  console.log('Received event {}', JSON.stringify(event, 3))
  throw new Error('I always fail.')
}
```

응답 핸들러에 오류가 수신되었습니다. `util.appendError`로 응답에 오류를 추가하여 GraphQL 응답으로 다시 보낼 수 있습니다. 이렇게 하려면 AWS AppSync 함수 응답 핸들러를 다음과 같이 변경합니다.

```
// the lambdaInvoke response handler
export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  }
  return result;
}
```

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

```
{
  "data": {
    "allPosts": null
  },
  "errors": [
    {
      "path": [
        "allPosts"
      ],
      "data": null,
      "errorType": "Lambda:Unhandled",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "I fail. always"
    }
  ]
}
```

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

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

```
query {
    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 {   // 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 함수 하나 대신에, 해당 게시물 배치에 대한 관련 게시물 목록을 해석합니다.

이를 설명하기 위해 일괄 처리를 처리하도록 `relatedPosts`의 해석기를 업데이트해 보겠습니다.

```
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  }
  return result;
}
```

이제 이 코드는 해석 중인 `fieldName`이 `relatedPosts`일 때 작업을 `Invoke`에서 `BatchInvoke`로 변경합니다. 이제 **일괄 처리 구성** 섹션에서 함수에 대한 일괄 처리를 활성화합니다. 설정된 최대 일괄 처리 크기를 `5`로 설정합니다. **저장**을 선택합니다.

이 변경으로 Lambda 함수는 `relatedPosts` 해결 시 다음을 입력으로 받습니다.

```
[
    {
        "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
]
```

Lambda 코드를 업데이트하여 `relatedPosts`에 대한 일괄 처리를 처리할 수 있습니다.

```
export const handler = async (event) => {
  console.log('Received event {}', JSON.stringify(event, 3))
  //throw new Error('I fail. always')

  const 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', },
  }

  const relatedPosts = {
    1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }
  
  if (!event.field && event.length){
    console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`);
    return event.map(e => relatedPosts[e.source.id])
  }

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

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

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

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

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

```
export const handler = async (event) => {
console.log('Received event {}', JSON.stringify(event, 3))
  // throw new Error('I fail. always')
const 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', },
  }

  const relatedPosts = {
1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }
  
  if (!event.field && event.length){
console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`);
    return event.map(e => {
// return an error for post 2
if (e.source.id === '2') {
return { 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' }
      }
      return {data: relatedPosts[e.source.id]}
      })
  }

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

`relatedPosts` 해석기 코드 업데이트:

```
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  } else if (result.errorMessage) {
    util.appendError(result.errorMessage, result.errorType, result.data)
  } else if (ctx.info.fieldName === 'relatedPosts') {
      return result.data
  } else {
      return result
  }
}
```

이제 응답 핸들러는 `Invoke` 작업에 대해 Lambda 함수가 반환한 오류를 확인하고, `BatchInvoke` 작업에서 개별 항목에 대해 반환한 오류를 확인한 다음, 마지막으로 `fieldName`을 확인합니다. `relatedPosts`의 경우 함수는 `result.data`를 반환합니다. 다른 모든 필드의 경우 함수가 `result`를 반환합니다. 그 예로, 아래 쿼리를 살펴보겠습니다.

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

이 쿼리는 다음과 유사한 GraphQL 응답을 반환합니다.

```
{
  "data": {
    "allPosts": [
      {
        "id": "1",
        "relatedPosts": [
          {
            "id": "4"
          }
        ]
      },
      {
        "id": "2",
        "relatedPosts": null
      },
      {
        "id": "3",
        "relatedPosts": [
          {
            "id": "2"
          },
          {
            "id": "1"
          }
        ]
      },
      {
        "id": "4",
        "relatedPosts": [
          {
            "id": "2"
          },
          {
            "id": "1"
          }
        ]
      },
      {
        "id": "5",
        "relatedPosts": []
      }
    ]
  },
  "errors": [
    {
      "path": [
        "allPosts",
        1,
        "relatedPosts"
      ],
      "data": null,
      "errorType": "ERROR",
      "errorInfo": null,
      "locations": [
        {
          "line": 4,
          "column": 5,
          "sourceName": null
        }
      ],
      "message": "Error Happened"
    }
  ]
}
```

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

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

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --code "<code-goes-here>" \
 --runtime name=APPSYNC_JS,runtimeVersion=1.0.0 \
 --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

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

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

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

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

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

## 게시/구독 앱 만들기
<a name="create-the-pub-sub-application-js"></a>

먼저 GraphQL API를 생성할 때 **처음부터 디자인** 옵션을 선택하고 선택적 세부 정보를 구성하여 빈 GraphQL API를 생성합니다.

게시/구독 애플리케이션에서 클라이언트는 메시지를 구독하고 게시할 수 있습니다. 게시된 각 메시지에는 이름과 데이터가 포함됩니다. 스키마에 다음을 추가합니다.

```
type Channel {
	name: String!
	data: AWSJSON!
}

type Mutation {
	publish(name: String!, data: AWSJSON!): Channel
}

type Query {
	getChannel: Channel
}

type Subscription {
	subscribe(name: String!): Channel
		@aws_subscribe(mutations: ["publish"])
}
```

다음으로 `Mutation.publish` 필드에 해석기를 연결해 보겠습니다. **스키마** 창 옆의 **해석기** 창에서 `Mutation` 유형을 찾고 `publish(...): Channel` 필드를 찾은 다음 **연결**을 클릭합니다.

*없음* 새 데이터 소스를 생성하고 이름을 *PageDataSource*로 지정합니다. 해석기에 연결합니다.

다음 코드 조각을 사용하여 해석기 구현을 추가합니다.

```
export function request(ctx) {
  return { payload: ctx.args };
}

export function response(ctx) {
  return ctx.result;
}
```

해석기를 만들고 변경한 내용을 저장했는지 확인합니다.

## 메시지 전송 및 구독
<a name="send-and-subscribe-to-messages-js"></a>

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

**쿼리** 창에서 `SubscribeToData` 구독을 실행합니다.

```
subscription SubscribeToData {
    subscribe(name:"channel") {
        name
        data
    }
}
```

 구독자는 `publish` 뮤테이션이 호출될 때마다 메시지를 수신하지만 메시지가 `channel` 구독으로 전송될 때만 수신됩니다. **쿼리** 창에서 이 방법을 시도해 보겠습니다. 콘솔에서 구독이 계속 실행되는 동안 다른 콘솔을 열고 **쿼리** 창에서 다음 요청을 실행합니다.

**참고**  
이 예시에서는 유효한 JSON 문자열을 사용합니다.

```
mutation PublishData {
    publish(data: "{\"msg\": \"hello world!\"}", name: "channel") {
        data
        name
    }
}
```

결과는 다음과 같습니다.

```
{
  "data": {
    "publish": {
      "data": "{\"msg\":\"hello world!\"}",
      "name": "channel"
    }
  }
}
```

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

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

GraphQL 스키마의 해석기와 필드는 매우 뛰어난 유연성으로 1:1 관계를 갖습니다. 데이터 소스는 스키마와 독립적으로 해석기에 구성되므로, 다양한 데이터 소스를 통해 GraphQL 유형을 확인하거나 조작할 수 있으므로 필요에 따라 스키마를 혼합하고 일치시킬 수 있습니다.

다음 시나리오는 스키마에서 데이터 소스를 혼합하고 일치시키는 방법을 보여줍니다. 시작하기 전에, Amazon DynamoDB 및 AWS Lambda Amazon OpenSearch Service에 대한 데이터 소스 및 해석기 구성에 익숙해야 합니다.

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

다음 스키마에는 각각 3개의 `Query` 및 `Mutation` 작업이 있는 `Post` 유형이 있습니다.

```
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개의 해석기가 있습니다. 이 문제를 해결하는 한 가지 방법은 `Posts`라는 단일 Amazon DynamoDB 테이블에 연결하여 `AllPost` 필드는 스캔을 실행하고 `searchPosts` 필드는 쿼리를 실행하는 것입니다([DynamoDB용 JavaScript 해석기 함수 참조](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html) 참조). 그러나 Amazon DynamoDB에만 국한되지는 않습니다. 비즈니스 요구 사항을 충족하기 위해 Lambda 또는 OpenSearch Service와 같은 다양한 데이터 소스가 존재합니다.

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

 AWS AppSync 데이터 소스에서 직접 지원하지 않는 타사 데이터베이스의 결과를 반환해야 할 수 있습니다. 또한 데이터를 API 클라이언트에 반환하기 전에 복잡한 수정을 수행해야 할 수도 있습니다. 이는 클라이언트의 타임스탬프 차이 또는 이전 버전과의 호환성 문제 처리와 같은 데이터 형식의 부적절한 서식으로 인해 발생할 수 있습니다. 이 경우 AWS Lambda 함수를 AWS AppSync API에 데이터 소스로 연결하는 것이 적절한 솔루션입니다. 설명을 위해 다음 예제에서 AWS Lambda 함수는 타사 데이터 스토어에서 가져온 데이터를 조작합니다.

```
export const handler = (event, context, callback) => {
    // fetch data
    const result = fetcher()

    // apply complex business logic
    const data = transform(result)	

    // return to AppSync
    return data
};
```

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

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

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

이러한 특정 데이터 소스로 시작하려면 [DynamoDB](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-resolvers-js.html) 및 [Lambda](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers-js.html) 자습서를 참조하세요.

예를 들어, 이전 튜토리얼의 스키마를 사용하여 다음 뮤테이션을 수행하면 DynamoDB에 항목이 추가됩니다.

```
mutation addPost {
  addPost(
    id: 123
    author: "Nadia"
    title: "Our first post!"
    content: "This is our first post."
    url: "https://aws.amazon.com/appsync/"
  ) {
    id
    author
    title
    content
    url
    ups
    downs
    version
  }
}
```

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

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

---------- or ----------

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

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

**참고**  
이 Python 코드는 예시이며 프로덕션 코드에 사용하기 위한 것이 아닙니다.

```
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = '' # e.g. us-east-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)

host = '' # the OpenSearch Service domain, e.g. https://search-mydomain.us-west-1.es.amazonaws.com
index = 'lambda-index'
datatype = '_doc'
url = host + '/' + index + '/' + datatype + '/'

headers = { "Content-Type": "application/json" }

def handler(event, context):
    count = 0
    for record in event['Records']:
        # Get the primary key for use as the OpenSearch ID
        id = record['dynamodb']['Keys']['id']['S']

        if record['eventName'] == 'REMOVE':
            r = requests.delete(url + id, auth=awsauth)
        else:
            document = record['dynamodb']['NewImage']
            r = requests.put(url + id, auth=awsauth, json=document, headers=headers)
        count += 1
    return str(count) + ' records processed.'
```

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

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

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

자세한 내용은 [OpenSearch용 JavaScript 해석기 함수 참조](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-elasticsearch-js.html)를 참조하세요.

## 새 OpenSearch Service 도메인 생성
<a name="create-a-new-es-domain-js"></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
```

계정의 US-West-2(오레곤) 리전에서 다음 AWS 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/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-js"></a>

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

또한 해당 역할에 대해 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 도메인에 대한 적정 작업 및 리소스를 사용하여 아래와 비슷한 정책을 추가해야 합니다. 보안 **주체**는 AWS AppSync 데이터 소스 역할이며, 이는 해당 콘솔에서 생성하도록 허용한 경우 IAM 콘솔에서 찾을 수 있습니다.

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

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

```
 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`를 찾아 **연결**을 선택합니다. OpenSearch Service 데이터 소스를 선택하고 해석기를 저장합니다. 아래 코드 조각을 사용하여 해석기 코드를 업데이트합니다.

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by using an input term
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: `/post/_search`,
		params: { body: { from: 0, size: 50 } },
	}
}

/**
 * Returns the fetched items
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result.hits.hits.map((hit) => hit._source)
}
```

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

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

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

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

**해석기** 창에서 `allPostsByAuthor`를 찾아 **연결**을 선택합니다. OpenSearch Service 데이터 소스를 선택하고 다음 코드를 사용합니다.

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: '/post/_search',
		params: {
			body: {
				from: 0,
				size: 50,
				query: { match: { author: ctx.args.author } },
			},
		},
	}
}

/**
 * Returns the fetched items
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result.hits.hits.map((hit) => hit._source)
}
```

`body`가 `author` 필드에 대한 쿼리 용어로 채워져서 클라이언트에서 인수로 전달됩니다. 선택 사항으로 표준 텍스트와 같이 미리 채워진 정보를 사용할 수도 있습니다.

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

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

 AWS AppSync 콘솔의 **스키마** 페이지로 돌아가 `addPost()` 변형에 **** 연결을 선택합니다. OpenSearch Service 데이터 소스를 다시 선택하고 다음 코드를 사용합니다.

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'PUT',
		path: `/post/_doc/${ctx.args.id}`,
		params: { body: ctx.args },
	}
}

/**
 * Returns the inserted post
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result
}
```

전과 마찬가지로, 이 코드도 데이터의 구조화 방식을 보여주는 예입니다. 다양한 필드 이름 또는 인덱스가 있으면 `path` 및 `body`를 업데이트해야 합니다. 이 예는 요청 핸들러에서 `ctx.args`로 작성할 수도 있는 `context.arguments`를 사용하는 방법도 보여줍니다.

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

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

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: `/post/_doc/${ctx.args.id}`,
	}
}

/**
 * Returns the post
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result._source
}
```

## 쿼리 및 뮤테이션 수행
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations-js"></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 search {
    searchPosts {
        id
        title
        author
        content
    }
}
```

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

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

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

## 권한
<a name="permissions-js"></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-js"></a>

간단하게 설명하기 위해 이 자습서에서 사용되는 모든 해석기에 대해 동일한 데이터 원본을 사용합니다.

`accountNumber`를 파티션 키로 사용하는 **savingAccounts** 및 **checkingAccounts**라는 두 개의 테이블과 `transactionId`를 파티션 키로 사용하는 **transactionHistory** 테이블이 있습니다. 아래 CLI 명령을 사용하여 테이블을 만들 수 있습니다. `region`을 해당 리전으로 바꿔야 합니다.

**CLI 사용**

```
aws dynamodb create-table --table-name savingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name checkingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name transactionHistory \
  --attribute-definitions AttributeName=transactionId,AttributeType=S \
  --key-schema AttributeName=transactionId,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region
```

 AWS AppSync 콘솔의 **데이터 소스**에서 새 DynamoDB 데이터 소스를 생성하고 이름을 **TransactTutorial**으로 지정합니다. **savingAccounts**를 테이블로 선택합니다(단, 트랜잭션을 사용할 때 특정 테이블은 중요하지 않음). 새 역할과 데이터 소스를 만들도록 선택합니다. 데이터 소스 구성을 검토하여 생성된 역할의 이름을 확인할 수 있습니다. IAM 콘솔에서 데이터 소스가 모든 테이블과 상호 작용하도록 허용하는 인라인 정책을 추가할 수 있습니다.

`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-js"></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
}
```

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

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

스키마 섹션에서 `Mutation.populateAccounts` 작업 옆에 있는 **연결**을 클릭합니다. `TransactTutorial` 데이터 소스를 선택한 다음 **생성**을 선택합니다.

다음 코드를 사용합니다.

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { savingAccounts, checkingAccounts } = ctx.args

	const savings = savingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'savingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})

	const checkings = checkingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'checkingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})
	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const { savingAccounts: sInput, checkingAccounts: cInput } = ctx.args
	const keys = ctx.result.keys
	const savingAccounts = sInput.map((_, i) => keys[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => keys[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

해석기를 저장하고 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-js"></a>

다음 코드를 사용하여 `transferMoney` 뮤테이션에 해석기를 연결합니다. 이체할 때마다 당좌 예금 계좌와 저축 계좌 모두에 성공 한정자가 필요하고 거래의 이체를 추적해야 합니다.

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const transactions = ctx.args.transactions

	const savings = []
	const checkings = []
	const history = []
	transactions.forEach((t) => {
		const { savingAccountNumber, checkingAccountNumber, amount } = t
		savings.push({
			table: 'savingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: savingAccountNumber }),
			update: {
				expression: 'SET balance = balance - :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		checkings.push({
			table: 'checkingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: checkingAccountNumber }),
			update: {
				expression: 'SET balance = balance + :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		history.push({
			table: 'transactionHistory',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ transactionId: util.autoId() }),
			attributeValues: util.dynamodb.toMapValues({
				from: savingAccountNumber,
				to: checkingAccountNumber,
				amount,
			}),
		})
	})

	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings, ...history],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const tInput = ctx.args.transactions
	const tLength = tInput.length
	const keys = ctx.result.keys
	const savingAccounts = tInput.map((_, i) => keys[tLength * 0 + i])
	const checkingAccounts = tInput.map((_, i) => keys[tLength * 1 + i])
	const transactionHistory = tInput.map((_, i) => keys[tLength * 2 + i])
	return { savingAccounts, checkingAccounts, transactionHistory }
}
```

이제 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
    }
  }
}
```

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

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

한 번의 트랜잭션 요청으로 저축 계좌와 당좌 예금 계좌의 세부 정보를 검색하기 위해 스키마의 `Query.getAccounts` GraphQL 작업에 해석기를 연결하겠습니다. **연결**을 선택하고 자습서 시작 부분에서 생성한 `TransactTutorial` 데이터 소스를 선택합니다. 다음 코드를 사용합니다.

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { savingAccountNumbers, checkingAccountNumbers } = ctx.args

	const savings = savingAccountNumbers.map((accountNumber) => {
		return { table: 'savingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	const checkings = checkingAccountNumbers.map((accountNumber) => {
		return { table: 'checkingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	return {
		version: '2018-05-29',
		operation: 'TransactGetItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}

	const { savingAccountNumbers: sInput, checkingAccountNumbers: cInput } = ctx.args
	const items = ctx.result.items
	const savingAccounts = sInput.map((_, i) => items[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => items[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

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

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

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

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

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

 AWS AppSync의 배치 작업은 배치되지 않은 작업과 두 가지 주요 차이점이 있습니다.
+ 데이터 원본 역할에 해석기가 액세스할 모든 테이블에 대한 권한이 있어야 합니다.
+ 해석기에 대한 테이블 사양이 요청 객체의 일부입니다.

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

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

시작하기 위해 새로운 GraphQL API를 만들어 보겠습니다. AWS AppSync 콘솔에서 **API 생성**, **GraphQL APIs** 및 **처음부터 설계를** 선택합니다. API 이름을 `BatchTutorial API`로 지정하고 **다음**을 선택한 후, **GraphQL 리소스 지정** 단계에서 **나중에 GraphQL 리소스 생성**을 선택하고 **다음**을 클릭합니다. 세부 정보를 검토하고 API를 생성합니다. **스키마** 페이지로 이동하여 다음 스키마를 붙여넣고, 쿼리에서 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]
}
```

스키마를 저장하고 페이지 상단에서 **리소스 생성**을 선택합니다. **기존 유형 사용**을 선택하고 `Post` 유형을 선택합니다. 테이블 `Posts`의 이름을 지정합니다. **기본 키**가 `id`로 설정되어 있는지 확인하고 **GraphQL 자동 생성**을 선택 취소한 다음(직접 코드를 제공해야 함) **생성**을 선택합니다. 시작하기 위해 AWS AppSync 는 새 DynamoDB 테이블과 적절한 역할로 테이블에 연결된 데이터 소스를 생성합니다. 하지만 여전히 역할에 추가해야 하는 몇 가지 권한이 있습니다. **데이터 소스** 페이지로 이동하여 새 데이터 소스를 선택합니다. **기존 역할 선택**에서 테이블에 대한 역할이 자동으로 생성된 것을 확인할 수 있습니다. 역할(`appsync-ds-ddb-aaabbbcccddd-Posts`와 비슷하게 표시되어야 함)을 메모한 다음 IAM 콘솔([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/))로 이동합니다. IAM 콘솔에서 **역할**을 선택한 다음 표에서 역할을 선택합니다. 역할의 **권한 정책**에서 정책 옆에 있는 '`+`'를 클릭합니다(역할 이름과 이름이 비슷해야 함). 정책이 나타나면 접을 수 있는 항목의 상단에서 **편집**을 선택합니다. 정책에 일괄 권한(특히 `dynamodb:BatchGetItem` 및 `dynamodb:BatchWriteItem`)을 추가해야 합니다. 결과는 다음과 같을 것입니다.

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

****  

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

------

**다음**을 선택한 후 **변경 사항 저장**을 선택합니다. 이제 정책에서 일괄 처리를 허용해야 합니다.

 AWS AppSync 콘솔로 돌아가 **스키마** 페이지로 이동하여 `Mutation.batchAdd` 필드 옆의 **연결을** 선택합니다. `Posts` 테이블을 데이터 소스로 사용하여 해석기를 만듭니다. 코드 편집기에서 핸들러를 아래 코드 조각으로 바꿉니다. 이 코드 조각은 GraphQL `input PostInput` 유형의 각 항목을 자동으로 가져와서 `BatchPutItem` 작업에 필요한 맵을 빌드합니다.

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchPutItem",
    tables: {
      Posts: ctx.args.posts.map((post) => util.dynamodb.toMapValues(post)),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return 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` 테이블에 기록된 값을 스캔하면 결과를 확인할 수 있습니다.

그런 다음 해석기를 연결하는 과정을 반복하되, `Posts` 테이블을 데이터 소스로 사용하는 `Query.batchGet` 필드에 대해 반복합니다. 핸들러를 아래 코드로 바꿉니다. 이렇게 하면 GraphQL `ids:[]` 형식의 각 항목을 자동으로 받아 `BatchGetItem` 작업에 필요한 맵을 빌드합니다.

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchGetItem",
    tables: {
      Posts: {
        keys: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
        consistentRead: true,
      },
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return 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` 값을 반환하는지 알게 됩니다.

마지막으로 `Posts` 테이블을 데이터 소스로 사용하여 `Mutation.batchDelete` 필드에 해석기를 하나 더 연결합니다. 핸들러를 아래 코드로 바꿉니다. 이렇게 하면 GraphQL `ids:[]` 형식의 각 항목을 자동으로 받아 `BatchGetItem` 작업에 필요한 맵을 빌드합니다.

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchDeleteItem",
    tables: {
      Posts: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

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

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

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

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

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

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

 AWS AppSync 콘솔에서 **API 생성**, **GraphQL APIs** 및 **처음부터 설계를** 선택합니다. API 이름을 `MultiBatchTutorial API`로 지정하고 **다음**을 선택한 후, **GraphQL 리소스 지정** 단계에서 **나중에 GraphQL 리소스 생성**을 선택하고 **다음**을 클릭합니다. 세부 정보를 검토하고 API를 생성합니다. **스키마** 페이지로 이동하여 다음 스키마를 붙여넣고 저장합니다.

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

DynamoDB 테이블 두 개를 생성해야 합니다.
+ `locationReadings`는 센서 위치 판독값을 저장합니다.
+ `temperatureReadings`는 센서 온도 측정값을 저장합니다.

두 테이블은 동일한 기본 키 구조를 공유합니다. 파티션 키는 `sensorId (String)`, 정렬 키는 `timestamp (String)`입니다.

페이지 상단에서 **리소스 생성**을 선택합니다. **기존 유형 사용**을 선택하고 `locationReadings` 유형을 선택합니다. 테이블 `locationReadings`의 이름을 지정합니다. **기본 키**는 `sensorId`로, 정렬 키는 `timestamp`로 설정되어 있는지 확인합니다. **GraphQL 자동 생성**을 선택 취소하고(직접 코드를 제공해야 함) **생성**을 선택합니다. `temperatureReadings`를 유형 및 테이블 이름으로 사용하여 `temperatureReadings`에 이 프로세스를 반복합니다. 위와 같은 키를 사용합니다.

새 테이블에는 자동으로 생성된 역할이 포함됩니다. 여전히 역할에 추가해야 하는 몇 가지 권한이 있습니다. **데이터 소스** 페이지로 이동하여 `locationReadings`를 선택합니다. **기존 역할 선택**에서 역할을 볼 수 있습니다. 역할(`appsync-ds-ddb-aaabbbcccddd-locationReadings`와 비슷하게 표시되어야 함)을 메모한 다음 IAM 콘솔([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/))로 이동합니다. IAM 콘솔에서 **역할**을 선택한 다음 표에서 역할을 선택합니다. 역할의 **권한 정책**에서 정책 옆에 있는 '`+`'를 클릭합니다(역할 이름과 이름이 비슷해야 함). 정책이 나타나면 접을 수 있는 항목의 상단에서 **편집**을 선택합니다. 이 정책에 권한을 추가해야 합니다. 결과는 다음과 같을 것입니다.

**다음**을 선택한 후 **변경 사항 저장**을 선택합니다. 위와 동일한 정책 코드 조각을 사용하여 `temperatureReadings` 데이터 소스에 대해 이 프로세스를 반복합니다.

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

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

 AWS AppSync 콘솔의 **스키마** 페이지에서 `Mutation.recordReadings` 필드 옆에 있는 **연결을** 선택합니다. 다음 화면에서 `locationReadings` 테이블을 데이터 소스로 사용하여 해석기를 만듭니다.

해석기를 생성한 후 편집기에서 다음 코드를 사용하여 핸들러를 대체합니다. 이 `BatchPutItem` 작업을 통해 여러 테이블을 지정할 수 있습니다.

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const locationReadings = locReadings.map((loc) => util.dynamodb.toMapValues(loc))
	const temperatureReadings = tempReadings.map((tmp) => util.dynamodb.toMapValues(tmp))

	return {
		operation: 'BatchPutItem',
		tables: {
			locationReadings,
			temperatureReadings,
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

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

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

해석기를 저장하고 AppSync AWS 콘솔의 **쿼리** 페이지로 이동합니다. 이제 일부 센서 측정값을 전송할 수 있습니다.

다음 변형을 실행합니다.

```
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-js"></a>

마찬가지로 센서 판독값의 배치를 삭제할 수 있어야 합니다. 이를 위해 `Mutation.deleteReadings` GraphQL 필드를 사용해 보겠습니다. AWS AppSync 콘솔의 **스키마** 페이지에서 `Mutation.deleteReadings` 필드 옆에 있는 **연결을** 선택합니다. 다음 화면에서 `locationReadings` 테이블을 데이터 소스로 사용하여 해석기를 만듭니다.

해석기를 생성한 후 코드 편집기에서 핸들러를 아래의 코드 조각으로 대체합니다. 이 해석기에서는 제공된 입력에서 `sensorId`와 `timestamp`를 추출하는 도우미 함수 매퍼를 사용합니다.

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const mapper = ({ sensorId, timestamp }) => util.dynamodb.toMapValues({ sensorId, timestamp })

	return {
		operation: 'BatchDeleteItem',
		tables: {
			locationReadings: locReadings.map(mapper),
			temperatureReadings: tempReadings.map(mapper),
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

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

다음 변형을 실행합니다.

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

**참고**  
`DeleteItem` 작업과 달리 완전히 삭제된 항목은 응답에서 반환되지 않습니다. 전달된 키만 반환됩니다. 자세히 알아보려면 [DynamoDB용 JavaScript 해석기 함수 참조의 BatchDeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-delete-item)을 참조하세요.

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

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

앱의 또 다른 일반 작업은 특정 시점에 센서의 판독값을 검색하는 것입니다. 스키마의 `Query.getReadings` GraphQL 필드에 해석기를 연결해 보겠습니다. AWS AppSync 콘솔의 **스키마** 페이지에서 `Query.getReadings` 필드 옆에 있는 **연결을** 선택합니다. 다음 화면에서 `locationReadings` 테이블을 데이터 소스로 사용하여 해석기를 만듭니다.

다음 코드를 사용하겠습니다.

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const keys = [util.dynamodb.toMapValues(ctx.args)]
	const consistentRead = true
	return {
		operation: 'BatchGetItem',
		tables: {
			locationReadings: { keys, consistentRead },
			temperatureReadings: { keys, consistentRead },
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	const { locationReadings: locs, temperatureReadings: temps } = ctx.result.data

	return [
		...locs.map((l) => ({ ...l, __typename: 'LocationReading' })),
		...temps.map((t) => ({ ...t, __typename: 'TemperatureReading' })),
	]
}
```

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

다음 쿼리를 실행합니다.

```
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-js"></a>

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

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

1. 핸들러 평가를 중단해 오류를 나타냅니다(`util.error(...)` 사용). 이 경우에는 데이터가 반환되지 않습니다.

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

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

### DynamoDB 배치 작업
<a name="dynamodb-batch-operations-js"></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-js"></a>

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

여기서 작성한 코드는 알기 쉽고 결과 데이터에만 중점을 두고 있습니다.

**응답 핸들러**

```
export function response(ctx) {
  return 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-response-execution-js"></a>

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

**응답 핸들러 코드**

```
export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return 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-js"></a>

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

**응답 핸들러 코드**

```
export function response(ctx) {
  if (ctx.error) {
    util.appendError(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return ctx.result.data;
}
```

GraphQL 응답의 오류 블록 내에서 호출 오류 및 `unprocessedKeys` 요소를 전달합니다. 아래 응답에 표시된 것처럼 `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에서 HTTP 해석기 사용
<a name="tutorial-http-resolvers-js"></a>

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

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

## 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를 생성하려면:

1.  AWS AppSync 콘솔을 열고 **API 생성을** 선택합니다.

1. **GraphQL API**를 선택한 다음 **처음부터 새로 디자인**을 선택합니다. **다음**을 선택합니다.

1. API에 `UserData`를 입력합니다. **다음**을 선택합니다.

1. `Create GraphQL resources later`을 선택합니다. **다음**을 선택합니다.

1. 입력 내용을 검토하고 **API 생성**을 선택합니다.

 AWS AppSync 콘솔은 API 키 인증 모드를 사용하여 새 GraphQL API를 생성합니다. 콘솔을 사용하여 GraphQL API를 추가로 구성하고 요청을 실행할 수 있습니다.

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

GraphQL API가 마련되었으면 GraphQL 스키마를 생성해 보십시오. AWS AppSync 콘솔의 **스키마** 편집기에서 아래 코드 조각을 사용합니다.

```
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 데이터 원본을 구성하려면 다음을 수행하십시오.

1.  AWS AppSync GraphQL API의 **데이터 소스** 페이지에서 **데이터 소스 생성을** 선택합니다.

1. 데이터 소스의 이름(예: `HTTP_Example`)을 입력합니다.

1. **데이터 소스 유형**에서 **HTTP 엔드포인트**를 선택합니다.

1. 자습서의 시작 부분에서 생성한 API 게이트웨이 엔드포인트로 엔드포인트를 설정합니다. Lambda 콘솔로 이동하여 **애플리케이션**에서 애플리케이션을 찾으면 스택에서 생성된 엔드포인트를 찾을 수 있습니다. 애플리케이션의 설정에서 AWS AppSync의 엔드포인트가 될 **API 엔드포인트**를 확인할 수 있습니다. 단계 이름을 엔드포인트의 일부로 포함하지 않았는지 확인합니다. 예를 들어 엔드포인트가 `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com/v1`인 경우 `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com`을 입력합니다.

**참고**  
현재 퍼블릭 엔드포인트만 AWS AppSync에서 지원됩니다.  
 AWS AppSync 서비스에서 인식하는 인증 기관에 대한 자세한 내용은 [HTTPS 엔드포인트에 AWS AppSync 대해에서 인식하는 인증 기관(CA)을 참조하세요](http-cert-authorities.md#aws-appsync-http-certificate-authorities).

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

이 단계에서는 HTTP 데이터 소스를 `getUser` 및 `addUser` 쿼리에 연결합니다.

`getUser` 해석기를 설정하려면:

1.  AWS AppSync GraphQL API에서 **스키마** 탭을 선택합니다.

1. **스키마** 편집기의 오른쪽에 있는 **해석기** 창의 **쿼리** 유형 아래에서 `getUser` 필드를 찾아 **연결**을 선택합니다.

1. 해석기 유형을 `Unit`으로 유지하고 런타임을 `APPSYNC_JS`로 유지합니다.

1. **데이터 소스 이름**에서 이전에 만든 HTTP 엔드포인트를 선택합니다.

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

1. **해석기** 코드 편집기에서 다음 코드 조각을 요청 핸들러로 추가합니다.

   ```
   import { util } from '@aws-appsync/utils'
   
   export function request(ctx) {
   	return {
   		version: '2018-05-29',
   		method: 'GET',
   		params: {
   			headers: {
   				'Content-Type': 'application/json',
   			},
   		},
   		resourcePath: `/v1/users/${ctx.args.id}`,
   	}
   }
   ```

1. 다음 코드 조각을 응답 핸들러로 추가합니다.

   ```
   export function response(ctx) {
   	const { statusCode, body } = ctx.result
   	// if response is 200, return the response
   	if (statusCode === 200) {
   		return JSON.parse(body)
   	}
   	// if response is not 200, append the response to error block.
   	util.appendError(body, statusCode)
   }
   ```

1. **쿼리** 탭을 선택한 후, 다음 쿼리를 실행합니다.

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

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

   ```
   {
       "data": {
           "getUser": {
               "id": "1",
               "username": "nadia"
           }
       }
   }
   ```

`addUser` 해석기를 설정하려면:

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

1. **스키마** 편집기의 오른쪽에 있는 **해석기** 창의 **쿼리** 유형 아래에서 `addUser` 필드를 찾아 **연결**을 선택합니다.

1. 해석기 유형을 `Unit`으로 유지하고 런타임을 `APPSYNC_JS`로 유지합니다.

1. **데이터 소스 이름**에서 이전에 만든 HTTP 엔드포인트를 선택합니다.

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

1. **해석기** 코드 편집기에서 다음 코드 조각을 요청 핸들러로 추가합니다.

   ```
   export function request(ctx) {
       return {
           "version": "2018-05-29",
           "method": "POST",
           "resourcePath": "/v1/users",
           "params":{
               "headers":{
                   "Content-Type": "application/json"
               },
           "body": ctx.args.userInput
           }
       }
   }
   ```

1. 다음 코드 조각을 응답 핸들러로 추가합니다.

   ```
   export function response(ctx) {
       if(ctx.error) {
           return util.error(ctx.error.message, ctx.error.type)
       }
       if (ctx.result.statusCode == 200) {
           return ctx.result.body
       } else {
           return util.appendError(ctx.result.body, "ctx.result.statusCode")
       }
   }
   ```

1. **쿼리** 탭을 선택한 후, 다음 쿼리를 실행합니다.

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

   `getUser` 쿼리를 다시 실행하면 다음과 같은 응답이 반환되어야 합니다.

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

## AWS 서비스 호출
<a name="invoking-aws-services-js"></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 목록을 반환합니다.

# 에서 데이터 API와 함께 Aurora PostgreSQL 사용 AWS AppSync
<a name="aurora-serverless-tutorial-js"></a>

 

를 사용하여 GraphQL API를 Aurora PostgreSQL 데이터베이스에 연결하는 방법을 알아봅니다 AWS AppSync. 이 통합을 통해 GraphQL 작업을 통해 SQL 쿼리 및 변형을 실행하여 확장 가능한 데이터 기반 애플리케이션을 구축할 수 있습니다.는 데이터 API로 활성화된 Amazon Aurora 클러스터에 대해 SQL 문을 실행하기 위한 데이터 소스를 AWS AppSync 제공합니다. AWS AppSync 해석기를 사용하여 GraphQL 쿼리, 변형 및 구독과 함께 데이터 API에 대해 SQL 문을 실행할 수 있습니다.

이 자습서를 시작하기 전에 AWS 서비스 및 GraphQL 개념에 기본적으로 익숙해야 합니다.

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

**Topics**
+ [Aurora PostgreSQL 데이터베이스 설정](#creating-clusters)
+ [데이터베이스 및 테이블 생성](#creating-db-table)
+ [GraphQL 스키마 생성](#rds-graphql-schema)
+ [RDS용 해석기](#rds-resolvers)
+ [모든 클러스터 삭제](#rds-delete-cluster)

## Aurora PostgreSQL 데이터베이스 설정
<a name="creating-clusters"></a>

Amazon RDS 데이터 소스를에 추가하기 전에 다음을 AWS AppSync수행합니다.

1. Aurora Serverless v2 클러스터에서 데이터 API를 활성화합니다.

1. 를 사용하여 보안 암호 구성 AWS Secrets Manager

1. 다음 AWS CLI 명령을 사용하여 클러스터를 생성합니다.

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

그러면 클러스터에 대한 ARN이 반환됩니다. 클러스터를 생성한 후 다음 AWS CLI 명령을 사용하여 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-postgresql
```

**참고**  
이러한 엔드포인트가 활성화되려면 시간이 걸립니다. 클러스터의 **연결 및 보안** 탭에 있는 RDS 콘솔에서 상태를 확인할 수 있습니다.

다음 AWS CLI 명령을 사용하여 클러스터 상태를 확인합니다.

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

이전 단계의 `USERNAME` 및를 사용하여 다음과 같은 AWS CLI 입력 파일을 사용하여 AWS Secrets Manager 콘솔 또는 `COMPLEX_PASSWORD`를 통해 보안 암호를 생성합니다.

```
{
    "username": "USERNAME",
    "password": "COMPLEX_PASSWORD"
}
```

이를 파라미터로 AWS CLI에 전달합니다.

```
aws secretsmanager create-secret \
    --name appsync-tutorial-rds-secret \
    --secret-string file://creds.json
```

그러면 암호에 대한 ARN이 반환됩니다. AWS AppSync 콘솔에서 데이터 소스를 생성할 때 나중에 사용할 수 있도록 Aurora Serverless v2 클러스터의 ARN과 보안 암호를 **기록해 둡니다**.

## 데이터베이스 및 테이블 생성
<a name="creating-db-table"></a>

먼저 `TESTDB`라는 데이터베이스를 생성합니다. PostgreSQL에서 데이터베이스는 테이블 및 기타 SQL 객체를 포함하는 컨테이너입니다. AWS AppSync API에 추가하기 전에 Aurora Serverless v2 클러스터가 올바르게 구성되었는지 검증합니다. 먼저 다음과 같이 `--sql` 파라미터를 사용하여 *TESTDB*라는 데이터베이스를 생성합니다.

```
aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --sql "create DATABASE \"testdb\"" \
    --database "postgres"
```

 오류 없이 실행되면 `create table` 명령을 사용하여 두 개의 테이블을 추가합니다.

```
 aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --database "testdb" \
    --sql 'create table public.todos (id serial constraint todos_pk primary key, description text not null, due date not null, "createdAt" timestamp default now());'

aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --database "testdb" \
    --sql 'create table public.tasks (id serial constraint tasks_pk primary key, description varchar, "todoId" integer not null constraint tasks_todos_id_fk references public.todos);'
```

성공하면 클러스터를 API의 데이터 소스로 추가합니다.

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

이제 Aurora Serverless v2 데이터 API가 구성된 테이블을 사용하여 실행되었으므로 GraphQL 스키마를 생성해 보겠습니다. API 생성 마법사를 통해 기존 데이터베이스에서 테이블 구성을 가져와서 빠르게 API를 생성할 수 있습니다.

시작하려면 다음과 같이 하십시오.

1.  AWS AppSync 콘솔에서 **API 생성을** 선택한 다음 **Amazon Aurora 클러스터로 시작을** 선택합니다.

1. **API 이름**과 같은 API 세부 정보를 지정한 다음 API를 생성할 데이터베이스를 선택합니다.

1. 데이터베이스 유형을 선택합니다. 필요한 경우 리전을 업데이트한 다음 Aurora 클러스터 및 *TESTDB* 데이터베이스를 선택합니다.

1. 암호를 선택한 다음 **가져오기**를 선택합니다.

1. 테이블이 검색되면 유형 이름을 업데이트하십시오. `Todos`를 `Todo`로, `Tasks`를 `Task`로 변경하십시오.

1. **스키마 미리 보기**를 선택하여 생성된 스키마를 미리 볼 수 있습니다. 스키마는 다음과 비슷한 모습이 됩니다.

   ```
   type Todo {
     id: Int!
     description: String!
     due: AWSDate!
     createdAt: String
   }
   
   type Task {
     id: Int!
     todoId: Int!
     description: String
   }
   ```

1. 역할의 경우 새 역할을 AWS AppSync 생성하거나 아래 역할과 유사한 정책을 사용하여 역할을 생성할 수 있습니다.

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

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "rds-data:ExecuteStatement"
               ],
               "Resource": [
                   "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial",
                   "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial:*"
               ]
           },
           {
               "Effect": "Allow",
               "Action": [
                   "secretsmanager:GetSecretValue"
               ],
               "Resource": [
               "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret",
               "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret:*"
               ]
           }
       ]
   }
   ```

------

   이 정책에는 역할 액세스 권한을 부여하는 문이 두 개 있습니다. 첫 번째 리소스는 Aurora 클러스터이고 두 번째는 AWS Secrets Manager ARN입니다.

   **다음**을 선택하고 구성 세부 정보를 검토한 다음 **API 생성**을 선택합니다. API가 이제 완전히 작동합니다. **스키마** 페이지에서 API의 전체 세부 정보를 검토할 수 있습니다.

## RDS용 해석기
<a name="rds-resolvers"></a>

API 생성 흐름에 따라 유형과 상호 작용하는 해석기가 자동으로 생성되었습니다. **스키마** 페이지를 보면 해석기 중 일부가 표시됩니다.
+ `Mutation.createTodo` 필드를 통해 `todo`를 생성하십시오. 
+ `Mutation.updateTodo` 필드를 통해 `todo`를 업데이트하십시오. 
+ `Mutation.deleteTodo` 필드를 통해 `todo`를 삭제하십시오. 
+ `Query.getTodo` 필드를 통해 단일 `todo`를 가져오십시오.
+ `todos` 필드를 통해 `Query.listTodos`를 모두 나열하십시오.

`Task` 유형에는 유사한 필드와 해석기가 첨부되어 있습니다. 몇 가지 해석기를 좀 더 자세히 살펴보겠습니다.

### Mutation.createTodo
<a name="createtodo"></a>

 AWS AppSync 콘솔의 스키마 편집기에서 오른쪽에 있는 `testdb`옆의를 선택합니다`createTodo(...): Todo`. 해석기 코드는 `rds` 모듈의 `insert` 함수를 사용하여 `todos` 테이블에 데이터를 추가하는 삽입 문을 동적으로 만듭니다. Postgres를 사용하고 있기 때문에 `returning` 문을 활용하여 삽입된 데이터를 다시 가져올 수 있습니다.

`due` 필드의 `DATE` 유형을 올바르게 지정하도록 해석기를 업데이트합니다.

```
import { util } from '@aws-appsync/utils';
import { insert, createPgStatement, toJsonObject, typeHint } from '@aws-appsync/utils/rds';

export function request(ctx) {
    const { input } = ctx.args;
    // if a due date is provided, cast is as `DATE`
    if (input.due) {
        input.due = typeHint.DATE(input.due)
    }
    const insertStatement = insert({
        table: 'todos',
        values: input,
        returning: '*',
    });
    return createPgStatement(insertStatement)
}

export function response(ctx) {
    const { error, result } = ctx;
    if (error) {
        return util.appendError(
            error.message,
            error.type,
            result
        )
    }
    return toJsonObject(result)[0][0]
}
```

해석기를 저장합니다. 유형 힌트는 입력 객체의 `due`를 올바르게 `DATE` 유형으로 표시합니다. 이렇게 하면 Postgres 엔진이 값을 올바르게 해석할 수 있습니다. 다음으로 스키마를 업데이트하여 `CreateTodo` 입력에서 `id`를 제거합니다. Postgres 데이터베이스는 생성된 ID를 반환할 수 있으므로 다음과 같이 단일 요청으로 이 ID를 사용하여 결과를 생성하고 반환할 수 있습니다.

```
input CreateTodoInput {
    due: AWSDate!
    createdAt: String
    description: String!
}
```

스키마를 변경하고 업데이트하십시오. 다음과 같이 **쿼리** 편집기로 이동하여 데이터베이스에 항목을 추가합니다.

```
mutation CreateTodo {
  createTodo(input: {description: "Hello World!", due: "2023-12-31"}) {
    id
    due
    description
    createdAt
  }
}
```

결과는 다음과 같습니다.

```
{
  "data": {
    "createTodo": {
      "id": 1,
      "due": "2023-12-31",
      "description": "Hello World!",
      "createdAt": "2023-11-14 20:47:11.875428"
    }
  }
}
```

### Query.listTodos
<a name="listtodo"></a>

콘솔의 스키마 편집기 오른쪽에서 `listTodos(id: ID!): Todo` 옆에 있는 `testdb`를 선택합니다. 요청 핸들러는 선택 유틸리티 함수를 사용하여 런타임에 요청을 동적으로 작성합니다.

```
export function request(ctx) {
    const { filter = {}, limit = 100, nextToken } = ctx.args;
    const offset = nextToken ? +util.base64Decode(nextToken) : 0;
    const statement = select({
        table: 'todos',
        columns: '*',
        limit,
        offset,
        where: filter,
    });
    return createPgStatement(statement)
}
```

`due` 날짜를 기준으로 `todos`를 필터링하려고 합니다. `due` 값을 `DATE`로 캐스팅하도록 해석기를 업데이트해 보겠습니다. 다음과 같이 가져오기 목록과 요청 핸들러를 업데이트합니다.

```
import { util } from '@aws-appsync/utils';
import * as rds from '@aws-appsync/utils/rds';

export function request(ctx) {
  const { filter: where = {}, limit = 100, nextToken } = ctx.args;
  const offset = nextToken ? +util.base64Decode(nextToken) : 0;

  // if `due` is used in a filter, CAST the values to DATE.
  if (where.due) {
    Object.entries(where.due).forEach(([k, v]) => {
      if (k === 'between') {
        where.due[k] = v.map((d) => rds.typeHint.DATE(d));
      } else {
        where.due[k] = rds.typeHint.DATE(v);
      }
    });
  }

  const statement = rds.select({
    table: 'todos',
    columns: '*',
    limit,
    offset,
    where,
  });
  return rds.createPgStatement(statement);
}

export function response(ctx) {
  const {
    args: { limit = 100, nextToken },
    error,
    result,
  } = ctx;
  if (error) {
    return util.appendError(error.message, error.type, result);
  }
  const offset = nextToken ? +util.base64Decode(nextToken) : 0;
  const items = rds.toJsonObject(result)[0];
  const endOfResults = items?.length < limit;
  const token = endOfResults ? null : util.base64Encode(`${offset + limit}`);
  return { items, nextToken: token };
}
```

**쿼리** 편집기에서 다음을 수행합니다.

```
query LIST {
  listTodos(limit: 10, filter: {due: {between: ["2021-01-01", "2025-01-02"]}}) {
    items {
      id
      due
      description
    }
  }
}
```

### Mutation.updateTodo
<a name="updatetodo"></a>

`Todo`를 `update`할 수도 있습니다. **쿼리** 편집기에서 `id` `1`의 첫 번째 `Todo` 항목을 업데이트해 보겠습니다.

```
mutation UPDATE {
  updateTodo(input: {id: 1, description: "edits"}) {
    description
    due
    id
  }
}
```

업데이트하려는 항목의 `id`를 지정해야 합니다. 특정 조건을 충족하는 항목만 업데이트하도록 조건을 지정할 수도 있습니다. 예를 들어 다음과 같이 설명이 `edits`로 시작하는 경우에만 항목을 편집하고 싶을 수 있습니다.

```
mutation UPDATE {
  updateTodo(input: {id: 1, description: "edits: make a change"}, condition: {description: {beginsWith: "edits"}}) {
    description
    due
    id
  }
}
```

`create` 및 `list` 작업을 처리한 방식과 마찬가지로 해석기를 업데이트하여 `DATE`에 `due` 필드를 캐스팅할 수 있습니다. 이러한 변경 사항을 다음과 같이 `updateTodo`에 저장합니다.

```
import { util } from '@aws-appsync/utils';
import * as rds from '@aws-appsync/utils/rds';

export function request(ctx) {
  const { input: { id, ...values }, condition = {}, } = ctx.args;
  const where = { ...condition, id: { eq: id } };

  // if `due` is used in a condition, CAST the values to DATE.
  if (condition.due) {
    Object.entries(condition.due).forEach(([k, v]) => {
      if (k === 'between') {
        condition.due[k] = v.map((d) => rds.typeHint.DATE(d));
      } else {
        condition.due[k] = rds.typeHint.DATE(v);
      }
    });
  }

  // if a due date is provided, cast is as `DATE`
  if (values.due) {
    values.due = rds.typeHint.DATE(values.due);
  }

  const updateStatement = rds.update({
    table: 'todos',
    values,
    where,
    returning: '*',
  });
  return rds.createPgStatement(updateStatement);
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    return util.appendError(error.message, error.type, result);
  }
  return rds.toJsonObject(result)[0][0];
}
```

이제 다음과 같은 조건을 적용하여 업데이트를 시도해 보십시오.

```
mutation UPDATE {
  updateTodo(
    input: {
        id: 1, description: "edits: make a change", due: "2023-12-12"},
    condition: {
        description: {beginsWith: "edits"}, due: {ge: "2023-11-08"}})
    {
          description
          due
          id
        }
}
```

### Mutation.deleteTodo
<a name="deletetodo"></a>

`deleteTodo` 변형으로 `Todo`를 `delete`할 수 있습니다. 다음과 같이 이는 `updateTodo` 변형과 비슷하게 작동하며 삭제하려는 항목의 `id`를 지정해야 합니다.

```
mutation DELETE {
  deleteTodo(input: {id: 1}) {
    description
    due
    id
  }
}
```

### 사용자 지정 쿼리 작성
<a name="writing-custom-queries"></a>

`rds` 모듈 유틸리티를 사용하여 SQL 문을 생성했습니다. 데이터베이스와 상호 작용하는 사용자 지정 정적 문을 직접 작성할 수도 있습니다. 먼저 스키마를 업데이트하여 `CreateTask` 입력에서 `id` 필드를 제거합니다.

```
input CreateTaskInput {
    todoId: Int!
    description: String
}
```

다음으로, 몇 가지 작업을 만듭니다. 다음과 같이 작업에는 `Todo`와의 외래 키 관계가 있습니다.

```
mutation TASKS {
  a: createTask(input: {todoId: 2, description: "my first sub task"}) { id }
  b:createTask(input: {todoId: 2, description: "another sub task"}) { id }
  c: createTask(input: {todoId: 2, description: "a final sub task"}) { id }
}
```

다음과 같이 `getTodoAndTasks`라는 `Query` 유형에 새 필드를 생성합니다.

```
getTodoAndTasks(id: Int!): Todo
```

다음과 같이 `Todo` 유형에 `tasks` 필드를 추가합니다.

```
type Todo {
    due: AWSDate!
    id: Int!
    createdAt: String
    description: String!
    tasks:TaskConnection
}
```

스키마를 저장합니다. 콘솔의 스키마 편집기 오른쪽에서 `getTodosAndTasks(id: Int!): Todo`에 대해 **해석기 첨부**를 선택합니다. Amazon RDS 데이터 소스를 선택합니다. 다음 코드로 해석기를 업데이트합니다.

```
import { sql, createPgStatement,toJsonObject } from '@aws-appsync/utils/rds';

export function request(ctx) {
    return createPgStatement(
        sql`SELECT * from todos where id = ${ctx.args.id}`,
        sql`SELECT * from tasks where "todoId" = ${ctx.args.id}`);
}

export function response(ctx) {
    const result = toJsonObject(ctx.result);
    const todo = result[0][0];
    if (!todo) {
        return null;
    }
    todo.tasks = { items: result[1] };
    return todo;
}
```

이 코드에서는 `sql` 태그 템플릿을 사용하여 런타임 중에 동적 값을 안전하게 전달할 수 있는 SQL 문을 작성합니다. `createPgStatement`는 한 번에 최대 두 개의 SQL 요청을 처리할 수 있습니다. 이를 사용하여 하나의 쿼리를 `todo`에 보내고 다른 쿼리는 `tasks`에 보냅니다. `JOIN` 문이나 다른 방법을 사용해도 이 작업을 수행할 수 있었을 것입니다. 요점은 직접 SQL 문을 작성하여 비즈니스 로직을 구현할 수 있게 하는 것입니다. **쿼리** 편집기에서 쿼리를 사용하려면 다음을 수행합니다.

```
query TodoAndTasks {
  getTodosAndTasks(id: 2) {
    id
    due
    description
    tasks {
      items {
        id
        description
      }
    }
  }
}
```

## 모든 클러스터 삭제
<a name="rds-delete-cluster"></a>

**중요**  
클러스터 삭제는 영구적입니다. 이 작업을 수행하기 전에 프로젝트를 철저하게 검토하십시오.

클러스터를 삭제하려면 다음과 같이 하십시오.

```
$ aws rds delete-db-cluster \
    --db-cluster-identifier appsync-tutorial \
    --skip-final-snapshot
```