Всем привет!
Ранее мы разбирались с одним конкретным примером — 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 с остальными проектами можно как по внутреннему доменному имени, так и по бесплатному внешнему.
PostgREST
Этот сервис предоставляется как преднастроенный. Для его создания выберите раздел “Преднастроенные сервисы”. При создании понадобится выбрать данные для подключения к PostgreSQL и название анонимной роли.
Keycloak
Как и PostgREST, Keycloak можно развернуть в разделе “Преднастроенные сервисы”. Понадобится выбрать данные временного администратора Keycloak и данные для подключения PostgreSQL.
Итог
BaaS появился не потому, что backend стал не нужен, он появился потому что в огромном количестве проектов повторяются одни и те же сценарии, которые можно не писать каждый раз заново.
В данной статье я рассказал об устройстве BaaS и о том, как можно создать свой BaaS достаточно поверхностно, но уже в следующей статье я разберу тему максимально подробно, показав на примере, как из open source компонентов собрать свое BaaS-решение.
ссылка на оригинал статьи https://habr.com/ru/articles/1047242/