Приручи бессерверность: 7 шагов создания бессерверного API

Пугает масштабирование? Выход есть: используй бессерверность. Как создавать бессерверные API? Эта статья – ответ на вопрос.

Секунду подумай о Твиттере, и подумай об этой сети с точки зрения масштаба. У Twitter 326 миллионов пользователей. Совместно создаётся ~ 6000 твитов каждую секунду. Каждую минуту создаются 360 000 твитов. Это составляет 200 миллиардов твитов в год. Что, если создатели Твиттера испугались бы вопроса масштабирования до такой степени, что даже не начали?

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

Метрики в Application Insights

Как видно из приведенного выше, масштабирование от одного до семи серверов занимает считанные секунды, когда поступает больше пользовательских запросов. Ты сможешь так же легко масштабировать.

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

Как начать использовать бессерверность?

Для каждой новой технологии сначала выясняем, какие инструменты доступны, и как интегрировать эти средства в текущий набор инструментов. Когда осваиваем бессерверность, сталкиваемся с несколькими вариантами.

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

Бессерверность в браузере

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

  • Написание кода в предпочтительном редакторе.
  • Инструменты для выполнения тяжёлой работы и генерирования шаблонного кода.
  • Запуск и локальная отладка кода.
  • Поддержка быстрого развёртывания кода.

В Microsoft программисты создают серверные приложения по большей части с использованием службы «Функции Azure», которая будет использоваться далее в этой статье как пример. Благодаря Функциям Azure получаем поддержку упомянутых фич. Основные инструменты службы устанавливаются из пакета npm:

npm install -g azure-functions-core-tools

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

Если предпочитаешь редактор VS Code, используй его и для написания бессерверного кода. На самом деле для него есть отличное расширение.

После установки на левую боковую панель добавляется новый значок: отсюда получаем доступ к нашим Azure-расширениям. Связанные функции группируются в одном проекте, который также называется приложением-функцией. Это как папка для группировки функций, которые масштабируются совместно, и которые разработчик контролирует и мониторит одновременно. Чтобы инициализировать новый проект с использованием VS Code, щёлкни значок Azure, а затем значок папки.

Создание нового проекта в Функциях Azure

После создания генерируются файлы, которые помогут с глобальными настройками. Пройдёмся по этим файлам.

host.json

Глобальные параметры для всех функций проекта указываются непосредственно в файле host.json.

В этом файле настраиваем приложение-функцию на использование последней версии бессерверной среды выполнения – весной 2019 года это 2.0. Также зададим время ожидания функции десять минут: установим свойству functionTimeout значение 00:10:00. По умолчанию это значение составляет пять минут – 00:05:00.

В ряде случаев разработчик управляет префиксом маршрута для URL-адресов или даже подкручивает параметры наподобие количества одновременных запросов. Функции Azure позволяют настраивать и другие свойства, такие как ведение журнала, healthMonitor и всевозможные типы расширений.

Вот пример файла с конфигурацией:

// host.json
{
  "version": "2.0",
  "functionTimeout": "00:10:00",
  "extensions": {
  "http": {
    "routePrefix": "tacos",
    "maxOutstandingRequests": 200,
    "maxConcurrentRequests": 100,
    "dynamicThrottlesEnabled": true
  }
}
}

Настройки приложения

Параметры приложения относятся к глобальным параметрам для управления временем выполнения, языком и версией, строками подключения, доступом на чтение-запись, развёртыванием ZIP-файла и другими. Некоторые из этих настроек требуются платформой, например, FUNCTIONS_WORKER_RUNTIME. Здесь также определяются пользовательские настройки, которые будем использовать в коде нашего приложения. Такие как DB_CONN для подключения к экземпляру базы данных.

При локальной разработке определяем эти параметры в файле с именем local.settings.json и получаем к ним доступ, как и к другим переменным среды.

Вот снова пример фрагмента, который объединяет оба вида параметров:

// local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "ваш_ключ_здесь",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "WEBSITE_NODE_DEFAULT_VERSION": "8.11.1",
    "FUNCTIONS_EXTENSION_VERSION": "~2",
    "APPINSIGHTS_INSTRUMENTATIONKEY": "ваш_ключ_здесь",
    "DB_CONN": "ваш_ключ_здесь",
  }
}

Прокси-серверы Функций Azure

