Всем добра! Данную статью меня побудило написать сильное желание, во-первых, зафиксировать некоторые результаты своего профессионального развития и личного опыта помимо основной работы, а также впервые на столь широкой сцене как Хабр, рассказать вам о нюансах развития своего небольшого 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
Авторизация пользователей по номеру телефона, emailFirebase Realtime Database
Хранение всех данных в виде структурированной БД (древовидной), все сервисы по каждой сущности хранятся в дереве, а запрашиваются из БД через сервисы внутри ядра системыFirebase Functions
Функции обработки загруженных изображений (создание 3 разных превью в различных размерах при загрузке фото), сам код функций выполняется на удаленных серверах GoogleFirebase 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)
kharitonovAL
20.10.2023 02:03+2Автор молодчина! Поднять и развить такой проект не просто, еще и не забить на него по пути. Желаю дальнейшего успешного развития! Было бы интересно что стало с проектом через год.
По фаербейс, на сколько знаю, после перехода на платный тариф цены там кусаются. Ну и плюсом ко всему платный аккаунт Гугл сейчас или совсем не сделать в России (чтоб оплачивать Blaze план) или нужно несколько бубнов для этого.
yuryweiland Автор
20.10.2023 02:03Благодарю Вас, постараюсь освещать жизнь проекта в следующих статьях :) Долгое время уже горю различными идеями автоматизации общепита, очень нравится эта сфера.
Одно время (до зимы 2022) я даже оплачивал тариф Blaze, там буквально копейки тратились на тот момент, потом случились проблемы с привязкой карты на платежный аккаунт, в итоге сейчас задумываюсь отходить от Firebase хотя бы частично.kharitonovAL
20.10.2023 02:03+1Мы настраивали Parse Server для своего проекта. Одно время он был отличным конкурентом Firebase, но потом стал опенсорс. В целом неплохая альтернатива. На Firebase у нас остались только push-уведомления.
LeshaRB
20.10.2023 02:03А зачем вам дата рождения клиента? У меня при заказе ни разу не спрашивали об этом (не считая если ДР, и какие-то скидки, то просят паспорт)
И ещё если клиент заказал столик, но не пришел или отказался. В таблице клиенты колонка заказы меняется в сторону уменьшения или нет?
yuryweiland Автор
20.10.2023 02:03Дата рождения клиентов появилась не сразу - она нужна для использования в модуле "Программа лояльности" - там со стороны заведений можно сделать кампанию по уведомлению пользователей, у которых в ближайшие несколько дней будет день рождения (например, предлагать им скидки и прочее).
vindy123
20.10.2023 02:03отличная статься, спасибо! а вы умеете в списание ингредиентов со склада на основании калькуляционных карт блюд (продали пиццу на кассе->списали 300 гр теста, 80 гр сыра, 50 гр салями со склада)? и есть ли мысли по продаже решения другим общепит бизнесам?
yuryweiland Автор
20.10.2023 02:03Добрый день! Списание ингредиентов по тех.картам сейчас в процессе разработки, если успею то до конца года выкачу
Мысли по массовому запуску есть :) для начала это будет в основном модуль бронирования, а далее уже подключение остальных модулей системы вплоть до интеграции с ККТ и работой с ЕГАИС и прочим
olegbarabanov
Если вас напрягает привязка к Google Firebase вы можете попробовать присмотреться к неполной альтернативе в виде Supabase (GitHub: https://github.com/supabase/supabase), которое является Open Source решением и большинство функционала которого базируется поверх уже знакомого многим PostgreSQL.
У Supabase в облачном варианте также есть бесплатные тарифы для ознакомления, но в отличие от Firebase, Supabase можно полноценно развернуть на своем сервере.
Конечно Supabase не является полноценной альтернативой Firebase, но какую-то часть задач вполне может на себя взять.
yuryweiland Автор
Спасибо большое! Не знал о таком решении, возможно это очень даже неплохая альтернатива для потребностей проекта - обязательно ознакомлюсь.