

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

# 에서 데이터 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
```