Прокси-серверы Функций Azure реализованы в файле proxies.json и позволяют разбивать код на несколько приложений-функций с единым API, а также изменять запросы и ответы. В коде ниже публикуем две конечные точки под одним и тем же URL:

// proxies.json
{
  "$schema": "http://json.schemastore.org/proxies",
  "proxies": {
    "read-recipes": {  
      "matchCondition": {
        "methods": ["POST"],
        "route": "/api/recipes"
      },
      "backendUri": "https://tacofancy.azurewebsites.net/api/recipes"
    },
    "subscribe": {
      "matchCondition": {
        "methods": ["POST"],
        "route": "/api/subscribe"
      },
      "backendUri": "https://tacofancy-users.azurewebsites.net/api/subscriptions"
    }
  }
}

Щёлкни на значок молнии в расширении, чтобы создать новую функцию.

Создание новой функции в Функциях Azure

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

Используем function.json, чтобы настроить тип событий, которые слушает наша функция, и по желанию связать с конкретными источниками данных. Код запускается в ответ на конкретные триггеры типа HTTP, если реагируем на запросы HTTP: когда запускаем код в ответ на файл, который загружается в учётную запись хранилища. Ещё часто используются триггеры типа очередь для обработки сообщения, которое поставили в очередь. Или временные триггеры для запуска кода через заданные интервалы времени. Привязки используются для чтения и записи в источники данных или сервисы, такие как базы данных или служба для отправки электронных писем.

Здесь видим, что наша функция слушает HTTP-запросы. Получаем доступ к фактическому запросу через объект с именем req:

// function.json
{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"],
      "route": "recipes"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

index.js – место, где пишем код для нашей функции. Для связи с бессерверной средой выполнения используем доступный объект контекста. Также логируем информацию, устанавливаем ответ для нашей функции, считываем и записываем данные из объекта bindings. Иногда приложение-функция будет состоять из набора функций, которые зависят от соединения с базой данных. Лучше поместить этот код в отдельный файл, чтобы уменьшить дублирование кода.

//Index.js
module.exports = async function (context, req) {
  context.log('JavaScript HTTP триггерная функция обработала запрос.');

  if (req.query.name || (req.body && req.body.name)) {
      context.res = {
          // status: 200, /* По умолчанию 200 */
          body: "Здравствуйте " + (req.query.name || req.body.name)
      };
  }
  else {
      context.res = {
          status: 400,
          body: "Пожалуйста, передайте имя в строке запроса или в теле запроса"
      };
  }
};

Кому уже не терпится запустить это? ;)

Как локально запускать и выполнять отладку бессерверных функций?

При использовании VS Code и расширения Функций Azure получаем массу настроек, которые необходимы для локального запуска и отладки функций, что реализуют бессерверность. При создании нового проекта с использованием этой среды автоматически генерируется папка .vscode, где содержится конфигурация отладки. Чтобы выполнить отладку новой функции, используем палитру команд Ctrl + Shift + P и выбираем «Отладка: Начать отладку», или печатаем отладка.

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

Причина, почему это возможно, в том, что среда выполнения Функций Azure с открытым исходным кодом и устанавливается локально на машине при установке пакета azure-core-tools.

Как установить зависимости?

Возможно, ты уже знаешь ответ на этот вопрос, если работал с Node.js. Как и в любом другом проекте Node.js, сначала создаём файл package.json в корневой папке проекта. Делаем это с помощью команды npm init -y. -y инициализирует файл с конфигурацией по умолчанию.

Затем устанавливаем зависимости с использованием npm, как и для любого другого проекта. Для этого проекта установим пакет MongoDB из npm, выполнив:

npm i mongodb

Пакет теперь будет доступен для импорта во всех функциях в приложении-функции.

Как подключиться к сторонним сервисам?

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

Как же подключаться и читать информацию из базы данных? Используем клиент MongoDB для чтения данных из экземпляра Azure Cosmos DB, который создали в Azure. Этот способ подойдёт и для других баз данных MongoDB.

/Index.js
const MongoClient = require('mongodb').MongoClient;

// Инициализация деталей аутентификации, необходимых для подключения к базе данных
const auth = {
  user: process.env.user,
  password: process.env.password
};

