Firebase, Supabase и BaaS: как мы к такому пришли и что там внутри

от автора

Всем привет!

Ранее мы разбирались с одним конкретным примером — Supabase: как его поставить, зачем он нужен, какие есть аналоги и почему вокруг него в последнее время так много шума.

Но, мне кажется, что сейчас будет правильно сделать шаг назад и поговорить не про конкретный сервис, а про весь BaaS (Backend-as-a-Service). Как мы уже узнали из прошлой статьи, Supabase не возник сам по себе, до него был Firebase, а до Firebase были обычные самописные API, куча настроек авторизации, хранения файлов, нотификаций с вебсокетами и остального.

В этой статье мы разберем, что такое BaaS, почему он вообще понадобился, чем Firebase отличается от Supabase, для каких приложений такой подход подходит, а где уже нужен собственный backend.

Как мы вообще пришли к BaaS

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

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

То есть:

  • Нужно зарегистрировать пользователя,

  • Нужно сделать вход по почте и паролю,

  • Нужно хранить сессии и токены,

  • Нужно дать пользователю возможность загрузить аватарку,

  • Нужно сделать CRUD (Create, Read, Update, Delete) для каких-то данных,

  • Нужно настроить роли доступа,

  • и т.д., это можно продолжать бесконечно.

И тут у одних умных людей возникла мысль: а что если типовой backend не писать каждый раз заново? И мысль действительно умная, ведь уже очень давно существует один из главных принципов программирования — DRY — Don’t Repeat Yourself! Зачем повторять в каждом проекте одно и то же, если можно создать набор готовых backend-сервисов, к которым можно подключать frontend.

Так и появился подход Backend-as-a-Service, или BaaS.

Если совсем коротко, то BaaS — это набор готовых backend-сервисов, к которым frontend или мобильное приложение подключаются через SDK или API.

А что с безопасностью?

Я думаю у многих читателей, кто только-только узнает о BaaS возник очень даже логичный вопрос: а как это? Получается, что прям в коде веб-приложения, который доступен абсолютно каждому пользователю, что открыл сайт, есть код подключения BaaS со всеми секретными токенами и бизнес-логикой?

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

Но, думаю было бы очень глупо, если бы этот токен был секретом. На самом деле это просто публичный идентификатор проекта в самом Firebase. Он нужен лишь для того, чтобы API Firebase знал, к какому проекту относить запросы с этого фронтенда.

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

В общем, с безопасностью все окей, никакие секретные токены в коде frontend’а не хранятся.

В чем разница между Firebase и Supabase?

Тут максимально кратко: Firebase — это сервис от Google, у которого каждый сервис — это что-то свое, но который не раскрывает свои исходники, а Supabase — это аналог, который собран на OpenSource компонентах, исходный код которого открыт для каждого пользователя. Естественно, на этом все отличия не заканчиваются, некоторые из них я опишу ниже.

А что именно входит в состав готовых backend-сервисов?

В основном, в состав готовых backend-сервисов входит то, что повторяется в проектах чаще всего:

  • Авторизация;

  • база данных;

  • автоматический API;

  • хранение файлов;

  • получение обновлений в realtime (об этом чуть ниже подробнее будет);

  • serverless-функции;

  • нотификации;

  • аналитика;

  • и разные доп. инструменты для логов, мониторинга и прочего.

То есть BaaS пытается закрыть не какую-то одну проблему, а целый слой backend-сервисов.

Давайте чуть подробнее для некоторых сервисов

Авторизация

Авторизация — это, наверное, второе, после базы данных, с чем сталкивается любое приложение, которое сложнее лендинга.

Пользователь должен зарегистрироваться, войти, восстановить пароль, подтвердить почту, может войти через Google/Github/Yandex/Apple. Все это довольно сложно, если писать самому.

И сложно не потому что работать долго, а потому что авторизация обязательно должна быть достаточно безопасной. Пароли обязательно должны хешироваться, должны возвращаться JWT или сессии, нужно защититься от перебора паролей, придумать и настроить роли.

Ну и конечно же это есть практически в любом BaaS. В Firebase для этого есть Firebase Authentication. В Supabase — Supabase Auth. В обоих случаях можно довольно быстро получить регистрацию, логин, OAuth и текущего пользователя.

То есть вместо того, чтобы писать отдельный сервис пользователей, вы делаете что-то типо:

const { data, error } = await supabase.auth.signInWithPassword({  email: 'почта пользователя',  password: 'пароль пользователя',})

или

import { getAuth, signInWithEmailAndPassword } from 'firebase/auth'const auth = getAuth()const userCredential = await signInWithEmailAndPassword(  auth,  email,  password)const user = userCredential.user

База данных

В BaaS все довольно просто: база данных уже есть, SDK обычно есть, API тоже.

Но здесь, кстати, есть важное отличие между Firebase и Supabase.

Firebase уже давно живет в мире NoSQL. У него есть своя Realtime Database и Cloud Firestore. Это не обычная реляционная база данных с таблицами, связями и JOIN’ами, а документная модель данных.

Realtime Database хранит данные как одно большое JSON-дерево:

{  "users": {    "user_1": {      "name": "user1",      "email": "user1@users.com"    },    "user_2": {      "name": "user2",      "email": "user2@users.com"    }  },  "tasks": {    "task_1": {      "title": "Read article",      "userId": "user_1",      "isDone": false    },    "task_2": {      "title": "Write article",      "userId": "user_2",      "isDone": true    }  }}

То есть данные фактически лежат по путям. Например, можно обратиться к /users/user_1 и получить данные конкретного пользователя. Или к /tasks/task_1 и получить конкретную задачу.

