亚马逊AWS官方博客

Amplify DataStore – 借助 GraphQL 简化离线应用程序的开发

开源 Amplify Framework 是一种命令行工具和库,可让 Web 和移动开发人员轻松预置和访问基于云的服务。例如,如果我想为我的移动应用程序创建一个 GraphQL API,我会在我的开发计算机上使用 amplify add api 来配置后端 API。在回答几个问题之后,我键入 amplify push 以在云中创建一个 AWS AppSync API 后端。Amplify 会生成相应的代码,从而让我的应用程序可以轻松访问新创建的 API。Amplify 支持 AngularReactVue 等流行的 Web 框架。它还支持使用 React Native、适用于 iOSSwift 或适用于 AndroidJava 开发的移动应用程序。如果您想了解更多有关将 Amplify 用于移动应用程序的的信息,请报名参加我们为 re:Invent 2019 大会准备的一个研讨会(iOSReact Native)。

AWS 客户告诉我们,开发 Web 和移动应用程序时最难的任务是跨设备同步数据以及处理离线操作。理想情况下,当一台设备离线时,您的客户应该能够继续使用您的应用程序,不仅能够访问数据,而且可以创建和修改数据。当设备在此上线时,该应用程序必须重新连接到后端,同步数据并解决冲突(如有)。即使是使用 AWS AppSync 软件开发工具包的设备内缓存以及离线变异增量同步功能,要正确处理各种边缘案例,仍需要使用许多非差异化的代码。

今天,我们推出 Amplify DataStore,这是一款持久的设备内存储库,方便开发人员写入、读取和观察数据更改。Amplify DataStore 允许开发人员编写利用分布式数据的应用程序,无需为离线或在线场景编写额外的代码。Amplify DataStore 可作为没有连接到云或需要使用 AWS 账户的 Web 和移动应用程序的独立本地数据存储。但在与云后端结合使用时,Amplify DataStore 会在网络连接可用时使用 AWS AppSync API 透明地同步数据。Amplify DataStore 会自动设置数据版本,并在云中使用 AppSync 检测和解决冲突。此工具链还会根据开发人员提供的 GraphQL schema,为我的编程语言生成对象定义。

下面我们来看它的工作原理。

我首先会安装 Amplify CLI创建一个 React 应用程序。这是一个标准的 React 应用程序,您可以在我的 git 存储库中找到脚本。我会使用 npx amplify-appAmplify DataStore 添加到该应用程序。npx 是专用于 NodeJS 的,Amplify DataStore 还集成了多种原生移动工具链,例如适用于 Android StudioGradle 插件,以及会创建适用于 iOS 的自定义 XCode 构建阶段的 CocoaPods

现在我的应用程序框架已经完成,我将添加一个 GraphQL schema,它代表两个实体:即这些博文的 PostsComments。我会安装依赖项并使用 AWS Amplify CLI 为 GraphQL schema 中定义的对象生成源代码。

# 添加一个 graphql schema 到 amplify/backend/api/amplifyDatasource/schema.graphql
echo "enum PostStatus {
  ACTIVE
  INACTIVE
}

type Post @model {
  id: ID!
  title: String!
  comments: [Comment] @connection(name: "PostComments")
  rating: Int!
  status: PostStatus!
}
type Comment @model {
  id: ID!
  content: String
  post: Post @connection(name: "PostComments")
}" > amplify/backend/api/amplifyDatasource/schema.graphql

# 安装依赖项 
npm i @aws-amplify/core @aws-amplify/DataStore @aws-amplify/pubsub

# 生成代表模型的源代码 
npm run amplify-modelgen

# 在云中创建 API 
npm run amplify-push

@model@connectionAmplify GraphQL Transformer 将用于生成代码的指令。 标注 @model 的对象是您的 API 中的顶层对象,它们存储在 DynamoDB 中,您可以使其可搜索、为它们建立版本控制将对它们的访问权限制为授权用户。@connection 指令可用于表达对象之间的 1-n 关系,这与您在使用关系数据库时定义的关系类似(您可以使用 @key 指令来建立 n-n 关系模型)。

