

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 搭配 使用 Aurora Serverless v2 AWS AppSync
<a name="tutorial-rds-resolvers"></a>

使用 將您的 GraphQL API 連接至 Aurora Serverless 資料庫 AWS AppSync。此整合可讓您透過 GraphQL 查詢、變動和訂閱執行 SQL 陳述式，讓您有彈性地與關聯式資料互動。

**注意**  
此教學會使用 `US-EAST-1` 區域。

**優勢**
+ GraphQL 與關聯式資料庫之間的無縫整合
+ 能夠透過 GraphQL 介面執行 SQL 操作
+ 搭配 Aurora Serverless v2 的無伺服器可擴展性
+ 透過 AWS Secrets Manager 安全存取資料
+ 透過輸入淨化防止 SQL 注入
+ 靈活的查詢功能，包括篩選和範圍操作

**常用案例**
+ 使用關聯式資料需求建置可擴展的應用程式
+ 建立同時需要 GraphQL 彈性和 SQL 資料庫功能的 APIs 
+ 透過 GraphQL 變動和查詢管理資料操作
+ 實作安全資料庫存取模式

在本教學課程中，您將學到以下內容。
+ 設定 Aurora Serverless v2 叢集
+ 啟用資料 API 功能
+ 建立和設定資料庫結構
+ 定義資料庫操作的 GraphQL 結構描述
+ 實作查詢和變動的解析程式
+ 透過適當的輸入清理來保護您的資料存取
+ 透過 GraphQL 介面執行各種資料庫操作

