尝试使用Amplify CLI GraphQL Transform命令和指令来操作AppSync+DynamoDB!(@model @auth, @key)

简而言之

AWS Amplify Advent Calendar 2019、第7節将介绍如何使用AWS Amplify CLI在schema.graphql中设置AWS AppSync + Amazon DynamoDB,并且了解可用的三个指令(@model、@auth、@key)。

希望找到读者

放大添加 API,对于那些有点使用经验但是对 @model、@auth 和 @key 等方面不太清楚的人来说,这篇文章就是为他们准备的。验证应用是用 React 来实现的,但是即使没有 React 的知识也没关系。

确认环境中的操作

    • @aws-amplify/cli 4.5.0

 

    • aws-amplify 2.2.0

 

    • aws-amplify-react 3.1.1

 

    @aws-amplify/api 2.1.1

做好准备

React应用程序

$ npx create-react-app amplify-react
$ cd amplify-react
$ npm start

浏览器打开并显示熟悉的起始页。

image.png

放大器的初始设置

假设您已经执行了amplify configure。(如果您尚未执行amplify configure,请点击这里)
请在${profile name}中输入您正在使用的 Amplify 的配置文件名。

amplify init
? Enter a name for the project amplify-react
? Enter a name for the environment dev
? Choose your default editor: Vim (via Terminal, Mac OS only)
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path:  src
? Distribution Directory Path: build
? Build Command:  npm run-script build
? Start Command: npm run-script start
Using default provider  awscloudformation

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use ${profile name}

Amplify的安装已经完成!

创建GraphQL API

在Amplify中,可以选择REST API和GraphQL作为API。选择REST API将创建一个由API Gateway + Lambda构成的REST API模板,选择GraphQL将创建一个由AppSync + DynamoDB构成的GraphQL模板。由于这次想要调整GraphQL,所以在Service中选择GraphQL。

$ amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: amplifyreact
? Choose the default authorization type for the API Amazon Cognito User Pool
Using service: Cognito, provided by: awscloudformation

 The current configured provider is Amazon Cognito.

 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections.
 How do you want users to be able to sign in? Username
 Do you want to configure advanced settings? No, I am done.
Successfully added auth resource
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Do you want a guided schema creation? No
? Provide a custom type name MyType 

如果选择了Amazon Cognito用户池作为默认的授权类型,那么将同时创建Auth类别的资源。可以通过amplify status来确认。

$ amplify status
Current Environment: dev
| Category | Resource name        | Operation | Provider plugin   |
| -------- | -------------------- | --------- | ----------------- |
| Auth     | amplifyreactbc0d7af4 | Create    | awscloudformation |
| Api      | amplifyreact         | Create    | awscloudformation |

我已确认Auth和Api已经分别创建完成。

@模型

@model是什么?

根据公式文档,通过使用@model,可以设置以下AWS资源。

    • デフォルトでPAY_PER_REQUEST請求モードのAmazon DynamoDB

 

    • schema.graphqlで設定した通りにアクセス可能なAWS AppSync DataSource

 

    • AWS AppSyncがAmazon DynamoDBを呼び出すのに必要なIAM Role

 

    • 最大8つのresolver(create, update, delete, get, list, onCreate, onUpdate, onDelete)

 

    • mutation(create, update, delete)に使用するInput objects

 

    list系のqueriyと@connectionで使用可能な、Filter input objects

在 schema.graphql 中,只需非常少的描述,就可以完成如此多的资源设置,这是 Amplify 的一个吸引力之处。

创建ToDo类型

打开amplify/backend/api/amplifyreact/schema.graphql,其内容如下。这次我们将全部删除。

type MyType @model {
    id: ID!
    title: String!
    content: String!
    price: Int
    rating: Float
}

然后定义以下类似的Todo模型。

type Todo 
    @model
{
    id: ID! 
    name: String!
    description: String!
    updatedAt: AWSDateTime 
    createdAt: AWSDateTime 
}

在这里先执行`amplify push`,以使更改生效。
会弹出许多问题,但是全部都只需按下Enter键,不输入任何内容,然后按照默认选项继续操作。

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name        | Operation | Provider plugin   |
| -------- | -------------------- | --------- | ----------------- |
| Auth     | amplifyreactbc0d7af4 | Create    | awscloudformation |
| Api      | amplifyreact         | Create    | awscloudformation |
? Are you sure you want to continue? Yes

The following types do not have '@auth' enabled. Consider using @auth with @model
     - Todo
Learn more about @auth here: https://aws-amplify.github.io/docs/cli-toolchain/graphql#auth


GraphQL schema compiled successfully.

Edit your schema at /Users/daisnaga/Dev/tmp/amplify-react/amplify/backend/api/amplifyreact/schema.graphql or place .graphql files in a directory at /Users/daisnaga/Dev/tmp/amplify-react/amplify/backend/api/amplifyreact/schema
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2