А в Supabase все проще: там используется PostgreSQL и данные хранятся в привычном всем нам виде.

Realtime

Realtime — это очень важная часть BaaS.

Раньше, если нужно было обновлять данные без перезагрузки страницы, приходилось выбирать между polling, SSE, WebSocket, отдельным message брокером или realtime-сервисом.

Firebase изначально очень сильно выстрелил именно за счет realtime-подхода. Приложение “подписывается” на данные, и когда они меняются, клиенты получают обновления.

Это очень удобно для:

  • чатов;

  • онлайн-досок;

  • статусов заказа;

  • уведомлений внутри приложения;

  • возможно даже совместного редактирования.

Supabase, конечно, тоже умеет в realtime, но там это работает иначе. Так как в центре вообще всего находится PostgreSQL, realtime подписывается на изменения в базе и рассылает их клиентам.

Serverless-функции

Не всегда базовых вещей типа авторизации и базы данных достаточно, почти в каждом проекте есть своя бизнес-логика, которую ни в коем случае нельзя реализовывать в коде frontend’а.

Поэтому обычно в BaaS даютserver-less функции.

В Firebase это Cloud Functions, в Supabase — Edge Functions.

Выглядит это примерно так:

await fetch('/create-payment', { // очень частый сценарий   method: 'POST',  body: JSON.stringify({ plan: 'pro' })})

А эта функция на серверной стороне обращается к платежке, проверяет пользователя, создает платеж и возвращает результат.

А можно ли собрать свой BaaS стек?

После Firebase и Supabase возникает вопрос: а можно ли собрать подобное самому?

Не обязательно прям “свой Supabase”, так как Supabase — это не один сервис, а довольно огромная платформа. Там есть PostgreSQL, Auth, PostgREST, Realtime, Storage, Edge Functions, Studio, API Gateway и еще куча связующего кода.

Но если говорить не про полную копию, а что-то идейно похожее, то да.

Например, можно взять:

  • PostgreSQL как основную базу данных

  • RLS (Row Level Security) в PostgreSQL для ограничения доступа к строкам,

  • PostgREST для автоматического RestAPI поверх PostgreSQL,

  • Keycloak для авторизации и выдачи JWT,

  • Отдельный backend для какой-то приватной бизнес-логики.

Развернуть это все можно и самостоятельно, но мы воспользуемся готовым решением, где все можно сделать «одной кнопкой» . И заодно немного прорекламируем себя, надеюсь на ваше понимание.

В простом запуске нам поможет сервис Amvera. Это сервис для быстрого развертывания IT-приложений, особенность которого заключается в отсутствии необходимости самостоятельной настройки окружения. Здесь вы буквально за 10-15 минут сможете развернуть весь вышеописанный стек, лишь заполнив некоторые параметры компонентов.

Самое важное: для первого запуска вы можете использовать бонусный баланс в размере 111 рублей, доступный сразу после регистрации.

Как это может работать

Фактически схема будет такая:

На фронтенде пользователь логинится через Keycloak. После успешного входа frontend получает JWT токен. Потом frontend отправляет запрос PostgREST и передает этот JWT токен в заголовке Authorization.

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

Например, в PostgreSQL можно включить RLS для таблицы: alter table <название-таблицы> enable row level security;

А потом создать Policy, которая разрешит пользователю видеть только свои данные:

create policy "Users can read only own data" on <название-таблицы> for select using (  user_id = (    current_setting('request.jwt.claims', true)::jsonb ->> 'sub'  )::uuid);

Если дословно:

Создать политику с названием "Users can read only own data" для таблицы <название-таблицы> для SELECT, при этом user_id (название ячейки в таблице, значение - обязательно uuid для данной политики) должно быть равно UUID, полученному из jwt во входящем запросе

И если все настроить корректно, PostgREST вернет только те строки, которые принадлежат конкретному юзеру. Ровно то же самое можно сделать с DELETE, INSERT, UPDATE и реализовать базовый CRUD.

Развертывание сервисов

Как я говорил выше, развернуть каждый из сервисов можно в Amvera. Это займет не более 20-и минут.

PostgreSQL

PostgreSQL предоставляется как managed-сервис с бэкапами. После регистрации его можно развернуть с помощью кнопки “Создать базу данных” в разделе PostgreSQL.

Запускаем PostgreSQL

Запускаем PostgreSQL

При создании вам понадобится лишь выбрать базовые настройки: название базы данных, юзера, пароли. Связать PostgreSQL с остальными проектами можно как по внутреннему доменному имени, так и по бесплатному внешнему.

PostgREST

Этот сервис предоставляется как преднастроенный. Для его создания выберите раздел “Преднастроенные сервисы”. При создании понадобится выбрать данные для подключения к PostgreSQL и название анонимной роли.

Выбираем преднастроенный сервис

Выбираем преднастроенный сервис
Выбираем PostgREST

Выбираем PostgREST

Keycloak

Как и PostgREST, Keycloak можно развернуть в разделе “Преднастроенные сервисы”. Понадобится выбрать данные временного администратора Keycloak и данные для подключения PostgreSQL.

Выбираем Keycloack

Выбираем Keycloack
Задаем необходимые переменные

Задаем необходимые переменные

Итог

BaaS появился не потому, что backend стал не нужен, он появился потому что в огромном количестве проектов повторяются одни и те же сценарии, которые можно не писать каждый раз заново.

В данной статье я рассказал об устройстве BaaS и о том, как можно создать свой BaaS достаточно поверхностно, но уже в следующей статье я разберу тему максимально подробно, показав на примере, как из open source компонентов собрать свое BaaS-решение.

ссылка на оригинал статьи https://habr.com/ru/articles/1047242/