Когда речь заходит о готовых решениях в качестве сервера для мобильного приложения, то первое, что приходит на ум - это Firebase и решения от Amazon. Часто этого действительно бывает достаточно даже в рамках Spark тарифа Firebase. Тем не менее, если хочется большего, включая возможность расширять функционал сервера не только силами Cloud Functions, то для этих целей отлично может подойти Appwrite.
Appwrite - это открытое BaaS решение, которое содержит множество готовых модулей, основные из которых - это база данных, хранилище, авторизация и функции. Конечно, это не единственное подобное решение на просторах Github, и, если вам по какой то причине Appwrite пришелся не по душе, то можно рассмотреть такие аналоги:
Kuzzle - это реалтайм база данных;
Purse Server - имеет массу различных адаптеров, включая адапер для push-уведомлений;
Strapi - больше функционирует как CMS, но есть и интересное среди плагинов;
Superbase - молодой проект относительно аналогов, но активно развивается.
Изначально Appwrite привлек своей документацией, по которой не сложно ориентироваться и опциями рантаймов для функций. Сегодня я бы хотел рассказать именно о Appwrite и подводных камнях, с которыми пришлось столкнуться в процессе выхода в продакшн.
Актуальную документацию и листинги кода призываю смотреть в официальном источнике, а в этой статье расскажу об основных деталях развертки, и о том, что в документации не найти или легко пропустить.
Функционал
Интеграция
У Appwrite есть SDK для всех основных платформ которые могут пригодится iOS, Android, Web и Flutter. Стоит заметить, что iOS фреймворк хоть и имеет минимальную версию 15.0, может спокойно работать и на 13.0, если просто форкнуть и поправить версию самостоятельно.
Если вам нужно самостоятельно поддержать API, то есть REST с GraphQL. Так же есть веб-сокеты, которые в документации называется Realtime - https://appwrite.io/docs/realtime
Пользователи
Авторизация может быть реализована с помощью почты или номера телефона. Если такой модуль включен, то пользователи смогут создавать себе аккаунты со стандартным набором полей (имя, почта, номер телефона и пр.), а так же кастомных полей. Также юзеров можно группировать, что помогает разграничить их доступ к БД, файлам или функциям.
База данных
База данных хранится в MariaDB и, к сожалению, не имеет удобных инструментов для миграций. Однако, можно написать скрипты чтобы применить изменения массово, например, использовав готовый пример на питоне - https://github.com/appwrite/playground-for-python. Он спасал меня несчетное количество раз, прежде чем я начал полноценно встраивать функции.
Также стоит сделать ремарку по поводу пагинации: лимит запрашиваемых записей 100, максимальный offset - 5000. Поэтому, если у вас большая база и нужно полностью перебрать все записи, то лучше двигаться по страницам с помощью курсора - https://appwrite.io/docs/pagination#cursor-pagination.
Хранение файлов
Выбор места для хранения файлов довольно велик: S3, DOSpaces, Backblaze, Linode, Wasabi + локально, что может быстро выйти за рамки дроплета. Так как мы разворачивали сервер на Digital Ocean, то решили использовать DOSpaces, подключили конфиг и забыли. Стоит еще заметить, что можно подключить антивирус в виде https://www.clamav.net/.
Функции
Функции доступны на обширном наборе языков - список в доках, выбор ограничен лишь env конфигурацией, которая поможет поддержать рантайм. Функции удобно создаются через CLI, который может полностью заменить вам web-интерфейс.
Прочее
геолокации по IP,
получение изображений по типа флагов, иконок платежных карт,
обрезка и ресайз изображения по url,
возможность настройки веб-хуков на событие и пр.
Установка
Заводится Appwrite просто: достаточно запустить дроплет с ним в Digital Ocean и можно пользоваться. Если нет желания тратить на него дроплет, то существует огромный Docker Compose конфиг. Редактируем конфиг с нужными вам volumes, добавляем .env файл и можно запускать.
Настройка
Настройка происходит с помощью изменения файла .env. Если вы устанавливали Appwrite сразу из Digital Ocean, то он будет находится по адресу /root/appwrite/.env
.
Правок вносить в него не нужно, но можно указать _APP_DOMAIN_TARGET
с адресом хоста. Можно еще настроить SMTP сервер, но делать этого не обязательно, особенно если не планируете, что пользователи будут регистрироваться. Так же, если планируется хранить много файлов, то хорошо бы подключить какой-то из предложеных в конфигурации решений. Например, для DOSpaces это будет 4 поля:
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
_APP_STORAGE_DO_SPACES_SECRET=
_APP_STORAGE_DO_SPACES_REGION=
_APP_STORAGE_DO_SPACES_BUCKET=
Тут же можно встретить настройки рантаймов для функций - _APP_FUNCTIONS_RUNTIMES
. На данный момент доступны такие варианты: node-14.5, node-16.0, node-18.0, php-8.0, php-8.1, ruby-3.0, ruby-3.1, python-3.8, python-3.9, python-3.10, deno-1.21, deno-1.24, dart-2.15, dart-2.16, dart-2.17, dotnet-3.1, dotnet-6.0, java-8.0, java-11.0, java-17.0, java-18.0, swift-5.5, kotlin-1.6, cpp-17.0
Вот и вся настройка. После этого можно заходить на ваш сервер, создавать админа и начать настройку первого проекта.
Проект
Сначала требуется создать организацию, в которых будут создаваться проекты. Очень удобно, например, если необходимо дать доступ к группе проектов клиенту. В проект нужно добавить приложения которые будут подключаться к серверу: iOS, Android, Web и пр. Также есть возможность сгенерировать токен для доступа к API.
База данных
Проект может содержать несколько баз данных. Например, для своего проекта я создал две базы. Одну для хранения токенов, вторую для хранения контента для пользователя. При создании базы не очень очевидно, что можно самому выбрать id, тапом по кнопке под полем названия БД Database ID
.
Свой ID очень полезен тем, что прописывая какие либо триггеры на события или же просто подключаясь к ней c клиента, вам визуально будет видно что это за БД. Каждая БД содержит коллекции (таблицы), в которых будут наши данные. Плюс, также сюда можно вписать свой ID, как и при создании самой базы данных. В настройках коллекции дан уже более развернутый список опций:
Documents: содержимое нашей коллекции. Лимит offset 5000 поэтому внизу может быть написано, что коллекция содержит 5000 документов, даже если у вас их в разы больше. Возможно, это когда-нибудь поправят.
Attributes: описание полей с их типами.
Indexes: нужны для ускорения доступа к базе данных. А так же просто необходимы, если нужно получить данные в какой-нибудь сортировке.
Activity: показывает последние устройства с которых были произведены изменения в БД, но не за все время.
Usage: различные графики чтения, записи, удаления и пр. Если для вас это актуально, то у Appwrite есть возможность отдавать статистику на другие сервисы, плюс есть API. На практике не проверял.
Settings: тут можно поменять название, настроить права для групп пользователей. В моем случае мне не нужно было авторизовать или регистрировать пользователей приложения, поэтому доступ я дал практически полный, кроме записи для read-only коллекций.
Хранилище
Хранилище разделяется на бакеты. На каждый бакет, да и на каждый файл в бакете можно настроить права доступа. Если вы будете хранить изображения, то для доступа к ним есть отдельный интерфейс который обрежет, отмасштабирует и даже добавит скругление - https://appwrite.io/docs/client/storage?sdk=web-default#storageGetFilePreview. Среди пул-реквестов видел поддержку HLS, так что, возможно, в будущем будет возможность грузить и видео файлы быстрее.
Функции
Для того, чтобы создавать функции, удобно использовать CLI Appwrite, но можно это сделать и через SDK. Для функций очень много различных примеров. В нашем проекте мы использовали их для подготовки превью видео роликов (сжатие и обрезка через ffmpeg), которая запускалась при каждом изменении коллекции с видеороликами, а так же операции записи пуш токенов и отправке уведомлений.
Стоит отметить интересный момент. По умолчанию, в функцию передается токен пользователя который ее запустил, но если вам нужен другой доступ, то ключи с токеном доступа необходимо указать самостоятельно. Иначе вы можете долго гадать почему же выполнение падает, как было со мной: когда писал функции на питоне, приходилось смотреть логи контейнера через Docker. Если что-то крашится и логов не видно, то логи контейнера вам помогут. Id активного рантайма указано в поле Deployment ID
.
Push-уведомления
Серьезный недостаток Appwrite в том, что у него нет поддержки пушей из коробки. Для этого можно использовать другие решения и реализовать остальное функциями и базой данных - нам нужна будет коллекция, где мы будем хранить пуши, и функция, которая позволит их писать. Я не стал разрешать пользователям читать эту коллекцию, за это будет отвечать функция, которая будет принимать id пользователя, платформу и токен пушей.
В качестве отправителя уведомлений я взял go реализацию appleboy/gorush и добавил ее в Docker Compose файл Appwrite.
gorush:
image: appleboy/gorush
container_name: push-server
restart: unless-stopped
networks:
appwrite:
runtimes:
volumes:
- ${PWD}/../gorush/config.yml:/config.yml:rw
Тут я добавляю доступ к сетям рантаймов и, на всякий случай, самой appwrite.
При массовой рассылке пушей стоит учесть лимиты offset значения у БД. Лучше собрать токены для рассылки путем пагинации через курсор, о котором я поминал ранее (документация).
Также необходимо продумать механизм удаления неактивных пушей, так они могут терять актуальность. К сожалению, автоматического решения этого я еще не нашел, пока обхожусь парсингом логов gorush контейнера, выделением токенов, которые провалились, и удалением их из БД. Хорошо то, что операция не такая частая и можно решить ее bash-ем + cron. Пользуясь логами, я удалил порядка половины всех хранившехся токенов.
Аналитика
К сожалению, замены аналитики у Appwrite нет, но если хочется полностью отказаться от Firebase, можно посмотреть на https://github.com/Countly/countly-server. Он так же умеет собирать краши и отправлять push-уведомления. Уверен, это еще и не единственное решение.
Заключение
Appwrite подающий надежны аналог Firebase который продолжает развиваться и имеет сильный потенциял. У них есть достаточно активный дискорд где можно задать вопросы по каждому модулю в различных чатах, что порой очень помогает. Наш проект c Appwrite находится в продакшене уже 3й месяц. При чем он переживал нагрузку в 17 тысяч пользователей в сутки на самом дешевом дроплете DigitalOcean‑а и серьезных проблем с загрузкой я не заметил. Следил через Firebase Performance по отдельным моментам, такие как скорость загрузки видео с сервера. Сами запросы к нашему серверу Firebase автоматически не трекает если использовать SDK. В целом результат нас устроил. Не хватает только какого то дополнительного CMS для БД. Заполнять поля БД вручную бывает утомительно.
IliyaZimin
supabase *