Я делаю успехи в веб-приложении Bearcam Companion. Я реализовал большинство основных компонентов фронтенда React с соответствующими бэкендами Amplify. Однако некоторые функции, которые я реализовал в пользовательском интерфейсе, действительно должны быть автоматизированы. Для этого требуется один из основных продуктов бессерверной архитектуры — AWS Lambda.

АВС Лямбда

Что такое AWS Lambda? Вот что написано на странице AWS Lambda:

AWS Lambda — это бессерверная служба вычислений, управляемая событиями, которая позволяет запускать код практически для любого типа приложений или серверных служб без выделения серверов или управления ими. Вы можете запускать Lambda из более чем 200 сервисов AWS и приложений «программное обеспечение как услуга» (SaaS) и платить только за то, что используете.

Создание Lambda с помощью Amplify CLI

Первое, что я хотел автоматизировать, — запускать модели машинного обучения для обнаружения объектов на каждом новом изображении. В предыдущем посте я описал, как я добился этого с помощью Amazon Rekognition из пользовательского интерфейса. В своем самом последнем посте я описал, как загружаю изображения в S3 и обновляю таблицу Изображения. Теперь я хочу использовать обновление таблицы Images, чтобы Lambda запускала Rekognition для изображения и сохраняла результаты обнаружения объектов в таблице Objects.

Я создал Lambda с помощью Amplify CLI для добавления функции:

amplify add function

Существует множество вариантов настройки Lambda, поэтому внимательно прочитайте документацию. Для моих нужд вот некоторые ключевые настройки:

  • Имя функции: bcOnImagesFindObjects
  • Время выполнения: NodeJS
  • Шаблон функции: функция CRUD для Amazon DynamoDB, так как я буду читать из таблицы Изображения и сохранять результаты Rekognition в таблице Объекты.
  • Доступ к ресурсам: конечные точки GraphQL для изображений и объектов
  • Триггер: DynamoDB Lambda Trigger для Изображений

Разработка лямбды

После создания шаблон функции появится в вашем проекте в разделе:

amplify/backend/function/<function-name>/src/index.js

Шаблон обеспечивает базовую структуру, из которой можно строить. Данные триггера поступают в потоке событий (для повышения эффективности несколько событий могут быть объединены в пакеты). Первое, что я сделал, это проанализировал записи событий. Меня интересуют только события INSERT. Из них я вытаскиваю информацию S3 для образа. Вот моя функция parseRecords():

function parseRecords (records) {
  var inserts = [];
  records.forEach(record => {
    if (record.eventName === "INSERT")
    {
      // get image info
      const imageS3obj = record.dynamodb.NewImage.file.M
      const insert = {
        imageID: record.dynamodb.NewImage.id.S,
        Bucket: imageS3obj.bucket.S,
        Region: imageS3obj.region.S,
        Key: "public/" + imageS3obj.key.S
      }
    }
  });
  return (inserts);
}

Затем я перебираю изображения, вызывая processLabel(), который отправляет изображение в Rekognition для обнаружения объектов с помощью rekognition.detectLabels:

async function processImage(imageInfo) {
  const params = {
    Image: {
      S3Object: {
        Bucket: imageInfo.Bucket,
        Name: imageInfo.Key
      },
    },
    MinConfidence: MinimumConfidence
  }
  return await rekognition.detectLabels(params).promise();
}

Для каждого результата я вызываю parseDetections(), чтобы извлечь соответствующую информацию о ограничивающей рамке из ответа JSON:

function parseDetections(detections) {
  var boxes = [];
  const labels = detections.Labels;
  labels.forEach(object => {
    object.Instances.forEach(instance => {
      var bb = instance.BoundingBox;
      const box = {
        Name: object.Name,
        Confidence: instance.Confidence,
        Width: bb.Width,
        Height: bb.Height,
        Left: bb.Left,
        Top: bb.Top
      }
      boxes.push(box);
    })
  })
  return (boxes);
}

Наконец, я сохраняю каждое поле в таблице Objects, используя fetch() для POST данных в соответствующую конечную точку GraphQL. Основной обработчик выглядит так:

