

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

# 在 中的版本控制資料來源上使用 Delta Sync 操作 AWS AppSync
<a name="tutorial-delta-sync"></a>

**注意**  
我們現在主要支援 APPSYNC\$1JS 執行期及其文件。請考慮[在此處](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html)使用 APPSYNC\$1JS 執行期及其指南。

用戶端應用程式 in AWS AppSync 透過將 GraphQL 回應快取到本機行動/網路應用程式中的磁碟來存放資料。已建立版本的資料來源和 `Sync` 操作可讓客戶使用單一解析程式來執行同步處理程序。透過這些查詢，用戶端可以使用從可能包含大量記錄的一個 base 查詢結果補充其本機快取，然後僅接收從上次查詢後有所更動的資料 (*delta 更新*)。藉由允許用戶端透過初始請求和其他請求的遞增性更新來執行快取的基本填入，所以您可將用戶端應用程式的計算工作移到後端。對於經常在上線和離線狀態之間切換的用戶端應用程式而言，這種做法可讓效率大幅提升。

若要實作 Delta Sync，`Sync` 查詢會在已建立版本的資料來源上使用 `Sync` 操作。當 an AWS AppSync 變動變更版本控制資料來源中的項目時，該變更的記錄也會存放在 *Delta* 資料表中。您可以選擇針對其他版本控制的資料來源使用不同的 *Delta* 資料表 （例如，每種類型一個，每個網域區域一個），或針對您的 API 使用單一 *Delta* 資料表。 AWS AppSync 建議不要針對多個 APIs 使用單一 *Delta* 資料表，以避免主要金鑰發生衝突。

此外，Delta Sync 用戶端也可以透過引數接收訂閱，然後用戶端協調訂閱和重新連線，並在離線和線上轉換之間進行寫入。Delta Sync 會自動恢復訂閱來執行這個程序，包括指數退避、透過不同的網路錯誤案例進行抖動，以及將事件存放在佇列中。接著會先執行適當的 delta 或 base 查詢，再合併佇列中的任何事件，最後依照一般方式處理訂閱。

