Serverless в первый раз

Я давно приглядывался к Serverless-технологиям, но все не доходили руки. В М2, как и во многих компаниях, есть строгое разделение на бэкендеров и фронтендеров. Проблемы у этого известные, но самая неприятная — надо договариваться, а разработчики далеко не всегда самые общительные люди.

Ну сами знаете, бекендеры с марса, фронтендеры с твикса.

Ну сами знаете, бекендеры с марса, фронтендеры с твикса.

Вместо предисловия

Моя профессия — фронтендер. Не я это придумал и не я сформулировал, в нашей компании есть фраза: «Любой фронтендер немножечко фулстек». И кроется под этим, конечно же nodejs. Мы решили, что некоторые сервисы можем писать без помощи бекенд-разработчиков и начали…

Скоро сказка сказывается, да не скоро дело делается, часть фронтенд-тусовки перешло на темную сторону и, вооружившись nestjs и mongodb, мы сделали несколько очень важных сервисов. Я тоже оказался по ту сторону сумрака. Но другая часть (она оказалась много больше чем первая) сердцем была с нами, но делами…

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

В общем, чтобы ни делать, лишь бы не бэкенд.

Революционно настроенный, я не смирился в ссылке и решил во что бы то ни стало повысить производительность команд, сделав их участников более самодостаточными чем вчера. И на помощь мне пришли Cloud Function от Yandex, так как мы используем Yandex Cloud, но то же самое можно сделать и с Firebase и c другими современными клаудами.

Моя первая функция

О том, как сделать маленькую функцию написано уже множество инструкций и статей — не буду повторяться. Мне же хотелось сделать репозиторий, куда любой разработчик М2 сможет добавить функцию и она, передвигаясь по пайплайну ci, появлялась бы на проде. Моя функция должна была стать примером для подражания, и я не придумал ничего нового и назвал ее template.

Каждая функция представляет собой одноименную папку со следующим наполнением:

/template
    jest.config.js
    package.json
    tsconfig.json
    /src
        index.ts
        index.test.ts
        /data
            data.json

index.ts

import { YC } from '../../yc';

import json from './data/data.json';

export type Payload = {
  data: any;
}

export const handler: YC.Handler<"POST", any, Payload> = async (event, context) => {

  const data = context.getPayload().data;

    return {
        statusCode: 200,
      headers: {
          'Content-Type': 'application/json; charset=utf-8',
        },
      body: { event, millis: context.getRemainingTimeInMillis(), data, json},
      isBase64Encoded: false,
      };
}

Функция в практическом смысле бесполезная и самое интересное в ней это import json (о нем напишу ниже). Сама же функция — это шаблон для таких же функций. То есть, при вызове cli create создается новая функция по образу и подобию текущей, которую дальше следует модифицировать, то есть наделить бизнес логикой.

CLI на страже галактики

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

Бок обок с CI, идет CLI: иногда надо переложить, переименовать файлы, а иногда и преобразовать и даже обогатить. Тут действует принцип «кто во что горазд», кто-то использует bash, а я и моя команда используем clipanion. На хабре есть статьи на эту тему, поэтому не буду останавливаться на библиотеке подробно, но штука просто бомба, и мы ее используем в каждом проекте.

В нашем CI есть этап обогащения или enrich. Фокус состоит в том, чтобы на этапе перед сборкой инкапсулировать все данные, необходимые для выполнения функции в нее. Для каждой функции есть соответствующая команда enrich-template, которая умеет ходить в базу, умеет скачивать что-то из интернета, затем делает какие-то преобразования и складывает все в data.json.

Изоляция во благо

Функция абсолютно изолирована, никуда не ходит, ни к чему не обращается Соответственно, не может иметь дыр в безопасности, ее не надо согласовывать с «первым» отделом и не надо подвергать дополнительным проверкам. А главное мы экономим на сетевых соединениях и прочих ресурсах.

Но на мое место придет другой

Но на мое место придет другой

Еще не нужно думать про идемпотентность и другие бекендерские шалости: функция ничего не меняет, а только что-то возвращает. Она никогда не остановит общий процесс, так как при зависании функции на ее место тут же придет другая и заменит ее.

Тестирование

Изолированные функции легко поддаются тестированию юнит-тестами. Для этого мы написали простую функцию-обертку:

export function makeHandlerParams(method: YC.HttpMethod, params: Record): [event: YC.CloudFunctionsHttpEvent, context: YC.CloudFunctionsHttpContext] {

    const event: YC.CloudFunctionsHttpEvent = {
        httpMethod: method
    } as YC.CloudFunctionsHttpEvent;
    const context: YC.CloudFunctionsHttpContext = {
        _data: event,
        requestId: 'requestId',
        awsRequestId: 'awsRequestId',
        uberTraceId: 'uberTraceId',
        deadlineMs: 3000,

        functionFolderId: 'functionFolderId',
        functionName: 'functionName', // "<идентификатор функции>",
        functionVersion: 'functionVersion', // "<идентификатор версии функции>",
        invokedFunctionArn: 'invokedFunctionArn',
        logGroupName: 'logGroupName',

        memoryLimitInMB: '120', // "<объем памяти версии функции, МБ>",
        getRemainingTimeInMillis: () => 3000, // возвращает время, оставшееся на выполнение текущего запроса в миллисекундах;
        getPayload: () => params as TIN
    }
    return [event, context];
}

И обычный тест выглядит следующим образом:

test('template', async () => {
    const data = await handler(...makeHandlerParams<'POST', Payload>('POST',
         {
        data: 99
    }));
    expect(99).toBe(data.body.data);
});

Вместо эпитафии

Нашел ли я серебряную пулю? Сможем ли мы теперь делать любые задачи без привлечения бекенда? Конечно, нет, честно говоря, я и не стремился к этому.

27594b998139a9609992905f47d9c23d.png

У этого решения очень узкий спектр задач, но эксперимент позволяет фронтендерам отвлечься от формашлепства, попробовать что-то новое, а главное закрыть часть задач бизнеса, которые ранее были вроде бы не в нашей компетенции.

На основе этого решения выше наша команда сделала функции для ротации баннеров и для поиска по документации.

В новом году хочу прикрутить валидацию пейлоада к функциям (благо typescript и проверка по схеме изобретены до меня) и какой-нибудь интересный cli шаблонизатор.

А вы пользуетесь клауд функциями в таком контексте? Если да, поделитесь в комментариях для каких задач.

© Habrahabr.ru