// Инициализируем глобальную переменную для хранения соединения с базой данных
//  для повторного использования в будущих вызовах
let db = null;
const loadDB = async () => {
  // Если клиент базы данных существует, используйте его повторно 
  if (db) {
    return db;
  }
  // В противном случае создаём новое соединение  
  const client = await MongoClient.connect(
    process.env.url,
    {
      auth: auth
    }
  );
  // Выбор базы данных tacos
  db = client.db('tacos');
  return db;
};

module.exports = async function(context, req) {
  try {
    // Получить соединение с базой данных
    const database = await loadDB();
    // Получить все элементы в коллекции рецептов 
    let recipes = await database
      .collection('Recipes')
      .find()
      .toArray();
    // Возвращаем объект JSON с массивом рецептов 
    context.res = {
      body: { items: recipes }
    };
  } catch (error) {
    context.log(`Error code: ${error.code} message: ${error.message}`);
    // Возвращаем сообщение об ошибке и код состояния внутренней ошибки сервера
    context.res = {
      status: 500,
      body: { message: 'Произошла ошибка, повторите попытку позже.' }
    };
  }
};

Здесь отметим, что в коде используем повторное соединение с базой данных, а не создаём новое для каждого вызова нашей функции. Это сбрасывает ~300 мс с каждого последующего вызова функции. Так выглядит победа!

Где хранить строки подключения?

При локальной разработке храним переменные окружения, строки подключения и другие секретные вещи в файле local.settings.json. А затем обращаемся к ним привычным способом с использованием process.env.yourVariableName.

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "user": "ваш-пользователь-базы",
    "password": "ваш_пароль_базы",
    "url": "mongodb://your-db-user.documents.azure.com:10255/?ssl=true"
  }
}

В промышленной эксплуатации настраиваем параметры приложения на странице функции на портале Azure.

Второй изящный способ это сделать – через расширение VS Code. Не придётся покидать IDE, чтобы добавлять новые настройки, удалять существующие или загружать их в облако.

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

Как настроить URL-путь?

При использовании REST API найдётся пара рекомендаций по формату самого URL. Для нашего API остановимся на следующем:

  • GET /recipes: получает список рецептов
  • GET /recipes/1: получение конкретного рецепта
  • POST /recipes: создаёт новый рецепт
  • PUT /recipes/1: обновляет рецепт с идентификатором 1
  • DELETE /recipes/1: удаляет рецепт с идентификатором 1

URL-адрес, доступный по умолчанию при создании новой функции, выглядит как http://host:port/api/function-name. Чтобы изменить URL-путь и метод, который слушаем, настроим конфигурацию в файле function.json:

// function.json
{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"],
      "route": "recipes"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

Кроме того, чтобы добавить параметры в путь нашей функции, используем фигурные скобки: route: recipes/{id}. Теперь параметр ID доступен в нашем коде из объекта req:

const recipeId = req.params.id;

Как развернуть это в облаке?

Поздравляю, ты выполнил работу до последнего шага! ? Настало время отправить это добро в облако. Снова поможет расширение VS Code. Требуется только один щелчок правой кнопкой мыши, и мы близки к завершению!

Развёртывание с использованием VS Code

Расширение поместит код и модули Node в ZIP-файл и отправит архив в облако.

Хотя эта опция подходит для тестирования нашего собственного кода или для работы над небольшим проектом, легко случайно перезаписать чужие изменения или – что ещё хуже – ваши.

Не позволяйте друзьям щёлкать правой кнопкой мыши!

– каждый инженер DevOps

Гораздо экологичнее настроить развёртывание GitHub, которое выполняется в пару шагов на портале Azure через вкладку Центр развёртывания.

Github развёртывание

Готов ли ты создавать бессерверные API?

Это было основательное введение в мир бессерверных API. Тем не менее, масштаб этой сферы не охватить в одной статье. Используя бессерверность, разработчики решают проблемы творчески и минимизируют затраты по сравнению с использованием традиционных платформ.

Оригинал статьи.

Больше интересных статей:

А как ты используешь бессерверность?

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

eFusion
01 марта 2020

ТОП-15 книг по JavaScript: от новичка до профессионала

В этом посте мы собрали переведённые на русский язык книги по JavaScript – ...
admin
10 июня 2018

Лайфхак: в какой последовательности изучать JavaScript

Огромный инструментарий JS и тонны материалов по нему. С чего начать? Расск...