Этот документ описывает все необходимые шаги для создания простого приложения с помощью Amplify. Все основные строительные блоки задокументированы, чтобы служить основой для других проектов:

  1. Как создать аутентификацию пользователя
  2. Как настроить конечную точку GraphQL, включая мутации
  3. Как создавать REST API
  4. Как создать «безсерверные» функции, которые:
    1. Запускаются при изменении данных
    2. Запускаются по расписанию
    3. Запускаются по требованию
    4. Делятся кодом

Иногда абзацы содержат только код или команды, если они говорят сами за себя. Файлы журналов в основном очищены от шума и уменьшены основы.

Пример проекта

Мы создадим веб-приложение React, отслеживающее стоимость крипто-портфелей в долларах США.

Проект находится в открытом доступе на gitlab:
https://gitlab.com/ifavo/amplify-chaintracker

В каждом основном разделе есть релиз для проверки соответствующего кода или сравнения изменений кода:
https://gitlab.com/ifavo/amplify-chaintracker/-/releases

Настраивать

Инициализируйте приложение реактивного проекта и настройте подключение к AWS.

В Amplify Docs есть хорошее руководство по этому разделу: Настройка проекта с полным стеком.

React-проект

npx create-react-app amplify-chaintracker
yarn add --dev standard
yarn add antd wouter aws-amplify @aws-amplify/ui-react

Amplify-Connectivity

Доступ к АМС

Настройте именованный профиль для полного контроля и прозрачности используемой учетной записи AWS. Пример имени профиля: favo.

Настройте учетные данные в ~/.aws/credentials

[favo]
aws_access_key_id=<AWS_ACCESS_KEY_ID>
aws_secret_access_key=<AWS_SECRET_ACCESS_KE>

Создайте раздел профиля в ~/.aws/config:

[profile favo]
region = eu-central-1

Инициализировать приложение Amplify

$ amplify init
? Enter a name for the project ChainTracker
The following configuration will be applied:

Project information
| Name: ChainTracker
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start

? Initialize the project with the above configuration? Yes
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS profile
? Please choose the profile you want to use favo
⠦ Initializing project in the cloud...

✔ Successfully created initial AWS cloud resources for deployments.
✔ Initialized provider successfully.
Initialized your environment successfully.

Your project has been successfully initialized and connected to the cloud!

Настройте Amplify в приложении реакции в пределах src/index.js

import Amplify from 'aws-amplify'
import awsExports from './aws-exports'
Amplify.configure(awsExports)

Добавьте хостинг с ручным развертыванием и опубликуйте веб-приложение:

amplify hosting add
amplify publish

Переключение рабочей станции или OnBoard Teammate

Код в репозитории связан с проектом Amplify в AWS, что затрудняет изолированную локальную разработку.

Для работы над проектом на другой рабочей станции вам необходимо:

  1. git clone репозиторий
  2. amplify pullдля повторной настройки Amplify и загрузки серверной конфигурации

Продолжает интеграцию / CI

Для CI это может быть достигнуто без головы:

  1. Пример сценария AWS Amplify
  2. Пример конфигурации GitLab и скрипты

Выпуск кода

Исходный код:
https://gitlab.com/ifavo/amplify-chaintracker/-/tree/03-amplify-ci

Аутентификация

Предоставление служб аутентификации

amplify auth add

Реализовать компонент аутентификации

AmplifyAuthenticator обеспечивает весь пользовательский опыт от регистрации до входа в систему, включая сброс пароля:

import { AmplifyAuthenticator } from '@aws-amplify/ui-react'

export default function Home () {
  return (
    <AmplifyAuthenticator>
      Homepage
    </AmplifyAuthenticator>
  )
}

Для защиты компонентов от несанкционированного доступа предоставляется компонент более высокого порядка (HoC) для обертывания других компонентов:

import { withAuthenticator } from '@aws-amplify/ui-react'

export default withAuthenticator(function Dashboard () {
  return (
    <>Dashboard</>
  )
})

