Всем добра! Данную статью меня побудило написать сильное желание, во-первых, зафиксировать некоторые результаты своего профессионального развития и личного опыта помимо основной работы, а также впервые на столь широкой сцене как Хабр, рассказать вам о нюансах развития своего небольшого pet-проекта, который впоследствии стал частью автоматизации сети заведений. Чтобы статья несла практический смысл и не выглядела как реклама, в ней не будут упомянуты ни название проекта, ни ссылки на его сайт и прочие сервисы.

C чего все началось

У меня есть брат, он с ребятами развивает сеть заведений общественного питания. Все началось в марте 2018 с его сообщения о том, насколько сложно мне будет написать веб-приложение, в котором будет отражен список заявок на бронирование столиков в баре. Стояла задача заменить функционал приведенной ниже Google-таблицы во что-то более приятное и расширяемое:

Исходная таблица с записями о бронировании столов
Исходная таблица с записями о бронировании столов

Мы обсудили основные необходимые моменты, которые были бы удобны работникам бара, и сошлись на написании MVP (фактически todo-листа) за ближайшие несколько дней. Думал ли я тогда, что из этого todo-листа вырастет целая система? Скорее нет. Но выросло что-то подобное.

Проектирование архитектуры

Из опыта у меня был лишь мучительный опыт фриланса (в основном JavaScript и верстка) и разработки сайтов на Wordpress, а по основной работе я был начинающим Frontend-разработчиком в организации, которая в то время начала активно переносить фронт своих проектов на Angular Framework.

К слову, опыт разработки на Angular на тот момент составлял у меня в лучшем случае полгода. Имея большее желание помочь ребятам, чем реальный опыт разработки, вооружившись безумием и отвагой, я начал делать первые шаги в проектировании структуры проекта. 

В качестве фронтенда для веб-приложения я выбрал Angular актуальной тогда версии v.4 с системой компонентов Angular Material. Опыта написания бэкенда, как и времени на его изучения под эту задачу не было, поэтому было решено поэкспериментировать с Firebase - ключевую роль в этом сыграло мое посещение с коллегами фронтенд-митапа в офисе Google летом 2017, где выступал докладчик из Google (нашлась ссылка на тот самый митап).

Первые версии, первый опыт и первые проблемы

Через неделю после обсуждений и выявления потребностей заказчика я показал первую версию списка бронирования столов - функционал был на уровне таблицы в Excel, а данные уже хранились в Realtime Database у Firebase:

Отображение структуры БД в виде дерева
Отображение структуры БД в виде дерева

Никакой аутентификации пользователей, логирования и прочего тогда даже не подразумевалось- это была обычная веб-страница, которую выложили в сеть и дали к ней доступ персоналу заведения (в плане архитектуры приложения - один Angular Module, один Injectable-сервис и пара-тройка компонентов). А поскольку в заведении назревала острая необходимость в использовании хоть какого-то средства для ведения заявок на бронирования, эту сырую версию с косяками и ошибками сразу же взяли в работу.

Одна из самых первых реализаций списка бронирования
Одна из самых первых реализаций списка бронирования

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

Также одна из ранних реализаций
Также одна из ранних реализаций

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

Для удобства работы, помимо списка столов было решено визуализировать схему рассадки гостей в заведении в виде схемы - по аналогии с тем, как она выглядела в системе Quick Resto (в этих же заведениях она уже использовалась для учета заказов и работы с кассовым терминалом):

Конструктор схемы зала (на скриншоте приведен режим редактирования)
Конструктор схемы зала (на скриншоте приведен режим редактирования)

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

Проблема 1: "один телефон на всех"

С ростом количества заявок появилась первая большая проблема: в баре есть "рабочий" мобильный телефон, на который поступают звонки с заявками. На звонок по нему может ответить либо бармен, либо администратор, либо даже повар, а затем внести новые данные или обновить бронь клиента. При этом возникала неразбериха, когда один работник не предупреждал коллег. Во избежание путаницы в архитектуру системы пришлось добавить такое понятие как "действие" (action) и складывать все действия в журнал бронирования. Теперь стало понятно, кто из сотрудников, когда и что сделал.

Проблема 2: Ошибки при внесении данных о бронировании