最后一步是创建 React 应用程序本身。我建议下载一个非常简单的示例应用程序以快速入门:

# 下载简单的 react 应用程序
curl -o src/App.js https://raw.githubusercontent.com/sebsto/amplify-datastore-js-e2e/master/src/App.js

# 启动应用程序 
npm run start

我通过浏览器连接到应用程序 http://localhost:8080,然后开始测试该应用程序。

示例应用程序提供了一个基本的 UI(正如大家可以猜到,我不是图形设计师!)来创建、查询和删除项目。Amplify DataStore 为开发人员提供了一种方便易用的 API,用于存储、查询和删除数据。读取和写入都将在后台填充到您在云中的 AppSync 终端节点。Amplify DataStore 通过存储适配器来使用本地数据存储,我们为 Web 应用程序使用 IndexedDB,为移动应用程序使用 SQLiteAmplify DataStore 是开源的,因此您可以根据需要添加对其他数据库的支持。

从代码的角度看,与数据的交互十分简单,只需调用 DataStore 对象的 save()delete()query() 操作即可(这是一个 Javascript 示例,您需要为 Swift 或 Java 编写类似的代码)。请注意 query() 操作接受基于 Predicates 表达式的筛选条件,例如 item.rating("gt", 4)Predicates.All

function onCreate() {
  DataStore.save(
    new Post({
      title: `New title ${Date.now()}`,
      rating: 1,
      status: PostStatus.ACTIVE
    })
  );
}

function onDeleteAll() {
  DataStore.delete(Post, Predicates.ALL);
}

async function onQuery(setPosts) {
  const posts = await DataStore.query(Post, c => c.rating("gt", 4));
  setPosts(posts)
}

async function listPosts(setPosts) {
  const posts = await DataStore.query(Post, Predicates.ALL);
  setPosts(posts);
}

我连接到 Amazon DynamoDB 控制台并观察到项目已在后端存储:

支持离线模式无需对我的代码作出任何更改。为模拟离线模式,我关闭了我的 WIFI。我在应用程序中添加了两个项目,然后再次打开 WIFI。此应用程序在离线期间继续正常运行。唯一明显的变化是 _version 字段在离线时没有更新,因为它是在后端填充的。

当网络连接恢复后,Amplify DataStore 会与后端透明地同步。我验证了现在 DynamoDB 中有 5 个项目(每次部署的表名称都不相同,因此请务必调整下面的表名称):

aws dynamodb scan --table-name Post-raherug3frfibkwsuzphkexewa-amplify \
                   --filter-expression "#deleted <> :value"            \
                   --expression-attribute-names '{"#deleted" : "_deleted"}' \
                   --expression-attribute-values '{":value" : { "BOOL": true} }' \
                   --query "Count"

5 // <= there are now 5 non deleted items in the table !

Amplify DataStore 利用 GraphQL 订阅来跟踪后端出现的更改。您的客户可以从其他设备修改数据,Amplify DataStore 将负责透明地同步本地数据存储。无需掌握任何 GraphQL 知识,Amplify DataStore 将自动为您解决底层 GraphQL API 调用问题。实时数据、连接、可扩展性、扇出和广播等等,全部由 Amplify 客户端和 AppSync 使用 WebSocket 协议在背后处理。

我们实际上是将 GraphQL 作为一个网络协议使用,从而通过 HTTPS 动态地将模型实例转换为 GraphQL 文档。

为了在后端发生更改时刷新 UI,我在 useEffect() React 挂钩 中添加了以下代码。它使用 DataStore.observe() 方法来注册回调函数 ( msg => { ... } )。Amplify DataStore 会在 Post 实例在后端发生更改时调用此函数。

const subscription = DataStore.observe(Post).subscribe(msg => {
  console.log(msg.model, msg.opType, msg.element);
  listPosts(setPosts);
});

现在我打开 AppSync 控制台。我查询现有的博文来检索 Post ID。

query ListPost {
  listPosts(limit: 10) {
    items {
      id
      title
      status
      rating
      _version
    }
  }
}