Если использования и оформления пользовательского интерфейса AWS Cognito недостаточно, весь процесс также можно настроить с помощью библиотеки Auth:

import { Auth } from 'aws-amplify';

async function signUp() {
    try {
        const { user } = await Auth.signUp({
            username,
            password,
            attributes: {
                email,
                phone_number,
                // other custom attributes 
            }
        });
        console.log(user);
    } catch (error) {
        console.log('error signing up:', error);
    }
}

См. также Документацию по аутентификации

Получить текущую информацию о пользователе

Полный профиль и многое другое службы Cognito доступны с Auth.currentAuthenticatedUser().

import { useEffect, useState } from 'react'
import { Auth } from 'aws-amplify'
import { withAuthenticator } from '@aws-amplify/ui-react'

export default withAuthenticator(function Profile () {
  const [user, setUser] = useState()

  useEffect(() => {
    updateUser()
  }, [])

  const updateUser = async () => {
    const user = await Auth.currentAuthenticatedUser()
    setUser(user)
  }

  return (<pre>{JSON.stringify(user, "", 2)}</pre>)
})

Пользовательские атрибуты

будьте осторожны: после добавления их нельзя изменить или удалить
(Custom Attributes@Amazon Cognito)

В amplify/backend/auth/<project folder>/<project>-cloudformation-template.yml можно настроить пул пользователей.

Найдите раздел Schema и настраиваемые атрибуты. В нашем случае currency как string:

Schema:
        
        - Name: currency
          Required: false
          Mutable: true
          AttributeDataType: String
          StringAttributeConstraints:
            MinLength: 3
            MaxLength: 3

Больше вариантов ищите в документации.

Изменение атрибутов требует префикса custom: и является однострочным с модулем Auth:

await Auth.updateUserAttributes(user, { 'custom:currency': currency })

Выпуск кода

Исходный код:
https://gitlab.com/ifavo/amplify-chaintracker/-/tree/04-auth-cognito

API (только данные, GraphQL)

Amplify предоставляет готовое решение и генерирует запросы на чтение, мутации и подписки.

Добавить новый Amplify API

Создайте новую конечную точку API с аутентификацией по ключу API:

amplify api add

Предварительно сгенерированная схема является хорошей отправной точкой, если вы хотите учиться и экспериментировать.

У нас будет две модели, которые связаны и могут запрашиваться вместе.

Кошелек – это список всех адресов, которые будут отслеживаться, и пользователей, которые их добавили.
Баланс – это список токенов, найденных в блокчейне.

Мы уже знаем наши потребности и меняем amplify/backup/api/wallets/schema.graphqlна нашу конфигурацию схемы:

type Wallet 
  @model 
  @key(name: "byNetwork", fields: ["network"])
  @key(name: "byOwner", fields: ["owner"])
  {
    id: ID!
    owner: String!
    name: String!
    network: String!
    address: String!
    balances: [Balance] @connection(keyName: "byWallet", fields: ["id"])
    isDeleted: Boolean
}

type Balance
  @model
  @key(name: "byWallet", fields: ["walletID", "token"])
  {
    id: ID!
    walletID: ID!
    owner: String!
    token: String!
    balance: Float!
  }

@connection обеспечит удаление Балансов при удалении Кошелька.

Amplify может генерировать фрагменты кода и запросы GraphQL для облегчения доступа. Вы можете настроить его с помощью codegen:

amplify codegen update

Чтобы опубликовать все изменения в инфраструктуре AWS, выполните следующие действия:

amplify push

Об этом подробнее здесь:

  1. Определите типы моделей
  2. Добавить отношения между типами
  3. Настройка правил авторизации
  4. Индексируйте свои данные ключами

Доступ к API в React

Вот где в игру вступает магия Amplify. Получить доступ к GraphQL API и изменить его очень просто.

Примерная линия только на первый взгляд кажется сложной, потому что это однострочный элемент, состоящий из трех компонентов:

  1. API.graphql функция, которая выполняет операции GraphQL. Он автоматически подключается к правильному серверу.
  2. graphqlOperationфункция, которая строит операцию GraphQL из заданного query и необязательного variables.
  3. createWalletили другой импорт из папки graphqlгенерируются запросы GraphQL