----
a lot of logs...
----

UPDATE_COMPLETE amplify-amplify-react-dev-XXXXXX AWS::CloudFormation::Stack Fri Dec 06 2019 21:36:26 GMT+0900 (Japan Standard Time)
✔ Generated GraphQL operations successfully and saved at src/graphql
✔ All resources are updated in the cloud

GraphQL endpoint: https://XXXXXXXX.appsync-api.us-west-2.amazonaws.com/graphql
image.png
image.png

创建一个用于验证的Web应用程序。

由于服务器端已经准备就绪,让我们创建一个可以添加和确认Todo的界面。

$ npm install --save aws-amplify @aws-amplify/api @aws-amplify/pubsub aws-amplify-react
$ vim src/App.js

打开一个合适的编辑器,然后打开src/App.js文件,将其内容全部删除并复制粘贴以下代码。

import React, {useState, useEffect, useReducer } from 'react';

import Amplify, { Auth } from 'aws-amplify';
import API, { graphqlOperation } from '@aws-amplify/api';

import { withAuthenticator } from 'aws-amplify-react'

import { createTodo } from './graphql/mutations';
import { listTodos } from './graphql/queries';
import { onCreateTodo } from './graphql/subscriptions';

import awsconfig from './aws-exports';
import './App.css';

Amplify.configure(awsconfig);

const QUERY = 'QUERY';
const SUBSCRIPTION = 'SUBSCRIPTION';

const initialState = {
  todos: [],
};

const reducer = (state, action) => {
  switch (action.type) {
    case QUERY:
      return {...state, todos: action.todos};
    case SUBSCRIPTION:
      return {...state, todos:[...state.todos, action.todo]}
    default:
      return state;
  }
};

async function createNewTodo() {
  const todo = { name:  "Todo " + Math.floor(Math.random() * 10) };
  await API.graphql(graphqlOperation(createTodo, { input: todo }));
}

function signOut(){
  Auth.signOut()
  .then(data => console.log(data))
  .catch(err => console.log(err));
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const [user, setUser] = useState(null);

  useEffect(() => {

    async function getUser(){
      const user = await Auth.currentUserInfo();
      setUser(user);
      return user
    }

    getUser();

    async function getData() {
      const todoData = await API.graphql(graphqlOperation(listTodos));
      dispatch({ type: QUERY, todos: todoData.data.listTodos.items });
    }

    getData();


    const subscription = API.graphql(graphqlOperation(onCreateTodo)).subscribe({
      next: (eventData) => {
        const todo = eventData.value.data.onCreateTodo;
        dispatch({ type: SUBSCRIPTION, todo });
      }
    });

    return () => subscription.unsubscribe();
  }, []);

  return (
    <div className="App">
      <p>user: {user!= null && user.username}</p>
      <button onClick={signOut}>Sign out</button>
      <button onClick={createNewTodo}>Add Todo</button>
      <div>
        {state.todos.length > 0 ? 
          state.todos.map((todo) => <p key={todo.id}>{todo.name} ({todo.createdAt})</p>) :
          <p>Add some todos!</p> 
        }
      </div>
    </div>
  );
}

export default withAuthenticator(App, {
  signUpConfig: {
    hiddenDefaults: ['phone_number']
  }
});

我们已经准备好了!让我们看看@auth和@key。

仅需一种选项,请将以下内容用中文进行本地化表达:
@auth

在本部分中,我们将使用@auth来确保只有用户自己创建的待办事项才会显示在列表中。

@auth 是什么?

应用程序需要授权才能与您的GraphQL API进行交互。 API密钥最适用于公共API(或您希望公开部分结构)或原型,且在部署前必须指定过期时间。IAM授权使用签名版本4附加到角色的策略来发出请求。由Amazon Cognito用户池或第三方OpenID Connect提供商提供的OIDC令牌也可用于授权,启用此功能只需提供简单的访问控制,要求用户进行身份验证以获得对API操作的最高级访问权限。您可以在模式中使用@auth来设置更精细的访问控制,这样可以利用这些令牌提供的授权元数据或设置在数据库项目上的授权元数据。

@auth对象类型通过一组授权规则进行保护,这些规则比API的顶级授权提供更多控制。您可以在项目的模式中对对象类型定义和字段定义使用@auth指令。

当在同时使用@model注解的对象类型定义上使用@auth指令时,返回该类型对象的所有解析器都将受到保护。当在字段定义上使用@auth指令时,将在字段上添加一个解析器,该解析器根据父类型中的属性来授权访问。

来源:https://aws-amplify.github.io/docs/cli-toolchain/graphql#auth

