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»