Добавить новую строку

import { API, graphqlOperation } from 'aws-amplify'
import { createWallet } from '../graphql/mutations'

// …

await API.graphql(graphqlOperation(createWallet, { input: { owner, name, network, address, isDeleted: false } }))

Обновить или удалить строку

_version требуется для предотвращения состояния гонки и модификации просроченных данных:

const currentData = await API.graphql(graphqlOperation(getWallet, { id }))
const { _version } = currentData.data.getWallet
const updatedData = await API.graphql(graphqlOperation(updateWallet, { input: { id, _version, name: `${new Date()}` } }))

const removedData = await API.graphql(graphqlOperation(deleteWallet, { input: { id, _version: updatedData.data.updateWallet._version } }))

Подпишитесь на обновления для получения обновлений в реальном времени

Подписка на мутации набора данных обеспечивает возможность обновления в реальном времени. Например, обновление списка всякий раз, когда данные добавляются или удаляются.

import { onCreateWallet, onDeleteWallet } from '../graphql/subscriptions'

// …

 useEffect(() => {
    const walletCreateSubscription = API.graphql(
      graphqlOperation(onCreateWallet)
    ).subscribe({
      next: fetchWallets
    })

    const walletDeleteSubscription = API.graphql(
      graphqlOperation(onDeleteWallet)
    ).subscribe({
      next: fetchWallets
    })

    return () => {
      walletCreateSubscription.unsubscribe()
      walletDeleteSubscription.unsubscribe()
    }
  }, [])

Выпуск кода

Исходный код:
https://gitlab.com/ifavo/amplify-chaintracker/-/tree/05-api-graphql

Фоновые задачи/лямбда-функции

Лямбда-функции или «бессерверные» функции — это небольшие изолированные функции, которые запускаются в определенных ситуациях и не используют ресурсы и ничего не стоят, если не вызываются. Нет режима ожидания, как в случае с обычным сервером, и каждый вызов оплачивается.

Мы создадим три лямбда-функции и один слой:

  1. onWalletUpdate срабатывает при добавлении новых кошельков.
  2. updateWalletsInterval выполняется каждые 30 минут для обновления токенов.
  3. markets — это простая конечная точка RESTful, которая проксирует данные от третьей стороны.
  4. chaintrackerwalletLibrary – это уровень, содержащий общие функции, общие с другими функциями.

Триггеры

Мы хотим подгружать баланс кошелька при добавлении в систему нового кошелька.

Триггер Lambda будет выполняться при изменении данных, настроенном с помощью интерфейса Amplify:

amplify function add
? Select which capability you want to add: Lambda function (serverless f
unction)
? Provide an AWS Lambda function name: onWalletUpdate
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Lambda trigger
? What event source do you want to associate with Lambda trigger? Amazon
 DynamoDB Stream
? Choose a DynamoDB event source option Use API category graphql @model 
backed DynamoDB table(s) in the current Amplify project
Selected resource wallets
? Choose the graphql @model(s) Wallet

Для записи в Balance-Model требуется доступ, который также настраивается с помощью cli:

amplify function update
? Select the Lambda function you want to update onWalletUpdate
General information
- Name: onWalletUpdate
? Which setting do you want to update? Resource access permissions
? Select the categories you want this function to have access to. api
? Select the operations you want to permit on wallets Query, Mutation

You can access the following resource attributes as environment variables from your Lambda function
        API_WALLETS_GRAPHQLAPIENDPOINTOUTPUT
        API_WALLETS_GRAPHQLAPIIDOUTPUT
        API_WALLETS_GRAPHQLAPIKEYOUTPUT

Не так интуитивно понятно и, возможно, ошибка в Amplify заключается в том, что API необходимо настроить для «разрешения конфликтов», чтобы всегда правильно запускать функции Lambda. Мне потребовалось некоторое время, чтобы понять это.

