Мы используем firebase почти три года, мы начали с малого и постепенно создавали новые системы и переносили программное обеспечение из локальной среды в облако. На момент написания у нас есть 105 облачных функций, запускаемых через https, pubsub, cloud Scheduler и firestore. Результаты блестящие, мы можем делать вещи, о которых не могли и мечтать всего 12 месяцев назад. Но……

Мы начали сталкиваться с проблемами:

  • с развертываниями. Мы используем GitHub в качестве исходного репозитория, связанного со сборкой GCP Cloud. Наш сценарий сборки облака развертывал каждую часть наших облачных функций параллельно. например https, pubsub, firestore, но потерпит неудачу из-за ограничений квоты.
  • с разбивкой развертываний. Зная, какие части наших функций развертывать, было проблематично, мы могли нацеливаться на отдельные функции, например https-user и строить цепочки отдельных функций, но это все очень вручную, не так, как нам нравится работать.
  • со знанием состояния живой среды. Из-за сломанного и ненадежного конвейера развертывания мы потеряли контроль над тем, что было в производстве. Отдельным инженерам приходилось выполнять развертывание вручную со своих рабочих станций.

Так где же это пошло не так?

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

Статья Тарика Хубера действительно помогла сформировать наше мышление — https://codeburst.io/organizing-your-firebase-cloud-functions-67dc17b3b0da

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

Именование функций должно указывать на триггер, а не на то, что они делают.

Но с этим подходом есть проблема. Мы строим монолит с общими зависимостями между различными триггерами, и вскоре стало трудно внести и развернуть небольшое изменение, потому что было невозможно узнать, какие другие функции могли измениться. С помощью Arkit мы смогли визуализировать зависимости нашего кода, посмотрите на этот график зависимостей, он подтверждает то, что мы знали — это отвратительная паутина.

Время переосмыслить.

Ранее мы разработали и развернули систему на основе микросервисов с использованием ранней версии kubernetes, это была тяжелая работа, взаимосвязь и отслеживание были трудными — это было до Istio и экосистемы, которая существует сейчас, а также зрелости kubernetes. Бессерверные функции дали нам возможность сосредоточиться на коде, который делал что-то без лишней инфраструктурной поддержки. Чтобы консолидировать нашу инфраструктуру, мы перенесли приложение микросервисов на функции GCP. Сделав это, мы поняли, что потеряли инкапсуляцию сервисов в микросервисах.

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

Соберите вместе вещи, которые меняются по одним и тем же причинам. Разделяйте те вещи, которые меняются по разным причинам.

В центре внимания оказалась важность принципов SOLID, и мы пересмотрели нашу работу.

Как это выглядит сейчас.

Теперь мы систематизируем по бизнес-функциям, а затем запускаем. Попутно мы также узнали о том, как лучше обрабатывать различные триггеры, например:

  • где ответ не требуется, например, триггер firestore, мы публикуем в теме pub-sub. Это позволяет любой другой функции использовать событие из другой бизнес-функции без прямой привязки на уровне кода.
  • основной код функции содержится в службе, которая предоставляет всю логическую функцию, но не знает и не понимает триггеры. Это также упрощает написание тестов.
  • код триггера очень прост, ранее триггер мог в конечном итоге координировать все виды обновлений, проверок и изменений. Теперь он обращается к службе, в идеале через триггер pubsub.

Наш репозиторий теперь структурирован следующим образом:

functions/
 auth/
  index.ts
 users/
  index.ts
  firestore.ts  
  https.ts
  service.ts
  pubsub.ts
 messaging/
  index.ts
  https.ts
  service.ts
  pubsub.ts
 search/
  index.ts
  https.ts
  service.ts
  pubsub.ts
index.ts

Каждая папка представляет собой «микросервис», не совсем того уровня, который был у нас в kubernetes, но гораздо ближе к принципам SOLID.

Теперь мы можем развернуть отдельные микросервисы.

firebase deploy --only functions:messaging

Предупреждение о вреде курения

Мы все еще работаем над этим, на данный момент это работает очень хорошо для нас, мы только опробовали это на небольшом приложении и еще не доказали это на некоторых из наших более крупных приложений (где мы столкнулись с описанными проблемами) . Как только мы это сделаем, мы обновим статью, в частности, добавив новую визуализацию зависимостей.

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