我选择了我的应用程序中的第一篇博文,也就是以 7d8… 开头的博文,然后我发送了以下 GraphQL 变异:

mutation UpdatePost {
  updatePost(input: {
    id: "7d80688f-898d-4fb6-a632-8cbe060b9691"
    title: "updated title 13:56"
    status: ACTIVE
    rating: 7
    _version: 1
  }) {
    id
    title
    status
    rating
    _lastChangedAt
    _version
    _deleted    
  }
}

我立即看到应用程序收到了通知并刷新了它的用户界面。

最后我使用多台设备进行了测试。我首先使用 amplify add hostingamplify publish 为我的应用程序创建了一个托管环境。在应用程序发布后,我同时打开 iOS Simulator 和 Chrome。这两个应用程序最初显示相同的项目列表。我在这两个应用程序中创建新的项目,观察到应用程序在实时刷新 UI。在测试结束时,我删除了所有的项目。

我验证了 DynamoDB 中不再有任何项目(每次部署的表名称都不相同,因此请务必调整下面的表名称):

aws dynamodb scan --table-name Post-raherug3frfibkwsuzphkexewa-amplify \
                   --filter-expression "#deleted <> :value"            \
                   --expression-attribute-names '{"#deleted" : "_deleted"}' \
                   --expression-attribute-values '{":value" : { "BOOL": true} }' \
                   --query "Count"

0 // <= all the items have been deleted !

在与后端同步本地数据时,AWS AppSync 会跟踪版本号以检测冲突。如果存在冲突,默认的解决策略是自动在后端合并更改。自动合并是一种十分简单的冲突解决策略,无需编写客户端侧代码。例如,假设我有一个初始 Post,而 Bob 和 Alice 同时更新了该博文:

原始项目:

{
   "_version": 1,
   "id": "25",
   "rating": 6,
   "status": "ACTIVE",
   "title": "DataStore is Available"
}
Alice 更新了 rating

{
   "_version": 2,
   "id": "25",
   "rating": 10,
   "status": "ACTIVE",
   "title": "DataStore is Available"
}
同时 Bob 更新了 title

{
   "_version": 2,
   "id": "25",
   "rating": 6,
   "status": "ACTIVE",
   "title": "DataStore is great !"
}
自动合并后的最终项目是:

{
   "_version": 3,
   "id": "25",
   "rating": 10,
   "status": "ACTIVE",
   "title": "DataStore is great !"
}

自动合并会根据 GraphQL schema 中定义的类型信息,严格定义字段级别的合并规则。例如,ListMap 将会合并,相互冲突的标量更新(例如数值与字符串)将会导致保留服务器上现有的值。开发人员可以选择其他冲突解决策略:开放式并发(冲突的更新将被拒绝)或自定义(调用 AWS Lambda 函数来决定哪种版本是正确版本)。您可以使用 amplify update api 来选择冲突解决策略。有关这些不同策略的更多信息,请参阅 AppSync 文档

此实例的完整源代码可从我的 git 存储库获取。此应用程序的代码不到 100 行,其中 20% 仅与 UI 有关。请注意我没有编写任何一行的 GraphQL 代码,这一切都是在 Amplify DataStore 中发生的。

Amplify DataStore 云后端现已在所有提供 AppSync 的 AWS 区域开放,在本文执笔之时包括:美国东部(弗吉尼亚北部)美国东部(俄亥俄)美国西部(俄勒冈)亚太地区(孟买)亚太地区(首尔)亚太地区(新加坡)亚太地区(悉尼)亚太地区(东京)欧洲(法兰克福)欧洲(爱尔兰)欧洲(伦敦)

在您的应用程序中使用 Amplify DataStore 不会产生额外的费用,您只需为您使用的后端资源付费,例如 AppSyncDynamoDB(有关定价详细信息请参阅此处此处)。这两种服务都提供免费套餐,以便您了解和免费试用。

Amplify DataStore 让您可以专注于应用程序的商业价值,无需编写无差异化价值的代码。我迫不及待想看到大家使用它构建的杰出应用程序。

— seb