После включения «разрешения конфликтов» будущие мутации в нашем приложении требуют атрибута _version. Повторно сгенерируйте весь GraphQL с помощью codegen, чтобы прочитать его со всеми запросами и сделать его доступным, когда это необходимо.

amplify api update
? Please select from one of the below mentioned services: GraphQL
? Select from the options below Walkthrough all configurations
? Choose the default authorization type for the API API key
? Enter a description for the API key: 
? After how many days from now the API key should expire (1-365): 7
? Do you want to configure advanced settings for the GraphQL API Yes, I 
want to make some additional changes.
? Configure additional auth types? No
? Enable conflict detection? Yes
? Select the default resolution strategy Auto Merge
? Do you want to override default per model settings? No

amplify codegen
✔ Downloaded the schema
✔ Generated GraphQL operations successfully and saved at src/graphql

Интервальные/регулярные задачи

Для синхронизации кошельков мы будем обновлять их баланс каждые 30 минут. Поможет лямбда-функция с настройкой интервала. Также требуется доступ к API GraphQL:

amplify function add
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: updateWalletsInterval
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda
 function? Yes
? Select the categories you want this function to have access to. api
? Select the operations you want to permit on wallets Query, Mutation

You can access the following resource attributes as environment variables from your Lambda function
        API_WALLETS_GRAPHQLAPIENDPOINTOUTPUT
        API_WALLETS_GRAPHQLAPIIDOUTPUT
        API_WALLETS_GRAPHQLAPIKEYOUTPUT
        ENV
        REGION
? Do you want to invoke this function on a recurring schedule? Yes
? At which interval should the function be invoked: Minutes
? Enter the rate in minutes: 30
? Do you want to enable Lambda layers for this function? No
? Do you want to configure environment variables for this function? No
? Do you want to configure secret values this function can access? No
? Do you want to edit the local lambda function now? No
Successfully added resource updateWalletsInterval locally.

Слой

Функция onWalletUpdate и функция updateWalletsInterval имеют в основном одинаковую функциональность. Чтобы сэкономить ресурсы (и затраты) на AWS, идентичную функциональность и node_modules можно совместно использовать с помощью слоев.

При разработке советую их не использовать, т.к. локальные mock-runs не поддерживаются.

Без слоев вы можете запустить amplify mock run <function name> и протестировать свою функцию без ее предварительного развертывания. При использовании слоев вам потребуется выполнять развертывание для каждого тестового запуска.

В нашей ситуации Layer убирает всю дублированность обеих функций и упрощает дальнейшую разработку до одной Lambda-функции.

Добавьте слой и настройте обе существующие функции, чтобы использовать его:

amplify function add
? Select which capability you want to add: Lambda layer (shared code & resource used across functions)
? Provide a name for your Lambda layer: walletLibrary
? Choose the runtime that you want to use: NodeJS
? The current AWS account will always have access to this layer.
Optionally, configure who else can access this layer. (Hit <Enter> to skip) 
✅ Lambda layer folders & files created:
amplify/backend/function/chaintrackerwalletLibrary


amplify function update
? Select which capability you want to update: Lambda function (serverless function)
? Select the Lambda function you want to update onWalletUpdate
? Which setting do you want to update? Lambda layers configuration
? Do you want to enable Lambda layers for this function? Yes
? Provide existing layers or select layers in this project to access from this function (pick up to 5): chaintrackerwalletLibrary


amplify function update
? Select which capability you want to update: Lambda function (serverless function)
? Select the Lambda function you want to update updateWalletsInterval
? Which setting do you want to update? Lambda layers configuration
? Do you want to enable Lambda layers for this function? Yes
? Provide existing layers or select layers in this project to access from this function (pick up to 5): chaintrackerwalletLibrary

Папки слоев

Две папки в Lambda Layer важны

lib/nodejs

  1. yarn add модулей в этой папке или добавьте их в package.jsonи предоставьте другим функциям
  2. Также хорошее место для размещения общих функций. Они могут потребоваться в других модулях с require('/opt/nodejs/<filename>')