[Amplify 架構網站](https://aws-amplify.github.io/)提供關於用戶端組態選項的文件，包括 Amplify DataStore。這份文件概述如何設定已建立版本的 DynamoDB 資料來源和 `Sync` 操作，以便與 Delta Sync 用戶端搭配使用，提供最佳的資料存取權。

## 一鍵設定
<a name="one-click-setup"></a>

若要在設定所有解析程式和必要 AWS 資源的情況下自動設定 GraphQL 端點 in AWS AppSync，請使用此 AWS CloudFormation 範本：

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

這個堆疊會在您的帳戶中建立下列資源：
+ 2 個 DynamoDB 資料表 (Base 和 Delta)
+ 1 AWS AppSync API 與 API 金鑰
+ 1 個具有 DynamoDB 資料表政策的 IAM 角色

兩個資料表會用來將同步查詢分割成第二個資料表，並做為用戶端離線時遺漏事件的日誌。為了讓 delta 資料表的查詢維持效率，您可以視需要使用 [Amazon DynamoDB TTLs](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) 來自動備妥這些事件。TTL 時間可根據您對資料來源的需求進行設定 (您可能希望將其設為 1 小時、1天等)。

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

為了示範 Delta Sync，範例應用程式會在 DynamoDB 中建立由 *Base* 和 *Delta* 資料表支援的 *Posts* 結構描述。 AWS AppSync 會自動將變動寫入兩個資料表。同步查詢會依適當情況從 *Base* 或 *Delta* 資料表提取記錄，並且定義一份訂閱，說明用戶端如何透過其重新連線邏輯來發揮變動功能。

```
input CreatePostInput {
    author: String!
    title: String!
    content: String!
    url: String
    ups: Int
    downs: Int
    _version: Int
}

interface Connection {
  nextToken: String
  startedAt: AWSTimestamp!
}

type Mutation {
    createPost(input: CreatePostInput!): Post
    updatePost(input: UpdatePostInput!): Post
    deletePost(input: DeletePostInput!): Post
}

type Post {
    id: ID!
    author: String!
    title: String!
    content: String!
    url: AWSURL
    ups: Int
    downs: Int
    _version: Int
    _deleted: Boolean
    _lastChangedAt: AWSTimestamp!
}

type PostConnection implements Connection {
    items: [Post!]!
    nextToken: String
    startedAt: AWSTimestamp!
}

type Query {
    getPost(id: ID!): Post
    syncPosts(limit: Int, nextToken: String, lastSync: AWSTimestamp): PostConnection!
}

type Subscription {
    onCreatePost: Post
        @aws_subscribe(mutations: ["createPost"])
    onUpdatePost: Post
        @aws_subscribe(mutations: ["updatePost"])
    onDeletePost: Post
        @aws_subscribe(mutations: ["deletePost"])
}

input DeletePostInput {
    id: ID!
    _version: Int!
}

input UpdatePostInput {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    _version: Int!
}

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

這個 GraphQL 結構描述雖然是標準程序，但在繼續之前，有幾個事項值得我們提出說明。首先，所有變動會先自動寫入 *Base* 資料表，然後寫入 *Delta* 資料表。*Base* 資料表是狀態實況的中心來源，而 *Delta* 資料表是日誌記錄。如果您未傳入 `lastSync: AWSTimestamp`，`syncPosts`查詢會針對 *Base* 資料表執行 ，並補充快取，以及在用戶端離線時間超過 *Delta* 資料表中設定的 TTL 時間時，定期執行 做為節點案例的*全域補齊程序*。如果您傳入 `lastSync: AWSTimestamp`，`syncPosts` 查詢會執行處理您的 *Delta* 資料表，並由用戶端用來擷取自其上次離線後有所變更的事件。Amplify 用戶端會自動傳遞 `lastSync: AWSTimestamp` 值，並依適當情況儲存到磁碟中。

*Post* 上的 *\$1deleted* 欄位用於 **DELETE** 操作。當用戶端離線且記錄已從 *Base* 資料表移除時，此屬性會通知用戶端執行同步處理，將記錄自其本機快取中移除。如果用戶端離線時間較長，而且在用戶端可以配合 Delta Sync 查詢來擷取這個值之前，該項目已先移除，則 Base 查詢 (可在用戶端中設定) 的全域更新事件將會執行，並從快取中移除該項目。此欄位會標記為選用項目，因為只有在執行同步查詢而結果出現已刪除的項目時，才會傳回值。

## 變動
<a name="mutations"></a>

對於所有變動， AWS AppSync 會在*基礎*資料表中執行標準Create/Update/Delete操作，也會自動在 *Delta* 資料表中記錄變更。您可以藉由在資料來源中修改 `DeltaSyncTableTTL` 值，以縮短或延長記錄保留時間。對於擁有高速資料的組織，應該會考慮縮短這段時間。或者，如果您的用戶端離線時間較長，最好設定較長的資料保留時間。

## 同步查詢
<a name="sync-queries"></a>

*基本查詢*是沒有指定`lastSync`值的 DynamoDB Sync 操作。這種做法適合許多組織，因為 Base 查詢只會在一開始時執行，且後續是定期執行。

*Delta 查詢*是指定`lastSync`值的 DynamoDB Sync 操作。每當離線用戶端恢復上線，就會執行 *delta 查詢* (只要 base 查詢定期時間未觸發執行)。用戶端會自動追蹤上次成功執行查詢以同步資料的時間。

執行 delta 查詢時，查詢的解析程式會使用 `ds_pk` 和 `ds_sk` 僅查詢自上次用戶端執行同步以來變更的記錄。用戶端會存放適當的 GraphQL 回應。

如需有關執行同步查詢的詳細資訊，請參閱[同步操作文件](aws-appsync-conflict-detection-and-sync-sync-operations.md)。

## 範例
<a name="example"></a>

首先，藉由呼叫 `createPost` 變動來建立一個項目：

```
mutation create {
  createPost(input: {author: "Nadia", title: "My First Post", content: "Hello World"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

此變動的傳回值將如下所示：

```
{
  "data": {
    "createPost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "My First Post",
      "content": "Hello World",
      "_version": 1,
      "_lastChangedAt": 1574469356331,
      "_deleted": null
    }
  }
}
```

如果您檢查 *Base* 資料表的內容，您會看到一筆記錄，如下所示：

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

如果您檢查 *Delta* 資料表的內容，您將會看見一筆記錄，如下所示：

```
{
  "_lastChangedAt": {
    "N": "1574469356331"
  },
  "_ttl": {
    "N": "1574472956"
  },
  "_version": {
    "N": "1"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:35:56.331:81d36bbb-1579-4efe-92b8-2e3f679f628b:1"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "My First Post"
  }
}
```

現在，我們可以模擬 *Base* 查詢，客戶端將使用以下 `syncPosts` 查詢來執行該查詢以填入其本機資料存放區：

```
query baseQuery {
  syncPosts(limit: 100, lastSync: null, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
      _lastChangedAt
    }
    startedAt
    nextToken
  }
}
```

此 *Base* 查詢的傳回值將如下所示：

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "My First Post",
          "content": "Hello World",
          "_version": 1,
          "_lastChangedAt": 1574469356331
        }
      ],
      "startedAt": 1574469602238,
      "nextToken": null
    }
  }
}
```

稍後，我們將儲存 `startedAt` 值，以模擬 *Delta* 查詢，但需要先對資料表進行變更。讓我們使用 `updatePost` 變動以修改現有的 Post：

```
mutation updatePost {
  updatePost(input: {id: "81d36bbb-1579-4efe-92b8-2e3f679f628b", _version: 1, title: "Actually this is my Second Post"}) {
    id
    author
    title
    content
    _version
    _lastChangedAt
    _deleted
  }
}
```

此變動的傳回值將如下所示：

```
{
  "data": {
    "updatePost": {
      "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
      "author": "Nadia",
      "title": "Actually this is my Second Post",
      "content": "Hello World",
      "_version": 2,
      "_lastChangedAt": 1574469851417,
      "_deleted": null
    }
  }
}
```

如果您現在檢查 *Base* 資料表的內容，您應該會看見更新的項目：

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

如果您現在檢查 *Delta* 資料表的內容，您應該會看到兩筆記錄：

1. 建立項目時的記錄

1. 更新項目時的記錄。

新項目如下所示：

```
{
  "_lastChangedAt": {
    "N": "1574469851417"
  },
  "_ttl": {
    "N": "1574473451"
  },
  "_version": {
    "N": "2"
  },
  "author": {
    "S": "Nadia"
  },
  "content": {
    "S": "Hello World"
  },
  "ds_pk": {
    "S": "AppSync-delta-sync-post:2019-11-23"
  },
  "ds_sk": {
    "S": "00:44:11.417:81d36bbb-1579-4efe-92b8-2e3f679f628b:2"
  },
  "id": {
    "S": "81d36bbb-1579-4efe-92b8-2e3f679f628b"
  },
  "title": {
    "S": "Actually this is my Second Post"
  }
}
```

現在，我們可以模擬 *Delta* 查詢，以擷取用戶端離線時發生的修改。我們將使用基本**查詢傳回`startedAt`的值來提出請求：

```
query delta {
  syncPosts(limit: 100, lastSync: 1574469602238, nextToken: null) {
    items {
      id
      author
      title
      content
      _version
    }
    startedAt
    nextToken
  }
}
```

此 *Delta* 查詢的傳回值將如下所示：

```
{
  "data": {
    "syncPosts": {
      "items": [
        {
          "id": "81d36bbb-1579-4efe-92b8-2e3f679f628b",
          "author": "Nadia",
          "title": "Actually this is my Second Post",
          "content": "Hello World",
          "_version": 2
        }
      ],
      "startedAt": 1574470400808,
      "nextToken": null
    }
  }
}
```