API без сервера с CDK (2 серии деталей)
Serverless дает нам огромные возможности для того, чтобы в начале быстрее отправлять новые цифровые продукты. Наиболее классический способ использования Server Bless — это API, поддерживаемый AWS Lambdas. Этот блог будет направлять предоставление и развертывание приложения с использованием версии AWS CDK версии 2. Однако, когда я начал его реализовать, начали появляться проблемы. Я бы сказал о них, изображая процесс и код. Serverless даст возможность бесплатно иметь полную настройку API.
Прежде всего, что мы строим? Это будет API -шлюз, поддерживаемый AWS Lambdas. Они будут читать и записывать элементы в таблице DynamoDB. Итак, вот мы, создав Crud с API REST. Можно найти репозиторий GitHub Здесь Анкет
API Gateway -> AWS Lambda -> DynamoDB
Чтение отлично подходит для многих вещей. Так что наши предметы будут книги! Модель будет такой:
{
"title": "name of the book>",
"author": "",
"yearPublished": "",
"isbn": ""
}
Реализация
В этом демонстрационном приложении мы используем Dynamo DB. Это база данных NOSQL, где AWS управляет инфраструктурой. Он работает как волшебное заклинание с функциями Lambda. Это приложение будет выполнять классические операции CRUD.
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
import { Book } from '../models/book';
import uuid from 'uuid';
const dynamo = new DocumentClient();
export async function create(table: string, book: Book) {
const params = {
TableName: table,
Item: {
id: uuid.v4(),
...book
}
}
const dbResponse = await dynamo.put(params).promise();
return params.Item;
}
Старт будет связан с написанными элементами в таблице. TypeScript позволяет иметь сильно напечатанный объект. Единственное, что будет здорово иметь максимально уникальную, это UUID. В этом случае получение книги от идентификатора будет простым. Операция удаления будет очень похожа.
export async function get(table: string, id: string) {
const params = {
TableName: table,
Key: {
id
}
};
const dbResponse = await dynamo.get(params).promise();
return dbResponse.Item;
}
export async function deleteItem(table: string, id: string) {
const params = {
TableName: table,
Key: {
id
}
}
await dynamo.delete(params).promise();
}
Было бы здорово проверить созданные элементы. Здесь я использую операцию «сканирования», которая проходит через всю таблицу. Для демонстрационных целей этого должно быть достаточно. Когда это возможно, нужно использовать «запрос».
export async function list(table: string): Promise{ const params = { TableName: table, } const dbResponse = await dynamo.scan(params).promise(); if (dbResponse.Items) { return dbResponse.Items; } throw new Error('Cannot get all books'); }
Самый сложный запрос — обновить книгу из базы данных. Он должен иметь параметры и значения для редактирования.
export async function update(table: string, id: string, book: Book) {
const params = {
TableName: table,
Key: {
id,
},
ExpressionAttributeValues: {
':title': book.title,
':author': book.author,
':yearPublished': book.yearPublished,
':isbn': book.isbn
},
UpdateExpression: 'SET title = :title, ' +
'author = :author, yearPublished = :yearPublished, isbn = :isbn',
ReturnValues: 'UPDATED_NEW',
}
await dynamo.update(params).promise();
}
Инъекция зависимости — это первое осложнение, которое происходит с Lambdas. Если кто -то упаковывает его в AWS с помощью CDK или CloudFormation, не будет никаких зависимостей. Я использую TypeScript в проекте, поэтому, теоретически, запуск создаст рабочий файл JS:
npm i -D typescript tsc init tsc
Однако это не так просто. Здесь есть две проблемы:
- Упаковка дополнительных файлов с функциональностью, потому что наличие Lambda с реализацией внутри одного файла — плохая практика
- Упаковка внешних зависимостей. В нашем случае это будет Uuid.
Я попытался упаковать только SRC/функции/создать. Бег Lambda даст это:
Вот почему следующим логическим шагом будет выяснить, как упаковать код Lambda вместе с зависимостями и не загружать 100 МБ node_modules. Я решил использовать WebPack для этого. Он правильно делает задание и позволит запустить только эту команду после установки: WebPack
Функции основного обработчика Lambdas будут так же просты, как вызов файла DynamoDB с необходимыми операциями. Например, создание элемента было бы таким.
import { APIGatewayProxyEventV2, Callback, Context } from 'aws-lambda';
import { create } from '../connectors/dynamo-db-connector';
export async function handler (event: APIGatewayProxyEventV2, context: Context, callback: Callback) {
if (typeof event.body === 'string') {
const bookItem = JSON.parse(event.body);
const createBook = await create(process.env.table as string, bookItem);
const response = {
statusCode: 201,
}
callback(null, response);
}
callback(null, {
statusCode: 400,
body: JSON.stringify('Request body is empty!')
});
}
Можно найти обработчиков для других операций в Репозиторий Анкет
Инфраструктура
Как упоминалось ранее, CDK будет отвечать за обеспечение инфраструктуры. Я буду использовать только один стек с DynamoDB, 5 Lambdas, REST API, разрешениями для подключений к таблице и интеграциями для конечных точек. Мы поместим приложение инфраструктуры в отдельную папку/инфра.
Чтобы создать DynamoDB с помощью Lambda, который его называют, понадобятся готовые типичные конструкции для DynamoDB, Lambda и разрешений. Код расположен на одном уровне выше, поэтому важно выразить его так. Хорошая вещь здесь в том, что CDK ошибся, когда кто -то называет «синтезирование» шаблона с неправильными параметрами. После этого Lambda понадобится разрешение на размещение элемента в DynamoDB. Можно сделать это, добавив одну строку кода.
const dynamoTable = new ddb.Table(this, 'BookTable', {
tableName: 'BookStorage',
readCapacity: 1,
writeCapacity: 1,
partitionKey: {
name: 'id',
type: ddb.AttributeType.STRING,
},
})
const createBookFunction = new lambda.Function(this, 'CreateHandler', {
runtime: lambda.Runtime.NODEJS_14_X,
code: lambda.Code.fromAsset('../code'),
handler: 'create.handler',
environment: {
table: dynamoTable.tableName
},
logRetention: RetentionDays.ONE_WEEK
});
dynamoTable.grant(createBookFunction, 'dynamodb:PutItem')
После этого функция может быть прикреплена к API. Во -первых, мы инициализируем Restapi. Затем мы добавляем ресурс, чтобы учесть, что у него есть путь, например, post/.
const api = new apigw.RestApi(this, `BookAPI`, {
restApiName: `book-rest-api`,
});
const mainPath = api.root.addResource('books');
const createBookIntegration = new apigw.LambdaIntegration(createBookFunction);
mainPath.addMethod('POST', createBookIntegration);
Шаги, показанные в этом разделе, будут повторяться и для других конечных точек. Лучшая часть приходит, когда можно увидеть, что создание лямбды одинаково, но с разными параметрами. CDK может завернуть его в функцию для сохранения пространства и иметь многократный код для обеспечения обработчиков.
Тестирование конечных точек
После манипуляций, упомянутых в предыдущих разделах, мы можем попробовать API, упомянутый в этой статье. Давайте начнем со списка всех книг. Результат должен быть пустым.
curl --location --request GET 'https://.execute-api. .amazonaws.com/prod/books' RESPONSE: Status: 200 []
После этого давайте создадим пару книг.
curl --location --request POST 'https://.execute-api. .amazonaws.com/prod/books' \ --header 'Content-Type: application/json' \ --data-raw '{ "title": "Life of PI", "author": "Yann Martel", "yearPublished": "2000", "isbn": "0-676-97376-0" }' RESPONSE: Status: 201
А также еще один:
curl --location --request POST 'https://.execute-api. .amazonaws.com/prod/books' \ --header 'Content-Type: application/json' \ --data-raw '{ "title": "Simulacra and Simulation", "author": "Jean Baudrillard", "yearPublished": "0-472-06521-1", "isbn": "0-676-97376-0" }' RESPONSE: Status: 201
Теперь мы можем позвонить в список, чтобы убедиться, что все книги находятся на их месте.
curl --location --request GET 'https://.execute-api. .amazonaws.com/prod/books' RESPONSE: Status: 200 [{ "isbn": "0-676-97376-0", "id": "617d6b3e-ce6d-4e8d-a10f-05d6703ad7ac", "yearPublished": "2000", "title": "Life of PI", "author": "Yann Martel" }, { "isbn": "0-676-97376-0", "id": "2a8251ee-73ee-4717-8f6f-0f11dd2b861f", "yearPublished": "0-472-06521-1", "title": "Simulacra and Simulation", "author": "Jean Baudrillard" }]
Lambda добавил книги, и список также работает. Однако в базе данных есть ошибка. Я заметил, что «Жизнь Пи» была опубликована в 2001 году, а не в 2000 году. Итак, нам нужно позвонить в конечную точку обновления.
curl --location --request PUT 'https://y55xcv8jmc.execute-api.eu-west-1.amazonaws.com/prod/books/617d6b3e-ce6d-4e8d-a10f-05d6703ad7ac' \
--header 'Content-Type: application/json' \
--data-raw '{
"isbn": "0-676-97376-0",
"yearPublished": "2001",
"title": "Life of PI",
"author": "Yann Martel"
}'
Status: 200
Еще один звонок в список покажет, что книга была успешно обновлена.
[{
"isbn": "0-676-97376-0",
"id": "617d6b3e-ce6d-4e8d-a10f-05d6703ad7ac",
"yearPublished": "2001",
"author": "Yann Martel",
"title": "Life of PI"
}, {
"isbn": "0-676-97376-0",
"id": "2a8251ee-73ee-4717-8f6f-0f11dd2b861f",
"yearPublished": "0-472-06521-1",
"title": "Simulacra and Simulation",
"author": "Jean Baudrillard"
}]
Давайте удалим одну из книг, позвонив в конечную точку Delete.
curl --location --request DELETE 'https://.execute-api. .amazonaws.com/prod/books/2a8251ee-73ee-4717-8f6f-0f11dd2b861f' Status: 200
После вызова его несколько раз с разными идентификаторами, можно получить пустой ответ для списка книг. Это то, что мы ожидаем здесь.
В этой статье я показал, как развернуть API без сервера с базой данных NOSQL с использованием AWS Lambda, DynamoDB и API Gateway, развернутой CDK. У Lambdas есть все разрешения на операции CRUD. Это может быть образцом проекта для более сложной настройки. Кроме того, я использовал WebPack, чтобы иметь все зависимости узел. Будущая работа будет включать в себя Codepipeline для CI/CD, подключенного к крючке GitHub. Спасибо, что прочитали эту статью!
Хотите узнать больше о AWS, Serverless и CDK? Подпишитесь на мой блог, где я регулярно публикую по этим темам.
API без сервера с CDK (2 серии деталей)
Оригинал: «https://dev.to/grenguar/how-to-build-serverless-api-with-database-using-aws-cdk-4i2d»