Со временем ребята столкнулись с еще одной проблемой - постоянное внесение номера телефона и данных клиента в заявку на бронь затратно по времени и влечет ошибки/опечатки. Мне предложили подумать о том, чтобы  в приложении была возможность доступа к контактам с мобильного телефона.

Но! это же обычная вэб-страница, на тот момент даже не PWA-приложение, и ни о какой интеграции с нативными функциями телефона не могло быть. В резульате обсуждений было принято вынести данные клиентов в отдельный справочник. Так созрела идея добавить второй модуль - "Клиенты", а при создании очередной заявки на бронь появилась возможность выбора из ранее добавленных данных.

Список клиентов с возможностью их деления на группы
Список клиентов с возможностью их деления на группы

Время шло, проект начал разрастаться

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

В период карантина и почти полного простоя общепита из-за COVID-19, я принял решение расширять возможности проекта, доведя его до полноценной системы учета и управления ресурсами заведения. К этому времени как разработчик я уже набрался большего опыта и посчитал это посильной задачей.

Работу над расширением проекта я начал с базовых модулей для учета ресурсов.

  • Что? Сами ресурсы: в быстрые сроки была реализована первая версия модуля "Номенклатура" с подразделами для учета "Ингредиентов", "Полуфабрикатов", "Модификаторов", а позднее там добавились "Стоп-листы".

  • Где? Где будут вести учет: для этого я выделил в структуре проекта логическую сущность "Предприятие", в которое вошли "Места реализации" (заведения), "Места приготовления" (кухня) и "Склады".

  • Когда? Списание ресурсов происходит через привязку связку модуля "POS-терминал" с данынми "Места реализации", который позволяет осуществить продажу готовых блюд и напитков (по аналогии с тем как работают системы rKeeper, iiko, Quick Resto).

В связи с тем, что другие модули в процессе работы требовали большого количества переиспользуемых данных, я вынес работу с подобными данными в "Справочники" - такие как "Типы оплат", "Фасовки", "Ставки НДС" и прочие.

Мне повезло, что при добавлении каждого из модулей сразу же была возможность применения его в реальных бизнес-процессах у тех заведений, где использовался проект. С того момента для себя я начал гордо называть этот проект "Системой", однако в действительности до системы было еще совсем далеко.

Механика взаимодействия с БД

Вкратце для того, чтобы внести представление о взаимодействии фронтенда с данными из Firebase Realtime Database, перечислю несколько примеров методов, которые используются в сервисах Angular-проекта для общего понимания:

Пример получения данных из Realtime Database:

/**
 * Возвращаем список всех единиц бронировния по заведению в компании
 *
 * @param {string} companyId
 * @param {string} divisionId
 * @return Observable<ReservationItem[]>
 */
 public getReservationItemsByDivision(companyId: string, divisionId: string): Observable<ReservationItem[]> {
     return this._db.list<ReservationItem>(`__идентификатор_ноды_дерева__${companyId}`, (ref: DatabaseReference) => {
         return ref.orderByChild('divisionId').equalTo(divisionId);
     }).snapshotChanges()
       .pipe(
          map((changes: SnapshotAction<ReservationItem>[]) =>
             changes.map((c: SnapshotAction<ReservationItem>) => {
                const data: ReservationItem = c.payload.val() as ReservationItem;
                const key: string = c.payload.key;
                return {key, ...data};
              })
           )
        )
    );
 }

Пример обновления данных в Realtime Database:

/**
 * Обновление заявки на бронирование
 * 
 * @param {ReservationOrder} reservationOrder
 */
public updateReservationOrder(reservationOrder: ReservationOrder): Promise<void> {
   return this._db.object(`__идентификатор_ноды_дерева__`)
      .update(reservationOrder);
}

Пример удаления данных из Realtime Database:

/**
 * Удаление заявки на бронирование
 *
 * @param reservationOrder
 * @return Promise<void>
 */
public deleteReservationOrder(reservationOrderId: string): Promise<void> {
   return this._db.object(`__идентификатор_ноды_дерева__`).remove();
}

Firebase наше всё? (нет)

C ростом количества модулей произошла некоторая неизбежность (из-за лени и отсутствия времени) - в проекте значительно увеличился уровень их интеграции с Google Firebase. Ниже приведу основные, задействованные в ходе разработки:

  • Firebase Authorization
    Авторизация пользователей по номеру телефона, email

  • Firebase Realtime Database
    Хранение всех данных в виде структурированной БД (древовидной), все сервисы по каждой сущности хранятся в дереве, а запрашиваются из БД через сервисы внутри ядра системы

  • Firebase Functions
    Функции обработки загруженных изображений (создание 3 разных превью в различных размерах при загрузке фото), сам код функций выполняется на удаленных серверах Google

  • Firebase Cloud Messages (ранее Google Cloud Messages)
    Отправка push-уведомлений (используется внутри MessagingService в проекте), работает в браузере и на Android-устройствах

  • Firebase Filestorage
    Хранение статичных файлов изображений

Полагаться только лишь на один сервис от Google было бы опрометчиво, поэтому постепенно в проекте начал появляться бэкенд. Так, модуль эквайринга для взаимодействия с банком и проведения платежей был написан на PHP (Symfony) моим товарищем бэкендером, а интеграция с ККТ на текущий момент запланирована через внешний сервис kkmserver.

Сейчас бизнес-логика и структура БД стабильна по своей структуре, однако вижу смысл в постепенном и частичном уходе от Firebase в силу ряда ограничений в гибкости при разработке - начиная от реализации структуры новых "веток" в структуре баз данных, заканчивая отсутствием полного контроля над данными в приложении (всегда есть опосредованный провайдер).

На текущий момент сам Google Firebase в России работает стабильно (за исключением республики Крым - там сервис не доступен), но в силу ряда известных всем ограничений, привязка российских карт к платежному аккаунту Google невозможна.

Развитие отдельных сервисов

Появление учета ресурсов позволило создать отдельное веб-приложение в общем доступе для просмотра меню заведения по QR-коду и помогло барам быстрее адаптироваться к условиям новой реальности после COVID-19 через внедрение электронного меню. Так появился первый отдельный сервис на базе "Системы" - назову его условно LINK. С его помощью гости заведения или пользователи сайта могут перейти по короткой ссылке и посмотреть информацию и цены блюд и напитков. Со временем удалось внедрить в LINK возможность оплаты через эквайринг банка.

На появление новых способов взаимодействия в гостями общепита меня вдохновляло и развитие технологий - в 2021 я взялся за изучение кастомных веб-компонентов и Angular Elements, и решил "завернуть" функционал бронирования столика со стороны гостя в виджет на сайте - наподобие тех, которые позволяют внедрить онлайн-мессенджеры на сайт или форму записи в салон красоты. Это занятие привело к появлению нового сервиса, назовем его WIDGETS с набором виджетов для размещения на сторонних сайтах.

Текущее состояние проекта

На сегодняшний день мой pet-проект ("Система") состоит из 65 модулей, 11 репозиториев и по потреблению ресурсов (на 5 заведений) еще умещается в бесплатном тарифе Firebase - суммарный объем хранилища на момент написания статьи порядка 4,5 Гб. На текуший день в проекте есть система управления доступом, с возможностью создания учетных записей для персонала и разграничением прав работника в зависимости от его роли (должности), не исключаю что со временем придется перейти на IDM-систему вроде Keykloak.

Так сейчас выглядит основной экран навигации после входа в проект
Так сейчас выглядит основной экран навигации после входа в проект

В состав проекта так же вошли мобильные приложения под iOS и Android для гостей и персонала заведений, которые реализованы по принципу One Codebase через использование Ionic Capactitor. Кодовую базу пока еще удается бережно хранить в актуальном состоянии (пару недель назад состоялся успешный переезд на Angular 16.2.5, частично переписал управление состоянием в проекте при помощи Angular Signals).

Конечно же, нельзя без ложки дёгтя

Надо понимать, что в основе проекта сейчас используется Firebase - если работу со статикой (изображениями) можно переписать с "Functions + Storage" на PHP или NodeJS (express.js), то с основными данными сложнее - так как данные хранятся в виде дерева - типичный NoSQL со своими плюсами, бубном и минусами, и к сожалению отсутствием адекватной возможности создания сложных запросов. Вероятно, для более оптимальной работы на широкие массы, со временем придется мигрировать на MySQL/PostgreSQL.

На текущем этапе понимаю, что в голове уже все перестаёт умещаться, поэтому большое внимание уделяю пользовательской и технической документации по проекту - для написания инструкций по использованию обратился к специалисту, а техническую документацию по проекту пока позволяет реализовать npm-пакет @compodoc/compodoc.

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