exports.handler = async function(event, context, callback) {
  try { // Parse DynamoDB Images Records
    const inserts = parseRecords(event.Records);
    for (insert of inserts) {
      // Call Rekognition on every new image
      let detections = await processImage(insert);
      const boxes = parseDetections(detections);
      for (box of boxes) {
        // Save each bounding box to Objects
        const options = getFetchOptions(box, insert.imageID);
        response = await fetch(GRAPHQL_ENDPOINT, options);
        body = await response.json();
        if (body.errors) {
          console.log("GraphQL error", body);
        } else {
          console.log("GraphQL success")
        }
      }
    }
  } catch (err) {
    callback(err.message);
  }
  return { status: "complete" };
}

После завершения вы можете развернуть Lambda с помощью amplify push. Конечно, сначала не получилось!

Локальное тестирование Lambda

Существует несколько способов отладки Lambdas. Вы можете начать тестирование локально, используя amplify mock function. Функция mock запустит Lambda локально и передаст ей данные о событиях из файла JSON. Мне удалось захватить событие потока DynamoDB из CloudWatch, которое я использовал в качестве тестового JSON.

Одна из моих основных проблем, и уже не в первый раз, была связана с асинхронными функциями. У меня все еще нет У меня все еще есть некоторые проблемы с ожиданиями, обещаниями и т. д. В основном я использую await внутри функций async, но иногда я обнаруживаю, что данные не возвращаются, потому что я каким-то образом вернулся из функции до того, как данные прибыли.

Еще одна проблема, с которой я столкнулся, заключалась в записи данных напрямую в DynamoDB. Это работает, но не заполняет все автоматические поля, созданные Amplify. Вместо этого используйте конечные точки GraphQL для записи через AppSync.

Тестирование Lambda в консоли

Одной из первых проблем, с которой я столкнулся, выполняя amplify push развертывание Lambda, был отсутствующий модуль. Не удалось выполнить следующую строку:

const fetch = require('node-fetch');

Неудивительно, что node-fetch не является частью стандартной среды выполнения NodeJS. Как-то мне нужно было включить этот пакет. Я мог либо перейти в каталог src функции Lambda и установить там пакет, либо использовать Lambda Layer. Я выбрал последнее. Подробнее об этом чуть позже.

Как только Lambda загрузится правильно, вы можете протестировать и изменить код в консоли Lambda:

Вы можете тестировать с заранее определенными файлами событий JSON так же, как и с amplify mock:

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

Из журналов мониторов вы можете перейти к CloudWatch LogStream:

Лямбда-слои

Слои Lambda предоставляют средства для совместного использования общих библиотек в нескольких Lambdas. Вот схема из Расширить документы по слоям.

С помощью amplify вы добавляете слой Lambda так же, как вы добавляете Lambda:

amplify add function

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

npm i node-fetch

Когда я закончу настройку слоя Lambda, мне нужно обновить функцию Lambda, чтобы она использовала слой:

amplify update function

Когда я закончу со всем, я могу развернуть функцию обновлений и новый слой с amplify push.

У меня все еще была ошибка, связанная с версиями JavaScript. Мне пришлось понизить версию node-fetch с 3.x до 2.x. Как только я это сделал, я повторно развернул слой Lambda и обновил функцию Lambda, чтобы использовать новую версию. Я вижу информацию о триггере и уровне в обзоре функции Lambda:

Заключение

В этом посте я описал

  • Создание функции Lambda, запускаемой изменением в таблице DynamoDB
  • Тестирование функции Lambda локально и в консоли
  • Реализация лямбда-слоя для общих библиотек

В целом, Amplify продолжает впечатлять, упрощая развертывание серверных функций. Я смог развернуть бессерверную функцию, используя Lambda, написанную на том же языке, что и код моего внешнего интерфейса. У меня все еще есть некоторые проблемы с асинхронными функциями, но это больше связано с моей неопытностью в NodeJS/JavaScript.

В следующий раз я напишу о публикации моего нового блестящего веб-сайта. Следите за новостями здесь и в Твиттере (bluevalhalla).

Первоначально опубликовано на https://dev.to 23 августа 2022 г.