**Topics**
+ [設定資料庫叢集](#create-cluster)
+ [啟用 Data API](#enable-data-api)
+ [建立資料庫及資料表](#create-database-and-table)
+ [GraphQL 結構描述](#graphql-schema)
+ [將您的 API 連線至資料庫操作](#configuring-resolvers)
+ [透過 API 修改您的資料](#run-mutations)
+ [擷取您的資料](#run-queries)
+ [保護您的資料存取](#input-sanitization)

## 設定資料庫叢集
<a name="create-cluster"></a>

將 Amazon RDS 資料來源新增至 之前 AWS AppSync，您必須先在 Aurora Serverless v2 叢集上啟用資料 API，並使用 **設定秘密***AWS Secrets Manager*。您可以使用 建立 Aurora Serverless v2 叢集 AWS CLI：

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

這會傳回叢集的 ARN。

建立叢集之後，您必須使用下列命令新增 Aurora Serverless v2 執行個體。

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

**注意**  
這些端點需要一些時間才能啟用。您可以在叢集**的連線與安全**索引標籤中的 Amazon RDS 主控台中檢查其狀態。您也可以使用下列 AWS CLI 命令來檢查叢集的狀態。  

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

您可以使用 AWS Secrets Manager 主控台或 AWS CLI 來建立*秘密*，並使用上`COMPLEX_PASSWORD`一個步驟中的 `USERNAME`和 搭配輸入檔案，如下所示。

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

將此做為參數傳遞至 AWS CLI：

```
aws secretsmanager create-secret --name HttpRDSSecret --secret-string file://creds.json --region us-east-1
```

這會傳回秘密的 ARN。

 **記下 ARN** (屬於 Aurora Serverless 叢集及 Secret) 以供日後建立資料來源時用於 AppSync 主控台。

## 啟用 Data API
<a name="enable-data-api"></a>

您可以[依照 RDS 文件中的說明](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html)，在您的叢集上啟用 Data API。Data API 必須先行啟用，才能新增為 AppSync 資料來源。

## 建立資料庫及資料表
<a name="create-database-and-table"></a>

啟用資料 API 後，您就可以確保它可與 中的 `aws rds-data execute-statement`命令搭配使用 AWS CLI。這可確保您的 Aurora Serverless 叢集在經過正確設定後，才新增到您的 AppSync API。首先使用 `--sql` 參數建立名為 *TESTDB* 的資料庫，如下所示：

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1"  \
--region us-east-1 --sql "create DATABASE TESTDB"
```

如果這次執行沒有錯誤，則搭配 *create table* 命令新增資料表：

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:123456789000:cluster:http-endpoint-test" \
 --schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:123456789000:secret:testHttp2-AmNvc1" \
 --region us-east-1 \
 --sql "create table Pets(id varchar(200), type varchar(200), price float)" --database "TESTDB"
```

如果所有執行都沒有問題，您就可以繼續將該叢集新增到您的 AppSync API。

## GraphQL 結構描述
<a name="graphql-schema"></a>

現在您的 Aurora Serverless Data API 已經啟動並搭配資料表運作，我們會建立 GraphQL 結構描述，並連接可執行變動和訂閱的解析程式。在 AWS AppSync 主控台中建立新的 API 並導覽至**結構描述**頁面，然後輸入以下內容：

```
type Mutation {
    createPet(input: CreatePetInput!): Pet
    updatePet(input: UpdatePetInput!): Pet
    deletePet(input: DeletePetInput!): Pet
}

input CreatePetInput {
    type: PetType
    price: Float!
}

input UpdatePetInput {
id: ID!
    type: PetType
    price: Float!
}

input DeletePetInput {
    id: ID!
}

type Pet {
    id: ID!
    type: PetType
    price: Float
}

enum PetType {
    dog
    cat
    fish
    bird
    gecko
}

type Query {
    getPet(id: ID!): Pet
    listPets: [Pet]
    listPetsByPriceRange(min: Float, max: Float): [Pet]
}

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

 **儲存**您的結構描述並瀏覽到 **Data Sources (資料來源)** 頁面，並建立新的資料來源。選取資料來源類型為** Relational database (關聯式資料庫)**，並提供易記名稱。使用您在上一個步驟中所建立的資料庫名稱，以及您所建立叢集的**叢集 ARN**。處理**角色**時，您可以使用 AppSync 來建立新角色，或是搭配類似下面政策來建立一個角色：

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

****  

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

------

請注意，在您授予角色存取權的這個政策中有 2 個**陳述式** 。第一個**資源**是您的 Aurora Serverless 叢集，第二個資源是您的 AWS Secrets Manager ARN。您將需要先在 AppSync 資料來源組態中**同時**提供兩種 ARN 後，再按一下 **Create (建立)**。

將此做為 參數傳遞至 AWS CLI。

```
aws secretsmanager create-secret \
  --name HttpRDSSecret \
  --secret-string file://creds.json \
  --region us-east-1
```

這會傳回秘密的 ARN。在 AWS AppSync 主控台中建立資料來源時，請記下 Aurora Serverless 叢集的 ARN 和秘密以供稍後使用。

### 建置您的資料庫結構
<a name="create-database-and-table"></a>

啟用資料 API 後，您就可以確保它可與 中的 `aws rds-data execute-statement`命令搭配使用 AWS CLI。這將確保您的 Aurora Serverless v2 叢集在新增至 AWS AppSync API 之前設定正確。首先，使用 `--sql` 參數建立名為 *TESTDB* 的資料庫，如下所示。

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

如果執行時沒有錯誤，請使用下列*建立資料表命令新增資料表*。

```
aws rds-data execute-statement \
      --resource-arn "arn:aws:rds:us-east-1:111122223333:cluster:http-endpoint-test" \
      --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333:secret:testHttp2-AmNvc1" \
      --region us-east-1 \
      --sql "create table Pets(id varchar(200), type varchar(200), price float)" \
      --database "TESTDB"
```

### 設計您的 API 介面
<a name="graphql-schema"></a>

Aurora Serverless v2 Data API 啟動並使用資料表執行後，請建立 GraphQL 結構描述並連接解析程式，以執行變動和訂閱。在 AWS AppSync 主控台中建立新的 API，並導覽至主控台中的**結構描述**頁面，然後輸入以下內容。

```
type Mutation {
        createPet(input: CreatePetInput!): Pet
        updatePet(input: UpdatePetInput!): Pet
        deletePet(input: DeletePetInput!): Pet
    }
    
    input CreatePetInput {
        type: PetType
        price: Float!
    }
    
    input UpdatePetInput {
        id: ID!
        type: PetType
        price: Float!
    }
    
    input DeletePetInput {
        id: ID!
    }
    
    type Pet {
        id: ID!
        type: PetType
        price: Float
    }
    
    enum PetType {
        dog
        cat
        fish
        bird
        gecko
    }
    
    type Query {
        getPet(id: ID!): Pet
        listPets: [Pet]
        listPetsByPriceRange(min: Float, max: Float): [Pet]
    }
    
    schema {
        query: Query
        mutation: Mutation
    }
```

 **儲存**您的結構描述並瀏覽到 **Data Sources (資料來源)** 頁面，並建立新的資料來源。選擇******資料來源類型的關聯式資料庫**，並提供易記的名稱。使用您在上一個步驟中所建立的資料庫名稱，以及您所建立叢集的**叢集 ARN**。對於**角色**，您可以 AWS AppSync 建立新的角色，或使用類似下列的政策來建立角色。

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

****  

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

------

請注意，在您授予角色存取權的這個政策中有 2 個**陳述式** 。第一個**資源**是您的 Aurora Serverless v2 叢集，第二個資源是您的 AWS Secrets Manager ARN。在按一下**建立**之前，您將需要在 AWS AppSync 資料來源組態中提供**這兩個** ARNs。

## 將您的 API 連線至資料庫操作
<a name="configuring-resolvers"></a>

現在我們有有效的 GraphQL 結構描述和 RDS 資料來源，您可以將解析程式連接到結構描述的 GraphQL 欄位。我們的 API 提供下列功能：

1. 使用 *Mutation.createPet* 欄位建立寵物

1. 使用 *Mutation.updatePet* 欄位更新寵物

1. 使用 *Mutation.deletePet* 欄位刪除寵物

1. 透過 *Query.getPet* 欄位取得單一使用

1. 使用 *Query.listPets* 欄位列出所有

1. 使用 *Query.listPetsByPriceRange* 欄位列出價格範圍內的寵物

### Mutation.createPet
<a name="mutation-createpet"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`createPet(input: CreatePetInput!): Pet`。選擇 RDS 資料來源。在 **request mapping template (要求映射範本)** 區段中，新增下列範本：

```
#set($id=$utils.autoId())
{
"version": "2018-05-29",
    "statements": [
        "insert into Pets VALUES (:ID, :TYPE, :PRICE)",
        "select * from Pets WHERE id = :ID"
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

系統會根據陳述式陣列中的順序依序執行 SQL **陳述**式。結果將以相同順序傳回。由於這是一個變動，因此您將在*插入*後執行*選取*陳述式，以擷取遞交的值，以填入 GraphQL 回應映射範本。

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

由於*陳述式*有兩個 SQL 查詢，所以我們需要使用下列程式碼，指定自資料庫傳回矩陣中的第二個結果：`$utils.rds.toJsonString($ctx.result))[1][0])`。

### Mutation.updatePet
<a name="mutation-updatepet"></a>

從 AWS AppSync 主控台的結構描述編輯器中，選擇**連接解析程式**。 `updatePet(input: UpdatePetInput!): Pet`選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("update Pets set type=:TYPE, price=:PRICE WHERE id=:ID"),
        $util.toJson("select * from Pets WHERE id = :ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id",
        ":TYPE": $util.toJson($ctx.args.input.type),
        ":PRICE": $util.toJson($ctx.args.input.price)
    }
}
```

在**回應映射範本**區段中，新增下列範本。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[1][0])
```

### Mutation.deletePet
<a name="mutation-deletepet"></a>

從 AWS AppSync 主控台的結構描述編輯器中，選擇**連接解析程式**。 `deletePet(input: DeletePetInput!): Pet`選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
"version": "2018-05-29",
    "statements": [
        $util.toJson("select * from Pets WHERE id=:ID"),
        $util.toJson("delete from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.input.id"
    }
}
```

在**回應映射範本**區段中，新增下列範本。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.getPet
<a name="query-getpet"></a>

現在已為您的結構描述建立變動，請連接三個查詢，展示如何取得個別項目、清單和套用 SQL 篩選。從 AWS AppSync 主控台的**結構描述編輯器**中，選擇**連接解析程式**。 `getPet(id: ID!): Pet`選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
"version": "2018-05-29",
        "statements": [
            $util.toJson("select * from Pets WHERE id=:ID")
    ],
    "variableMap": {
        ":ID": "$ctx.args.id"
    }
}
```

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])
```

### Query.listPets
<a name="query-listpets"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`getPet(id: ID!): Pet`。選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
    "version": "2018-05-29",
    "statements": [
        "select * from Pets"
    ]
}
```

在**回應映射範本**區段中，新增下列範本。

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

### Query.listPetsByPriceRange
<a name="query-listpetsbypricerange"></a>

從 AWS AppSync 主控台中的結構描述編輯器，選擇 的**連接解析程式**`getPet(id: ID!): Pet`。選擇您的 **RDS 資料來源**。在**請求映射範本**區段中，新增下列範本。

```
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.max),
        ":MIN": $util.toJson($ctx.args.min)
    }
}
```

在 **response mapping template (回應映射範本)** 區段中，新增下列範本：

```
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
```

## 透過 API 修改您的資料
<a name="run-mutations"></a>

現在，您已運用 SQL 陳述式設定您的所有解析程式，並已將 GraphQL API 連接到您的 Serverless Aurora Data API，您可以開始執行變動和查詢了。In AWS AppSync 主控台，選擇**查詢**索引標籤，然後輸入下列內容來建立寵物：

```
mutation add {
    createPet(input : { type:fish, price:10.0 }){
        id
        type
        price
    }
}
```

回應應該包含如下的 *id*、*type* 和 *price*：

```
{
  "data": {
    "createPet": {
      "id": "c6fedbbe-57ad-4da3-860a-ffe8d039882a",
      "type": "fish",
      "price": "10.0"
    }
  }
}
```

您可以執行 *updatePet* 變動，修改此項目：

```
mutation update {
    updatePet(input : {
        id: ID_PLACEHOLDER,
        type:bird,
        price:50.0
    }){
        id
        type
        price
    }
}
```

請注意，我們使用先前從 *createPet* 操作傳回的 *ID*。這將是您的記錄在解析程式運用 `$util.autoId()` 時的唯一值。您可以用類似的方式刪除記錄：

```
mutation delete {
    deletePet(input : {id:ID_PLACEHOLDER}){
        id
        type
        price
    }
}
```

運用第一個變動，搭配 *price* 的幾個不同值，建立一些記錄，然後執行一些查詢。

## 擷取您的資料
<a name="run-queries"></a>

仍在 主控台的**查詢**索引標籤中，請使用下列陳述式列出您已建立的所有記錄。

```
query allpets {
    listPets {
        id
        type
        price
    }
}
```

透過下列 GraphQL 查詢，利用 *Query.listPetsByPriceRange* 映射範本`where price > :MIN and price < :MAX`中具有 的 SQL *WHERE* 述詞：

```
query petsByPriceRange {
    listPetsByPriceRange(min:1, max:11) {
        id
        type
        price
    }
}
```

您應該只會看到 *price* 超過 \$11 元或不到 \$110 元的記錄。最後，您可以執行查詢來擷取個別記錄，如下所示：

```
query onePet {
    getPet(id:ID_PLACEHOLDER){
        id
        type
        price
    }
}
```

## 保護您的資料存取
<a name="input-sanitization"></a>

SQL Injection 是資料庫應用程式中的安全漏洞。當攻擊者透過使用者輸入欄位插入惡意 SQL 程式碼時，就會發生這種情況。這可以允許未經授權存取資料庫資料。建議您在處理所有使用者輸入之前，使用 仔細驗證和清理，`variableMap`以防止 SQL Injection 攻擊。如果未使用變數映射，您必須負責清理其 GraphQL 操作的引數。達成這個淨化的一種方法是先在要求映射範本中提供輸入特定驗證步驟，接著再對 Data API 執行 SQL 陳述式。讓我們來看看如何修改 `listPetsByPriceRange` 範例的請求映射範本。您可以執行以下操作，而不再只是依賴使用者輸入：

```
#set($validMaxPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.maxPrice))

#set($validMinPrice = $util.matches("\d{1,3}[,\\.]?(\\d{1,2})?",$ctx.args.minPrice))


#if (!$validMaxPrice || !$validMinPrice)
    $util.error("Provided price input is not valid.")
#end
{
    "version": "2018-05-29",
    "statements": [
            "select * from Pets where price > :MIN and price < :MAX"
    ],

    "variableMap": {
        ":MAX": $util.toJson($ctx.args.maxPrice),
        ":MIN": $util.toJson($ctx.args.minPrice)
    }
}
```

在對 Data API 執行解析程式時防堵惡意輸入的另外一種方法，就是同時使用預備的陳述式來搭配預存程序和參數化的輸入。例如，在 `listPets` 的解析程式中，定義下列執行 *select* 為預先準備陳述式的程序：

```
CREATE PROCEDURE listPets (IN type_param VARCHAR(200))
  BEGIN
     PREPARE stmt FROM 'SELECT * FROM Pets where type=?';
     SET @type = type_param;
     EXECUTE stmt USING @type;
     DEALLOCATE PREPARE stmt;
  END
```

在 Aurora Serverless v2 執行個體中建立此項目。

```
aws rds-data execute-statement --resource-arn "arn:aws:rds:us-east-1:xxxxxxxxxxxx:cluster:http-endpoint-test" \
--schema "mysql"  --secret-arn "arn:aws:secretsmanager:us-east-1:xxxxxxxxxxxx:secret:httpendpoint-xxxxxx"  \
--region us-east-1  --database "DB_NAME" \
--sql "CREATE PROCEDURE listPets (IN type_param VARCHAR(200)) BEGIN PREPARE stmt FROM 'SELECT * FROM Pets where type=?'; SET @type = type_param; EXECUTE stmt USING @type; DEALLOCATE PREPARE stmt; END"
```

針對 listPets 產生的解析程式碼變得簡單，因為我們現在只要呼叫預存程序。至少，任何字串輸入都應將單引號[逸出](#escaped)。

```
#set ($validType = $util.isString($ctx.args.type) && !$util.isNullOrBlank($ctx.args.type))
#if (!$validType)
    $util.error("Input for 'type' is not valid.", "ValidationError")
#end

{
    "version": "2018-05-29",
    "statements": [
        "CALL listPets(:type)"
    ]
    "variableMap": {
        ":type": $util.toJson($ctx.args.type.replace("'", "''"))
    }
}
```

### 使用逸出字串
<a name="escaped"></a>

使用單引號在 SQL 陳述式中標記字串常值的開頭和結尾，例如 。 `'some string value'`若要允許在字串中使用具有一或多個單引號字元 (`'`) 的字串值，必須以兩個單引號 (`''`) 取代每個字串值。例如，如果輸入字串是 `Nadia's dog`，則您會將其逸出為如下的 SQL 陳述式：

```
update Pets set type='Nadia''s dog' WHERE id='1'
```