Маркетинг стал частью мира разработки. По количеству звездочек на GitHub определяют, какое из похожих друг на друга решений круче, а по количеству твитов можно спрогнозировать, какая технология будет развиваться в ближайшие полгода. В таких условиях мы рискуем стать жертвами хайпа, что мы в Лайв Тайпинге и сделали, принимая Firebase за Священный Грааль, способный решить все проблемы разом: сбора статистики, интеграции чатов, выбора базы данных, быстрой разработки MVP. Когда же я столкнулся с этим сервисом в бою, то понял, что моё представление о Firebase расходилось с реальностью настолько сильно, что понимание области применения технологии стало для меня настоящим откровением. Я хочу поделиться этим пониманием и тем, как всё-таки использовать Firebase правильно.



Желание поработать с Firebase появилось у меня давно, но я ждал подходящего проекта. И дождался: MVP системы бронирования офисов. Так как это MVP, бизнес-логика бэкендa довольно примитивная. К тому же к Firebase будет подключаться мобильное приложение на iOS. С виду идеальный случай для использования сервиса, но в ходе реализации пришлось столкнуться с некоторыми проблемами, о которых и пойдёт речь дальше.

Но сначала хотелось бы устранить все недопонимания. Вот две вещи, которые нужно усвоить для работы с Firebase:

  1. это не бэкенд, а база данных. Можете забыть о тех чудо-примерах приложений на Firebase без серверной части, это пока недостижимо;
  2. это NoSQL со всеми его преимуществами и недостатками.

Выбирать решение для хранения данных нужно исходя из природы самих данных. Firebase имеет свои недостатки:

  • его область применения намного меньше, чем у NoSQL-решения;
  • Firebase сильно ограничивает вас при выборке данных и при необходимости записать данные в несколько мест одновременно;
  • далеко не со всеми структурами данных удобно работать в Firebase.

Но инструмент, позволяющий быстро начать разработку MVP и имеющий ещё массу преимуществ, выкидывать на свалку жалко. А слабые места можно и залатать, если они вам мешают.

Преамбула


Представим, что вы разрабатываете систему бронирования для сети отелей.



Там есть такие сущности:

  • отель
  • номер
  • клиент
  • бронь

Как реализовать это на SQL-базе, понятно: четыре таблицы со связями, и дело в шляпе.

Как реализовать это на NoSQL (Firebase)? Можно попробовать вложить сущности в друг-друга:

{
  “отель”: {
    …
    “номера”: {
        “номер”: {
        ???
      },
      ...
    }
  }
}

Тут начинают возникать вопросы: а стоит ли вкладывать все букинги в номер? а куда вкладывать клиентов? И т.п. Проблема NoSQL зачастую в том, что данные приходится дублировать.

Есть второй вариант: попытаться использовать NoSQL схожим с SQL способом и создать в корне объекты для каждой сущности, а связи поддерживать, храня id других объектов.

Вероятно, в других NoSQL-базах бороться с этими проблемами проще, но решения для своих задач я в Firebase не нашёл.

Какой бы вариант вы не предпочли, у них есть одинаковая проблема: невозможность сделать сложную выборку данных. Что делать, если вы хотите получить список бронирований конкретного клиента? Эти бронирования могут оказаться вложенными в разные номера и отели, а если структура плоская, то Firebase не сможет отфильтровать данные по нескольким параметрам (эту проблему даже обсуждали на StackOverflow). В общем, если вы хотите сделать выборку по клиенту и дате бронирования, Firebase SDK вам ничем не поможет.

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



Что делать?


Не использовать Firebase для сложной выборки данных. В этом нам может помочь собственный бэкенд на Node.js и один из нижеописанных инструментов.

ElasticSearch




Это поисковый движок с JSON REST API, использующий Lucene и написанный на Java. Подробности можно почитать на официальном сайте, а мы сразу начнём рассматривать его в связке с Firebase.

Установка


Нужно поставить ElasticSearch на сервер (сделать это по инструкции будет несложно). После нужно интегрировать его с Firebase, а именно — создать поисковый индекс из базы Firebase. Я использовал официальную интеграцию от Firebase. Для запуска нужно скачать репозиторий, установить зависимости и заполнить config с ключами для Firebase.

В этом решении я нашел несколько минусов:

  1. это отдельное приложение на Node.js, и его сложно связать с бэкендом;
  2. создать правильный индекс для ElasticSearch непросто, и одной синхронизацией данных с базой Firebase не обойтись;
  3. типы полей присваиваются автоматически.

Прочувствуйте их на таком простом примере. У вас есть список зданий с их описанием и координатами. ElasticSearch требует, чтобы поля, отвечающие за географические координаты зданий, были объявлены таковыми заранее, на этапе создания поискового индекса. Интеграция движка с Firebase не даёт контроля над этим процессом, а просто синхронизирует все данные, автоматически определяя типы данных.

ElasticSearch бесплатен и разворачивается на своём подконтрольном сервисе — это плюс. Но вместе с тем возникает ряд проблем с деплоем и безопасностью, которые нужно продумать заранее.

Пример: открыт порт, используемый Elastic Search для внешних запросов. Это создаёт уязвимость, так как этот же порт используется для записи и управления поисковыми индексами. Возможным результатом такого недосмотра станет удаление поискового индекса или внесение в него своих данных. Поэтому изначально этот порт открыт только для запросов с той же машины, на которой установлен ElasticSearch.

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

Algolia



SaaS-решение для поиска. Платное, но с бесплатным планом. С прайсом и прочими деталями можно ознакомиться на официальном сайте.

Интеграция с Firebase реализована при помощи официальной js-библиотеки. Процесс установки и запуска подробно описан в readme, и у меня всё заработало с первой попытки.

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

var algoliasearch = require('algoliasearch');

…

var client = algoliasearch(config.algolia.applicationID, config.algolia.apiKey); // инициализируем Algolia
var indexRooms = client.initIndex('rooms'); // инициализируем поисковый индекс Algolia

rooms.once('value', initInde); // rooms — это reference к объекту в Firebase
function initIndex(dataSnapshot) {
  var objectsToIndex = []; // Этот массив мы отправим в Algolia
  var values = dataSnapshot.val(); // Получаем значение snapshot’a
  for (var key in values) { // обрабатываем каждый room
    if (values.hasOwnProperty(key)) {
      var firebaseObject = values[key];
      firebaseObject.objectID = key; // id объекта в Algolia должен совпадать с ключом в Firebase
      objectsToIndex.push(firebaseObject); // добавляем в массив
    }
  }
  indexRooms.saveObjects(objectsToIndex, function(err, content) { 
    if (err) {
      console.log('error');
      return;
    }
    console.log('success');
    return;
  });
}

В результате мы получаем поисковый индекс в Algolia, содержащий все объекты rooms из Firebase. Обратите внимание, что по ходу импорта данные можно обработать дополнительно, например подтянуть название отеля из другого объекта в базе данных.

После того, как мы создали индекс, мы не собираемся обновлять его целиком, поэтому в дальнейшем следим за событиями в Firebase и обрабатываем их:

rooms.on('child_added', addOrUpdateObjectRooms);
rooms.on('child_changed', addOrUpdateObjectRooms);
rooms.on('child_removed', removeIndexRooms);

Единственный минус в использовании Algolia в том, что за SaaS нужно платить. Но для MVP бесплатного тарифа должно быть достаточно, а делать на Firebase масштабный проект мало кому придёт в голову (я надеюсь).

В противовес этому сомнительному минусу мы получаем удобную админку с доступом к аналитике, поисковому индексу и нюансам работы поисковых запросов.
Важным плюсом является наличие SDK под всё и вся — от мобильных платформ до фреймворков для бэкенда. В суть я не вникал, но iOS-разработчик сказал: это удобнее, чем REST.

Я советую вам попробовать именно Algolia: интеграция с Firebase лучше, установка проще, а в довесок мы получаем консоль с аналитикой и SDK. Я оставил без внимания технические детали и не анализировал производительность и скорость, это сложная и отдельная тема.

Итоги


Выгоды этой довольно простой системы ощутимы. Мы получаем:

  • Firebase для хранения данных, всех операций чтения и простых неконкурентных запросов;
  • Node.js для всех конкурентных запросов и сложной бизнес-логики + обслуживания Algolia/ElasticSearch;
  • Algolia/ElasticSearch для поиска и сложной выборки данных.

Налицо все преимущества Firebase, без недостатков в виде необходимости дублировать данные или организовывать сложные и медленные выборки на Node.js. При таком раскладе можно легко реализовать систему бронирования или любую другую задачу, требующую транзакций и сложных выборок данных. Например можно сначала подобрать комнату на конкретный день на двух человек, с кондиционером и балконом, а потом забронировать её и не бояться, что комната уже была занята или будет занята повторно. Некоторые данные, правда, придётся дублировать, но исключительно в самом поисковом индексе, а не базе данных.

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

Жду в комментариях ваших историй об интеграции Firebase и замечаний по статье. Спасибо!
Поделиться с друзьями
-->

