Этот документ описывает все необходимые шаги для создания простого приложения с помощью Amplify. Все основные строительные блоки задокументированы, чтобы служить основой для других проектов:
- Как создать аутентификацию пользователя
- Как настроить конечную точку GraphQL, включая мутации
- Как создавать REST API
- Как создать «безсерверные» функции, которые:
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, что затрудняет изолированную локальную разработку.
Для работы над проектом на другой рабочей станции вам необходимо:
git clone
репозиторийamplify pull
для повторной настройки Amplify и загрузки серверной конфигурации
Продолжает интеграцию / CI
Для CI это может быть достигнуто без головы:
Выпуск кода
Исходный код:
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
Об этом подробнее здесь:
- Определите типы моделей
- Добавить отношения между типами
- Настройка правил авторизации
- Индексируйте свои данные ключами
Доступ к API в React
Вот где в игру вступает магия Amplify. Получить доступ к GraphQL API и изменить его очень просто.
Примерная линия только на первый взгляд кажется сложной, потому что это однострочный элемент, состоящий из трех компонентов:
API.graphql
функция, которая выполняет операции GraphQL. Он автоматически подключается к правильному серверу.graphqlOperation
функция, которая строит операцию GraphQL из заданногоquery
и необязательногоvariables
.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
Фоновые задачи/лямбда-функции
Лямбда-функции или «бессерверные» функции — это небольшие изолированные функции, которые запускаются в определенных ситуациях и не используют ресурсы и ничего не стоят, если не вызываются. Нет режима ожидания, как в случае с обычным сервером, и каждый вызов оплачивается.
Мы создадим три лямбда-функции и один слой:
- onWalletUpdate срабатывает при добавлении новых кошельков.
- updateWalletsInterval выполняется каждые 30 минут для обновления токенов.
- markets — это простая конечная точка RESTful, которая проксирует данные от третьей стороны.
- 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
yarn add
модулей в этой папке или добавьте их вpackage.json
и предоставьте другим функциям- Также хорошее место для размещения общих функций. Они могут потребоваться в других модулях с
require('/opt/nodejs/<filename>')
opt
- Все файлы в этой папке доступны в
/opt
- Хорошее место для ресурсов без кода
Выпуск кода
Исходный код:
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
Данные могут быть прочитаны или изменены с помощью POST
request с запросом, защищенным ключом 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
Что мы сделали?
- Мы создали реагирующее веб-приложение, которое размещено на AWS Amplify.
- Пользователи могут зарегистрироваться и изменить пароль или пользовательские атрибуты
- API GraphQL предоставляет две основные модели, которые считываются и записываются пользователем и серверной частью.
- Изменения данных отображаются на веб-сайте в «реальном времени».
- Были созданы три типа функций:
- Срабатывает при изменении данных
- Задачи, которые выполняются с интервалом
- Конечная точка общедоступного API
Результат можно увидеть здесь: https://tracker.favo.org/