简而言之,

    • Amazon Cognito User PoolやサードパーティのOIDCプロバイダによって認証(Authentication)されたユーザーに対し、ユーザーの認証メタデータを使用してGraphQL APIのアクションに対する認可(Authorization)のルールを設定することが可能です。

@authを使用することでAPIの認可をトップレベルで行うことが可能です

@authと@modelがついたtypeのObjectをreturnする全てのGraphQL resolverは、設定したAuthorization Ruleによって保護されます。

将@auth添加到待办事项中

让我们使用@auth编写认证规则,以仅显示自己创建的项目。打开amplify/backend/api/amplifyreact/schema.graphql并进行以下更改。

type Todo 
    @model 
    @auth(rules: [{allow: owner}])
{
    id: ID! 
    name: String!
    owner: String
    description: String!
    updatedAt: AWSDateTime 
    createdAt: AWSDateTime 
}

使用amplify push命令,将这些更改同步到AWS资源上。

$ amplify push

React的更新

当使用开发者工具查看时,可以从AppSync返回以下类型的错误。

"Connection failed: {"errors":[{"message":"Variable 'owner' has coerced Null value for NonNull type 'String!'"}]}"

因为指定了@auth,所以在订阅时需要指定owner字段。让我们来部分修改/src/App.js文件。请删除[-]的代码,并在相同位置添加[+]。

[-]
const subscription = API.graphql(graphqlOperation(onCreateTodo)).subscribe({
  next: (eventData) => {
    const todo = eventData.value.data.onCreateTodo;
    dispatch({ type: SUBSCRIPTION, todo });
  }
});

[+]
let subscription;
getUser().then((user) => {
  subscription = API.graphql(graphqlOperation(onCreateTodo, {owner: user.username})).subscribe({
    next: (eventData) => {
      const todo = eventData.value.data.onCreateTodo;
      dispatch({ type: SUBSCRIPTION, todo });
    }
  });
});

请确认动作是否正确

当您返回应用程序时,您会发现待办事项未显示。这是因为在您之前创建的DynamoDB项目中缺少所有者字段,导致通过@auth被禁止获取。

image.png

接下来,让我们尝试使用另一个用户进行登录。先点击“SignOut”按钮退出当前账号,然后创建另一个账号并进行登录。可以尝试使用其他浏览器或Chrome的隐身模式来进行验证,可能会更方便。

image.png

我们可以看到之前使用的帐户中创建的所有 Todo 未被显示出来。您可以点击“添加 Todo”按钮,然后查看 DynamoDB。

image.png

我可以确认,在owner字段中创建了一个item的User的username已被输入。

额外的东西

根据公式文件显示,实际上刚才的写法与下面的写法完全相同。

type Post
  @model
  @auth(
    rules: [
      {allow: owner, ownerField: "owner", operations: [create, update, delete, read]},
    ])
{
    id: ID! 
    name: String!
    description: String!
    updatedAt: AWSDateTime 
    createdAt: AWSDateTime 
   owner: String
}

ownerFieldを省略するとowner

operationsを省略すると、create, update, delete, read

可以看出每个都有自己的设置。

让我们来看一下“allow”指的是什么。我们可以通过定义来确定。

# When applied to a type, augments the application with
# owner and group-based authorization rules.
directive @auth(rules: [AuthRule!]!) on OBJECT, FIELD_DEFINITION
input AuthRule {
  allow: AuthStrategy!
  provider: AuthProvider
  ownerField: String # defaults to "owner" when using owner auth
  identityClaim: String # defaults to "username" when using owner auth
  groupClaim: String # defaults to "cognito:groups" when using Group auth
  groups: [String]  # Required when using Static Group auth
  groupsField: String # defaults to "groups" when using Dynamic Group auth
  operations: [ModelOperation] # Required for finer control

  # The following arguments are deprecated. It is encouraged to use the 'operations' argument.
  queries: [ModelQuery]
  mutations: [ModelMutation]
}
enum AuthStrategy { owner groups private public }
enum AuthProvider { apiKey iam oidc userPools }
enum ModelOperation { create update delete read }

# The following objects are deprecated. It is encouraged to use ModelOperations.
enum ModelQuery { get list }
enum ModelMutation { create update delete }

在allow中,我们指定了AuthStrategy,也就是认可策略。认可策略有以下四个选项。

owner: itemの作成者をownerとし、ownerに関する認可を指定する

groups: itemの作成者が所属するgroupに対し、groupに関する認可を指定する

private: 認証済みユーザー全体に対して認可を指定する

public: 認証していないユーザー全体に対して認可を指定する

此外,在ownerField里面,可以指定在使用owner的授权策略时要用到的字段。由于@auth可以针对一个type进行多次指定,所以看起来可以非常灵活地创建授权逻辑。

@关键

让我们在本节中使用@key,按Todo的name进行排序并显示列表。

“@key是什么意思?”

