Як та з чим приготувати Firebase Functions
Вітаю, спільното DOU! Я Анатолій, Android Tech Lead у компанії N-iX та Tech Lead на волонтерському проєкті Donatta. Хочу поділитися досвідом нашої команди у використанні Firebase Functions.
Для контексту зазначу, що це мобільний застосунок на Android та iOS, розроблений на Flutter, а весь бекенд — на Firebase Functions із використанням Node.
У статті розглянемо декілька рецептів приготування Firebase Functions (далі function або функція), їхні переваги та недоліки на реальних прикладах, з якими ми мали справу у проєкті.
Стаття орієнтована переважно на:
- client side-розробників, які хочуть зазирнути за рамки фронтенду;
- будь-кого, кому хочеться розібратись із Firebase Functions.
Рецепт № 1. Firestore + Functions
Це підхід, коли мобільний застосунок зчитує та записує дані напряму у базу Firestore, однак частина логіки виконується на функції. Функція може викликатись повторювано через зазначені періоди часу, або як реакція на зазначені події. Мобільний застосунок у цьому випадку нічого не знає про Firebase Function.
Завдяки цьому підходу можна зручно налаштувати безпечний доступ до даних використовуючи Firestore Rules. Ця конфігурація дозволяє легко обмежити зону доступу до даних або забезпечити доступ лише за умови авторизації.
Ще одна перевага використання Firestore у клієнтському застосунку — можливість зробити підписку на оновлення даних, на кшталт сокета.
Отримання даних із Firestore працює досить швидко — швидше, ніж отримання даних із функцій. Крім цього, Firebase робить під капотом необхідне кешування, тож легко зробити офлайн-доступ до останньої версії даних.
Недоліки:
- Vendor lock. Дуже багато логіки ґрунтується на SDK та особливостях Firebase. Якщо ви не впевнені у використанні Firebase або є плани мігрувати на щось інше у майбутньому, то цей рецепт може бути вам не до смаку.
- Логіка на клієнті. Так чи інакше, дані обростають логікою. І в цьому рецепті значна частина логіки буде потрапляти на клієнтську сторону (валідації, мерджинг даних із різних джерел). Це серйозний архітектурний ризик на який потрібно йти свідомо. Відповідні наслідки: довше деліверити зміни через необхідність робити релізи, складність підтримки зворотної сумісності, security-ризики тощо.
Схематичний приклад такої архітектури можна побачити на зображенні нижче. Triggered Firebase Function
— це функція яка може бути викликана на модифікації даних бази, або додаткові інші додаткові івенти. У нашому проєкті така функція викликається на створення нового користувача та робить prepopulate даних. Scheduled Firebase Function
— повторювана крон-функція яка може оновлювати дані. Як приклад — оновлення даних про збори у Donatta.
Рецепт № 2. HTTPS Functions
У цьому рецепті з’являється усім відомий протокол HTTPS.
Якщо ви оголосите Firebase-функцію, використовуючи метод onRequest
, то її можна буде викликати через HTTPS.
Наприклад, такий код на TypeScript:
export const justFunction = functions .region(process.env.DEFAULT_REGION || "") // your config .https.onRequest(async (request, resp) => { // Just do it resp.status(200).end(); });
Дозволить викликати функцію justFunction
через такий url: https://<LOCATION>-<PROJECT_NAME>.cloudfunctions.net/justFunction
. Де <LOCATION> — це локація сервера, яку ви обрали у налаштуваннях, а <PROJECT_NAME> — назва вашого Firebase-проєкту.
Отже, завдяки HTTPS-функціям можна будувати усіма улюблений REST API. Крім цього, завдяки тому, що HTTPS-функція доступна публічно — її може використовувати не тільки ваш застосунок, а і сторонні сервіси. Тобто ви можете зареєструвати функцію як вебхук. Завдяки цьому ваш застосунок зможе реагувати на події сторонніх сервісів.
У Donatta така можливість використовується для отримання інформації про донати користувачів. Банківський API викликає зареєстровану URL-адресу Firebase-функції й функція виконує усі необхідні перевірки та записує інформацію у базу даних.
Завдяки такому рецепту зникає vendor lock на стороні клієнтського застосунку, адже використовуються звичайні HTTPS-запити. Однак разом із цим зникає і можливість потокового отримання даних. Збільшується важливість відповідально реалізовувати логіку функцій для швидкої роботи. І для цього доводиться шукати шляхи оптимізації логіки.
Тут варто згадати, що Firebase Functions із погляду інфраструктури — серверлес.
Тобто кожна функція виконується на окремому інстансі, і якщо максимальна кількість інстансів обмежена, то події на обробку стоять у черзі на опрацювання. Вже піднятий інстанс їх всі опрацьовує, і потім тухне.
Завдяки цьому можна робити in memory-кеші, за для зменшення кількості походів у базу даних. І немає потреби реалізувати актуалізацію кешів, через те, що рано чи пізно функція потухне. А коли підніметься, кеш буде сформовано заново.
Через те що HTTPS-функції доступні публічно, з’являються ризики кібератак. Нападник може підсунути будь-які дані у запит, із метою несанкціонованого доступу до даних. Або провести звичайну DDoS-атаку і накрутити немалий чек за послуги Google. Для захисту від цього, звичайно ж, можна робити свої велосипеди, механізми автентифікації тощо. А можна використати готове рішення — callable firebase functions.
Рецепт № 3. Callable Functions
Це функції які мусять викликатись через firebase sdk на клієнтській стороні. Тут одразу отримуємо певний захист від DDoS. Також Firebase SDK під капотом передає у callable-функції дані про користувача, якщо він автентифікований. Отже, з’являється готовий механізм для захисту даних.
Однак з’являється обмеження у тестуванні. Викликати функцію через curl
або postman
вже не вийде.
Приклад реалізації callable-функції:
export const justFunction = functions .region(process.env.DEFAULT_REGION || "") // your config .https.onCall(async (data, context) => { // Just do it return { code: 200, }; });
Разом із головною перевагою callable function, захищеністю, приходить і недолік — vendor lock на стороні клієнту.
Однак попри необхідність викликати callable firebase functions через SDK, все одно є ризик що зловмисники можуть дуже захотіти та зареверсити SDK і зробити собі середовище, де вони почнуть викликати ваші чудові функції.
Для захисту від цього callable-функції дозволяють додати перевірку із використанням сервісу App Integrity. Завдяки цьому функції можна буде викликати лише на верифікованих пристроях.
Архітектура
Виходячи із перелічених рецептів, можна зрозуміти що приготувати Firebase Functions у вашому проєкті можна дуже по-різному. До прикладу, у Donatta нам довелося випробувати різні підходи. Зрештою, на певний момент наша діаграма deployment view виглядала наступним чином:
Завдяки тому, що Firebase Functions насправді є Cloud Functions із набору сервісів Google Cloud Platform, то разом із функціями можна використовувати весь арсенал GCP. До прикладу на нашому проєкті використовуються такі сервіси:
- SecretManager — для безпечної взаємодії із захищеними змінними оточення;
- PubSub — для побудови черг подій і їх обробки;
- Alerting — для сповіщення про інциденти.
Підсумок
Firebase Functions — потужний сервіс для швидкої розробки бекенд-частини. Завдяки різноманітним способам використання, можна обрати саме той підхід, який підійде для проєкту як найкраще, враховуючи вимоги із гнучкості та безпеки.
Крім цього, можливість інтеграції із GCP дозволяє розширити можливості, знаходячись в одній екосистемі. Однак варто остерігатись vendor lock, адже для Google вигідно, щоб ви лишились із ним надовго.
А який ваш досвід використання Firebase Functions? Чи можете ви порадити достойні альтернативи? Розкажіть у коментарях.
16 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів