Создание производственной AWS Lambda с помощью TypeScript

В этой статье я расскажу о шагах и компонентах, необходимых для создания AWS Lambda с помощью TypeScript. Это включает в себя настройку TypeScript, WebPack, Jest для тестирования и упаковки, а также выполнение этой работы с помощью генератора Yeoman aws-lambda-typescript.

TLDR; Эта работа была выпущена как генератор Yeoman, который можно использовать для генерации скелетного кода для TypeScript AWS Lambda:

npm install -g yo
npm i -g generator-aws-lambda-typescript
yo aws-lambda-typescript

AWS Lambda — прекрасное решение для тех, кто хочет поддерживать высокую доступность и в то же время снижать затраты. Это особенно рентабельно для решений с низким трафиком, где обслуживание долго работающих контейнеров или виртуальных машин EC2 может привести к слишком большому финансовому бремени.

Общие проблемы, решаемые в этой статье,

  • размер функции Lambda — NPM и папка node_modules могут быть очень тяжелыми и превышать размеры развертывания. Огромные пакеты затрудняют повторное развертывание кода, его эффективную разработку и тестирование.
  • холодный старт — это время, необходимое для инициализации функции Lambda, прежде чем она сможет обработать запрос. Когда функция не используется в течение нескольких минут или необходимо запустить новый экземпляр для обработки более высокого трафика, инициализация (холодный запуск) может значительно увеличить время отклика, что может привести к ухудшению взаимодействия с конечным пользователем.

Решение проблем с холодным запуском заключается в использовании среды выполнения и языка, которые обеспечивают быстрый и эффективный запуск. Лучшим вариантом является JavaScript, однако из-за отсутствия типизации он может оказаться не лучшим выбором для крупных корпоративных проектов.

Естественной альтернативой является использование TypeScript, но он изначально не поддерживается AWS Lambda, поэтому для него требуется настроить транспирацию-tsc. Простая упаковка node_modules с транспилированным выходным кодом JavaScript может привести к созданию огромных ZIP-файлов, содержащих сотни или более МБ данных, и, возможно, превышение ограничений AWS для пакетов развертывания.

Чтобы решить проблемы с размером, мы можем использовать Webpack, который хорошо известен во фронтенд-разработке и имеет возможности минимизации (встряхивания дерева) и объединения в проекты с одним файлом.

Во-первых, нам нужно установить все необходимые dev-зависимости (TypeScript, linter, тесты, сборка):

npm i --save-dev ts-loader ts-node typescript webpack webpack-cli \
                 prettier jest eslint-plugin-prettie eslint \
                 @typescript-eslint/eslint-plugin @jest/globals \
                 @types/aws-lambda

Скорее всего нам потребуется один из пакетов AWS SDK

npm i @aws-sdk/client-s3

Во-первых, нам нужно настроить tsconfig.json в соответствии с нашими потребностями в языковой поддержке. Для поддержки бэктрейсов мы должны установить sourceMaps: true — иначе Erros в логах будет содержать бессмысленную информацию о местонахождении ошибки (хороший бэктрейс может сэкономить много времени для устранения проблем с продакшеном).

{
    "compilerOptions": {
        "lib": [ "ES2022"],

        "alwaysStrict": true,
        "esModuleInterop": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "strictNullChecks": true,
        "sourceMap": true,
        "strict": true,
    }
}

Далее идет настройка webpack.config.js

const path = require('path');
const { merge } = require('webpack-merge');

/** @type {import('webpack').Configuration} */
const commonConfig = {
  entry: './src/index.ts',
  /** Most recent Node version supported by Lambda when creating this template */
  target: 'node18',
  /** Inline source map always, to get meaningful stack traces */
  devtool: 'inline-source-map',

  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
    library: {
      type: 'commonjs2',
    },
  },

  externals: [
    /** Exclude all `@aws-sdk/*` packages, as those are provided in Lambda Node container. */
    /@aws-sdk\/.*/,
  ],
  module: {
    /** 
     * Configure TypeScript loader, as WebPack natively doesn't support TypeScript
     * ts-loader can be installed with `npm i --save-dev ts-loader`
     */
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: path.resolve(__dirname, 'node_modules'),
      },
    ],
  },

  resolve: {
    extensions: ['.js', '.mjs', '.ts'],
  },
};

/** @type {import('webpack').Configuration} */
const devOptions = {
  mode: 'development',
};

/** @type {import('webpack').Configuration} */
const prodOptions = {
  mode: 'production',
};

module.exports = (_env, argv) => {
  return merge(commonConfig, argv.mode == 'production' ? prodOptions : devOptions);
};

В дополнение к этому необходимо не забыть настроить среду выполнения JavaScript AWS для поддержки исходных карт, это можно сделать, установив переменную среды NODE_MODULES=--enable-source-maps в консоли или сценарии развертывания:

Конечно же, не забываем добавить основной исходный файл src/index.ts

import * as process from 'process';

import { APIGatewayProxyEvent, Context } from 'aws-lambda';

/*
 * Insert rest of function static initialization here, things like
 *  - loading KMS keys
 *  - setting up clients
 * will be shared among executions of handler.
 */

export const handler = async (event: APIGatewayProxyEvent, _ctx: Context) => {
  console.log('Received Event', event);
};

Мы можем построить весь проект с

./node_modules/.bin/webpack build --mode production

Или запустить в режиме разработки/просмотра с

webpack --watch

Этот краткий пошаговый обзор должен дать более глубокое понимание того, как AWS Lambdas можно создавать с помощью TypeScript и Webpack. Для готового пакета предлагаю использовать генератор Yeoman, упомянутый в начале статьи.

В следующих статьях я опишу другие облачные подходы. Следите за обновлениями. Хорошего дня!