Amazon DynamoDB是一种键值和文档数据库,可以在任何规模下提供一位数毫秒级的性能,但是为了使其适用于您的访问模式,需要一些事先的思考。DynamoDB查询操作可以使用最多两个属性来高效地查询数据。传递给查询的第一个查询参数(哈希键)必须使用严格相等性,而第二个属性(排序键)可以使用gt、ge、lt、le、eq、beginsWith和between。DynamoDB可以有效地实现各种强大的访问模式,适用于大多数应用程序。

在DynamoDB中,最重要的部分是粗体的地方。根据DynamoDB的查询指南,最好只使用最多两个属性进行查询。这两个属性被称为分区键(PK)和排序键(SK)。根据DynamoDB的专业资料,可以将PK单独使用,或者将PK和SK组合在一起作为主键使用。

分区表

分区键可作为主键单独使用
用于构建无序哈希索引的键
表可能被分割(分区)以保证性能

分区排序表

可将分区 + 排序作为主键
为保证使用相同的分区键时数据的顺序,使用排序键
分区键数量没有上限(使用本地二级索引时有数据大小限制)

@key的主要作用是指定PK和SK。如果不使用PK和SK来编写查询,将扫描DynamoDB表中的所有内容,效率非常低下。低效不仅导致查询时间长,还会增加费用,因为按量计费。(在Amplify的schema.graphql中设置的查询中,除了使用@key指定的字段以外,无法将其他字段用作输入。如果需要进行全文搜索等操作,建议使用@searchable并利用ElasticSearch资源来创建Resolver。)
因此,关键是在设计应用程序的整体架构时确定使用哪种Key来提取每个项,并从而逆向设计PK和SK。这一过程可能看起来有点繁琐,但通过适当的设计,可以缩短在扩展时进行查询时的延迟时间。有关具体的DynamoDB设计模式,请参阅此处。

在不设置@key的情况下创建的DynamoDB Table默认将什么作为PK和SK?

image.png

当确认后,我们发现作为主键(PK),id被设置,但是SK未设定任何值。

GraphQL 请求返回时进行排序并返回

由於無法更改一次性創建的DynamoDB表的主分割鍵(PK)和排序鍵(SK),因此需要重新創建該表。這次我們將使用DynamoDB的全局二級索引(GSI),通過使用description字段來實現排序功能。

为了适应这种需求,可以通过创建一个或多个全局二级索引,在Amazon DynamoDB上对该索引发出Query请求来使用各种属性作为查询条件执行不同类型的查询。

我们将不详细解释GSI,简而言之就是创建了另一个表,改变了PK和SK,以避免扫描并快速执行特定查询的功能。

这次我们将创建一个新的GSI,采用owner作为PK,name作为SK。

type Todo 
    @model 
    @auth(rules: [{allow: owner}])
    @key(name: "SortByName", fields:["owner", "name"], queryField: "listTodosSortedByName" )
{
    id: ID! 
    name: String!
    owner: String
    updatedAt: AWSDateTime 
    createdAt: AWSDateTime 
}

nameはGSIの名前です

fieldsの配列のうち、一つ目がPK、二つ目がSKになります

queryFieldは、このGSIを用いてqueryする時に使用するresolverの名前です
明示的にownerフィールドを書かないと@keyで指定できないため、ownerフィールドを足しています

那么,请在App.js中进行更改以使用此解析器。请删除[-]处的代码,并在相同位置添加[+]。

[-] 
import { listTodos} from './graphql/queries';

[+] 
import { listTodos, listTodosSortedByName } from './graphql/queries';
[-]
async function getData() {
    const todoData = await API.graphql(graphqlOperation(listTods));
    dispatch({ type: QUERY, todos: todoData.data.listTodos.items });
}
getData();

[+]
async function getData(user) {
    const todoData = await API.graphql(graphqlOperation(listTodosSortedByName, {owner: user.username}));
    dispatch({ type: QUERY, todos: todoData.data.listTodosSortedByName.items });
}
getUser().then((user) => getData(user));

确认行动

当更改完成后,请返回应用程序!可以确认Todo以创建顺序显示,现在已按Todo的名称升序进行了更改。

image.png

在这个例子中,项目数量较少,你可能觉得即使不使用@key,在React的一侧进行排序也没有太大区别。但在实际情况中,如果项目数量非常大,处理在前端变得困难,或者在使用NextToken实现分页时,它会发挥作用。

总结

@modelはAppSyncとDynamoDBのリソースを準備するよーという宣言です。

@authを使い、AppSyncのResolverに対するアクセス権限の設定を行うことが可能です。

@keyを使うことで、任意のフィールドを使用したQuery Resolverを作成することができます。

非常感谢您阅读到最后!明天是AWS Amplify循环日历的@FumihikoSHIROYAMA 活动!

广告
将在 10 秒后关闭
bannerAds