opt

  1. Все файлы в этой папке доступны в /opt
  2. Хорошее место для ресурсов без кода

Выпуск кода

Исходный код:
https://gitlab.com/ifavo/amplify-chaintracker/-/tree/06-lambda-function

API (функциональный, RESTful)

REST API предоставляют место для типичной серверной логики. Шаблоны при создании варьируются от примера «Hello World» до простого сервера Express JS.

Мы продемонстрируем API, извлекая данные из стороннего сервиса, чтобы получить текущую стоимость токена в долларах США:

amplify api add
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: markets
? Provide a path (e.g., /book/{isbn}): /markets
? Choose a Lambda source Create a new Lambda function
? Provide an AWS Lambda function name: markets
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Serverless ExpressJS function (Integration with API Gateway)

? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? No
Successfully added resource markets locally.
Succesfully added the Lambda function locally
? Restrict API access Yes
? Who should have access? Authenticated users only
? What kind of access do you want for Authenticated users? read
? Do you want to add another path? No
Successfully added resource markets locally

Экспресс JS

Новое приложение API создается по адресу amplify/backend/function/markets/src. В эту папку мы добавим CoinGecko-api и отредактируем app.js, чтобы предоставить доступ к данным рынка токенов:

const CoinGecko = require('coingecko-api')
const CoinGeckoClient = new CoinGecko()

app.get('/markets', async function (req, res) {
  const { data: markets } = await CoinGeckoClient.coins.markets({ vs_currency: 'usd' })
  const symbolValue = {}
  markets.forEach(({ symbol, current_price: price }) => {
    symbolValue[symbol] = price
  })

  res.json(symbolValue)
})

app.get('/markets/:token', async function (req, res) {
  const { data } = await CoinGeckoClient.coins.fetch(req.params.token)
  res.json(data)
})

Доступ к API в React

Используя HTTP-глаголы, доступ контролируется с помощью API. Загрузка данных из пользовательской конечной точки — это одна строка:

import { API } from 'aws-amplify'

// …

const market = await API.get('markets', `/markets/${token}`)

Доступ к API GraphQL в функциях Lambda

Данные могут быть прочитаны или изменены с помощью POSTrequest с запросом, защищенным ключом API. Вся необходимая информация предоставляется через переменные среды, которые перечислены во время настройки с помощью Amplify cli и в заголовке src/index.js в каждой функции Lambda.

axiosи graphqlмодули помогут:

const axios = require('axios')
const gql = require('graphql-tag')
const graphql = require('graphql')
const { print } = graphql

const getWalletQuery = gql`
query GetWallet($id: ID!) {
  getWallet(id: $id) {
    id
    address
	network
  }
}
`

module.exports = async function getWallet (id) {
  try {
    const { data } = await axios({
      url: process.env.API_WALLETS_GRAPHQLAPIENDPOINTOUTPUT,
      method: 'post',
      headers: {
        'x-api-key': process.env.API_WALLETS_GRAPHQLAPIKEYOUTPUT
      },
      data: {
        query: print(getWalletQuery),
        variables: { id }
      }
    })
    return data.data.getWallet
  } catch (err) {
    console.log('error posting to appsync: ', err)
  }
}

Выпуск кода

Исходный код:
https://gitlab.com/ifavo/amplify-chaintracker/-/tree/07-api-rest

Что мы сделали?

  1. Мы создали реагирующее веб-приложение, которое размещено на AWS Amplify.
  2. Пользователи могут зарегистрироваться и изменить пароль или пользовательские атрибуты
  3. API GraphQL предоставляет две основные модели, которые считываются и записываются пользователем и серверной частью.
  4. Изменения данных отображаются на веб-сайте в «реальном времени».
  5. Были созданы три типа функций:
  6. Срабатывает при изменении данных
  7. Задачи, которые выполняются с интервалом
  8. Конечная точка общедоступного API

Результат можно увидеть здесь: https://tracker.favo.org/