Комментарии (28)


  1. ertaquo
    24.01.2017 15:10
    +1

    Я недавно начал работать с Firebase (пробую писать кой-чего под Android). Мне в нем нравится то, что для моего приложения не нужно писать вообще ни байта серверного кода (за исключением правил для базы данных). Но огорчает ужаснейшая документация и отсутствие возможности сделать выборку элементов из базы по идентификаторам.
    Кстати, вложенные сущности использовать наоборот не рекомендуется. Суть в том, что при подписке на какой-либо из ключей при изменении вложенных сущностей передаются вообще все данные этого самого ключа. Например, в отеле изменили данные о номере. Если вы были подписаны на "/отель/", то вам придут данные об этом отеле целиком, со всеми номерами и прочим. Если же внутри отеля хранить данные о бронях, то придут и брони. Короче говоря, трафик увеличивается в разы. Поэтому нужно хранить данные как-то так:

    {
      "отели": {
        "id отеля": {
          ...
        }
      },
      "номера_отеля": {
        "id отеля": {
          "id номера": {
            ...
          }
        }
      }
    }
    

    Сумбурно описал, но надеюсь, смысл понятен будет.


    1. AllexIn
      24.01.2017 15:25
      +3

      Эмуляция реляционной БД?


      1. ertaquo
        24.01.2017 15:39

        Ага. Вот из доков кусок: https://firebase.google.com/docs/database/web/structure-data#flatten_data_structures
        И подобный стиль работы используется и в официальных примерах.


    1. DrPass
      24.01.2017 15:51
      +5

      Поэтому нужно хранить данные как-то так:

      Да дело в том, что это просто неподходящий инструмент для такой задачи. Если ваши данные имеют реляционную структуру, то и голову ломать не нужно, вам требуется реляционная СУБД. А подобные варианты — это забивание гвоздей крестообразной отвёрткой и закручивание шурупов молотком.


  1. A_V_E
    24.01.2017 15:27
    +11

    Придумать себе проблему, а потом ее героически преодолевать.

    Вывод — не надо пихать NoSQL туда, где его использовать не стоит.


    1. DmitrySkripkin
      24.01.2017 15:33
      +1

      Вывод верный, без костылей на Firebase далеко не уехать. Однако есть и свои плюсы. Авторизация и роли, real-time, SDK под разные платформы и многие другие полезные фичи. Для MVP годится.


      1. A_V_E
        24.01.2017 15:38
        +3

        Просто изначально Ваша задача подразумевала реляционность. Использовать для нее нереляционную БД — это напрашиваться на усложнение архитектуры. В частности, неизбежное потом прикручивание поисковика — эластика/солр/люцены/etc, просто потому, что понадобилось добавить фичу чуть-чуть усложненного поиска — и тут noSQL ожидаемо ломается.


    1. justboris
      24.01.2017 16:03

      Firebase позиционируется как Backend as a Service. NoSQL — это детали реализации, на которые наступаешь только уже попробовав.


      Посоветуете аналогичный сервис, только с SQL структорой данных?


      1. A_V_E
        24.01.2017 18:03

        В Backendless есть relations:

        https://backendless.com/documentation/data/js/data_relations.htm

        Плюс там есть поддержка внешних бд (поддерживаются пока только MySQL/MariaDB).


  1. GennPen
    24.01.2017 15:40
    +9

    Имейте совесть, не ставьте в следующий раз на КДПВ 10 мегабайтную гифку.


    1. justboris
      24.01.2017 16:05
      +1

      +1, не надо так, пожалуйста. Пара таких статей в ленте RSS — и квоте мобильного трафика на день придет конец.


      1. Kanumowa
        25.01.2017 12:16

        В настройках мобильного приложения можно включить загрузку картинок только через WiFi


        1. LoadRunner
          26.01.2017 17:19
          +1

          А можно и не включать. На десктопах 10-мегабайтные гифки тоже не вызывают фонтанов радости. На слабых компьютерах рендеринг повесит систему наглухо.


  1. ostapbender
    24.01.2017 15:51
    +17

    Хипстеры на марше.


    Ради задачи, для решения которой (в виде MVP) за глаза хватит SQLite, притащили адовый ворох SaaS решений, потратили кучу времени на взаимное интегрирование всего барахла (без описания, конечно же, что, куда и с какими учетными данными за чем обращается), технично разложили грабли с денормализацией и дублированием данных, да помножили в своей этой Ноде все строки на картинки.


    Оглушительный успех, я считаю.


    1. xRay
      24.01.2017 16:09

      да помножили в своей этой Ноде все строки на картинки

      Это 5 с плюсом :)


    1. Am0ralist
      25.01.2017 00:10

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


      1. vintage
        25.01.2017 00:34
        -1

        Почему? SQLite поддерживает транзакции.


    1. i360u
      25.01.2017 12:09
      +1

      Firebase — это, какбэ, далеко не только БД. А реализация собственной полноценной архитектуры/инфраструктуры дает не меньший простор для "ломания дров". Все вокруг такие умные, хипстерами обзываются, а сами никогда не лажают, ага.


  1. zharikovpro
    24.01.2017 17:33
    +1

    > позволяющий быстро начать разработку MVP

    На многие грабли не пришлось бы наступать, если изначально продумали бы как быстро завершить разработку.


  1. amaksr
    24.01.2017 17:43
    +2

    Это статья как не надо становиться жертвой хайпа? гм…


  1. vintage
    25.01.2017 00:31

    OrientDB:


    1. гибридная субд, хранящая грубо говоря JSON-ы с отношениями.
    2. умеющая в права пользователей вплоть до права конкретному пользователю на чтение конкретной записи.
    3. Поддежка REST и адаптеры под разные языки.
    4. Lucene на равне с другими индексами, из коробки работает по любым полям.
    5. Все данные хранятся в нормализованном виде.
    6. Шардинг и репликация из коробки.
    7. Веб админка тоже в коробке.
    8. Простым запросом можно выбрать всё необходимое на нужный уровень глубины связей.
    9. Может встраиваться в Java приложение.

    Подробнее тут: https://habrahabr.ru/post/267079/


    1. Riim
      25.01.2017 16:37

      С ArangoDB не приходилось сравнивать? Сейчас между ними выбираю.


      1. vintage
        25.01.2017 22:19
        +1

        http://db-engines.com/en/system/ArangoDB%3BOrientDB%3BNeo4j
        http://vschart.com/compare/arangodb/vs/orientdb


        Фичи, которые есть в Orient, но нет в Arango:


        1. Автоматические распределённые запросы. Ориент знает на какой ноде что лежит и сам делает map-reduce.
        2. Прямые ссылки между документами. Это даёт простые запросы, быстрый переход по связям, удобное администрирование.
        3. Возможность встраивания в Java приложение с доступом к низкоуровневым API.
        4. Поддержка пессимистичных и оптимистичных блокировок.
        5. Триггеры, правда мне в своё время не удалось прикрутить их ко внешнему приложению.
        6. Fetch plan, позволяющий выбирать не только найденные узлы, но и связанные с ними по заданным правилам.
        7. Более развитая поддержка графов.
        8. Продвинутая система прав: пользователи/роли, записи/классы, чтение/запись/обновление/удаление, наследование.
        9. Шифрованное хранилище.

        Преимущества Arango:


        1. Встроенный NodeJS со всеми вытекающими.


  1. dim_s
    25.01.2017 10:40

    ElasticSearch прекрасно может жить без Firebase как поисковой движок + база данных NoSQL.


  1. iloveip
    25.01.2017 11:14
    -1

    Мы на нашем сайте тоже используем Firebase для сервиса Банки http://www.iloveip.ru/banki/ и Algolia для поиска. Firebase работает в связке с таблицей Google, я писала об этом подробно на Хабре (https://m.habrahabr.ru/company/iloveip/blog/312232/). Для MVP Firebase действительно подходит, но в будущем мы будем от него отказываться. А вот к Algolia нет никаких вопросов, для поиска на статичном сайте этот сервис вполне устраивает.


  1. iloveip
    25.01.2017 11:35
    -1

    Прошу прощения, отправляла комментарий с мобильного, поэтому ссылки не поставились.

    Вот пост на Хабре про использование Firebase https://habrahabr.ru/company/iloveip/blog/312232/

    И ссылка на сервис http://www.iloveip.ru/banki/


  1. i360u
    25.01.2017 11:44

    ИМХО, для MVP вполне подходит вариант хранения связей по идентификаторам в сущностях. Проблемы возникают только если формат данных подразумевает большое количество связей и тогда это вопрос правильной категоризации. Firebase — это отличный инструмент, который позволяет реально быстро стартануть и многое дает из коробки (включая хостинг статики), но конечно подходит не на все случаи жизни. Не думаю, что его стоит в этом винить.


  1. via-site
    29.01.2017 23:23

    Последние несколько месяцев разрабатываю «Offline First Progressive Web App» (для работы с очень плохим или вообще без Интернета). С самого начала планировал использовать Firebase для быстрого старта.

    Проблема возникла уже в самом начале, т.к. браузерный клиент Firebase не поддерживает полноценную офлайн работу. Лучшим решением что я смог найти оказался как бы порт CouchDB в браузеры — PouchDB и во всех современных (и не очень) браузерах он работает просто великолепно. Из коробки есть встроенная, автоматическая синхронизация с CouchDB (пока не проверял) или DBaaS сервисами типа: https://console.ng.bluemix.net/catalog/services/cloudant-nosql-db

    Вот сейчас сижу и думаю: «зачем мне вообще использовать Firebase, если PouchDB — Simple Server — CouchDB / DBaaS CouchDB решают все мои проблемы?»