Выводы и планы на будущее

Во время работы мне удалось пощупать и популярные на текущий день в РФ системы управления ресторанами и посмотреть как они устроены, и очевидно, что стремиться моему проекту еще есть к чему - важнейшную роль тут играет не столько мое желание, сколько потребности в автоматизации бизнес-процессов у заведений.

Что будет интересно доработать - полноценно встроить работу с онлайн-кассами, интегрироваться с крупными игроками либо внедрить импорт/экспорт основных данных с проектом, реализовать монетизацию (идей много, но не в рамках этой статьи).

За почти пятилетний срок с первого запуска преодолена масса ошибок, багов, сонных дней и бессонных ночей - но, несмотря на это, я считаю этот проект наиболее технологичным решением из тех, с которыми мне удалось поработать за последнее время, именно с точки зрения разработки архитектуры и написания кода в своё удовольствие и есть желание развивать в будущем.

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


  1. olegbarabanov
    20.10.2023 02:03
    +2

    Если вас напрягает привязка к Google Firebase вы можете попробовать присмотреться к неполной альтернативе в виде Supabase (GitHub: https://github.com/supabase/supabase), которое является Open Source решением и большинство функционала которого базируется поверх уже знакомого многим PostgreSQL.

    У Supabase в облачном варианте также есть бесплатные тарифы для ознакомления, но в отличие от Firebase, Supabase можно полноценно развернуть на своем сервере.

    Конечно Supabase не является полноценной альтернативой Firebase, но какую-то часть задач вполне может на себя взять.


    1. yuryweiland Автор
      20.10.2023 02:03

      Спасибо большое! Не знал о таком решении, возможно это очень даже неплохая альтернатива для потребностей проекта - обязательно ознакомлюсь.


  1. kharitonovAL
    20.10.2023 02:03
    +2

    Автор молодчина! Поднять и развить такой проект не просто, еще и не забить на него по пути. Желаю дальнейшего успешного развития! Было бы интересно что стало с проектом через год.

    По фаербейс, на сколько знаю, после перехода на платный тариф цены там кусаются. Ну и плюсом ко всему платный аккаунт Гугл сейчас или совсем не сделать в России (чтоб оплачивать Blaze план) или нужно несколько бубнов для этого.


    1. yuryweiland Автор
      20.10.2023 02:03

      Благодарю Вас, постараюсь освещать жизнь проекта в следующих статьях :) Долгое время уже горю различными идеями автоматизации общепита, очень нравится эта сфера.

      Одно время (до зимы 2022) я даже оплачивал тариф Blaze, там буквально копейки тратились на тот момент, потом случились проблемы с привязкой карты на платежный аккаунт, в итоге сейчас задумываюсь отходить от Firebase хотя бы частично.


      1. kharitonovAL
        20.10.2023 02:03
        +1

        Мы настраивали Parse Server для своего проекта. Одно время он был отличным конкурентом Firebase, но потом стал опенсорс. В целом неплохая альтернатива. На Firebase у нас остались только push-уведомления.


  1. LeshaRB
    20.10.2023 02:03

    А зачем вам дата рождения клиента? У меня при заказе ни разу не спрашивали об этом (не считая если ДР, и какие-то скидки, то просят паспорт)

    И ещё если клиент заказал столик, но не пришел или отказался. В таблице клиенты колонка заказы меняется в сторону уменьшения или нет?


    1. yuryweiland Автор
      20.10.2023 02:03

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


  1. vindy123
    20.10.2023 02:03

    отличная статься, спасибо! а вы умеете в списание ингредиентов со склада на основании калькуляционных карт блюд (продали пиццу на кассе->списали 300 гр теста, 80 гр сыра, 50 гр салями со склада)? и есть ли мысли по продаже решения другим общепит бизнесам?


    1. yuryweiland Автор
      20.10.2023 02:03

      Добрый день! Списание ингредиентов по тех.картам сейчас в процессе разработки, если успею то до конца года выкачу

      Мысли по массовому запуску есть :) для начала это будет в основном модуль бронирования, а далее уже подключение остальных модулей системы вплоть до интеграции с ККТ и работой с ЕГАИС и прочим


  1. zubrbonasus
    20.10.2023 02:03
    +2

    Можно довести до коммерческой версии и продавать.