Как мы переписали проект на React/Nodejs/MongoDB

23.02.2021
0
Время прочтения: 5 мин
Wiki
Эксперт
Оглавление:

Оглавление

Предыстория

В начале октября поступил заказ на разработку сайта доставки для ресторана Bamboobar. Вдохновением послужил сайт Яндекс Еды, который позволяет сделать заказ буквально в пару кликов. Такой же концепции придерживались и мы при разработке. Однако воплатить данную реализацию оказалось куда сложнее чем предполагалось. Ограниченный бюджет и сроки подтолкнули к WordPress. Почему именно данная CMS?

  • Возможность без боли интегрировать систему онлайн оплаты от сбербанка
  • Удобная административная панель для манипуляции с товарами, заказами.
  • Автоформирования подробных отчетов о продажах

Важно понимать что WordPress + Wocoommerce идеальная связка для небольших интернет магазинов, и с помощью данных инструментов можно реализовать качественные E-commerce проекты

В нашем случае, цель было создать Single Page Application, где все действия пользователя происходят на одной странице, без перезагрузок.

Как чувствует SPA на WordPress’e

В момент написания данной статьи, сайт доставки работает на WordPress’e и выпоняет свои функции на отлично, учитывая что при загрузке , на странице выводятся 230 позиции с продуктами.

Как удалось сделать SPA если все манипуляции с базой данных WordPress осуществляется только при перезагрузке страницы?

Несмотря на то что проект работает на Ядре WordPress , который с выходом PHP 7 стал в полтора раза быстрее, мы не могли прибегнуть к стадартным операциям. Для этого мы выполняли хуки Woocommerc’а асинхронно посредством Javascript. В конце у нас получился гибрид выполняюший PHP функции по средствам AJAX.

К примеру по стандарту Woocoomerce добавляет товар в корзину лишь при перезагрузке странице. Выглядит данная операция таки образом

  1. Происходит клик по товару для добавления в корзину
  2. По средстам POST запроса в корзину добавляется товаров
  3. Перезагружается страница с обновленной корзиной

Данный подход катигорически не подходил ибо загружать каждый раз страницу было некомфортно для пользователя.

Для этого мы решили сделать следующим образом. При клике на товаре они методом append добавлялись в корзину, а так же сохранялись в localStorage в виде объекта. Объект хранил в себе ID продукта, названия, количество, и цену. Когда пользователь заходил повторна загружалась корзина из localStorage , и он продолжал процесс покупки.

При клике на оформления заказа, сорвешались следующие действия

  1. Отправлялся ajax запрос на удаления Woocommerce корзины, ибо там могли оставаться товары с предыдущих сеансов
  2. Отправлялся запрос на добавления товаров в корзину. Благо есть возможность одним запросом отправить массив товаров. Изначально мы не знали про данную возможность и отправляли каждый товар новым запросом. Когда в корзине было около 12 товаров, данная процедура могла длиться по 30 секунд.
  3. Обновления страницы для пересчитывания товаров, и формирования коректного запроса на оплату. Модуль Сбербанка подерживает и отправку корзины. Вследствие чего, руководство могли просматривать транзакции в личном кабинете Банка + корзину покупок.

Тут возникает логический вопрос. Почему столько манипуляции с корзиной? Дело в том что при реализации на Woocommerce корзину можно обновлять либо при перезагрузке либо ajax функциями. Вторым вариантом мы решили воспользоваться, НО, при добавление, удаление товара, или же при смене количество , каждый раз требовалось отправлять данные на сервер, и самое важное дожидаться успешного ответа. Если говорить проще, то при каждом действие пользователь не мог делать операцию пока не завершиться предыдущия. И если он к примеру хотел удалить 5 позиции из корзины, ему бы приходилось ожидать по 2 секунды( в лучшем случае) каждую операцию. По этой причине мы упростили опирации. Javascript запоминает все продукты что пользователь выбрал, и при клике на оформить заказ, отправляет на сервер по средствам Ajax.

Зачем перезагрузка при выборе адреса ?

Выбор адресса имел важную особенность в разработке данного сайта, ибо минимальная стоимость заказа, стоимость доставки зависили от адресса. При выборе адресса , имя и расстояния от ресторана( полученные через яндекс карты API ) сохранялись в localStorage, и при перезагрузке высчитывались. Исходя из расстояния применялись условия доставки и самой стоимости. Перезагрузка позволялась снизить нагрузка JS скриптов и вернуть некоторые переменные в исходное состояния

Если все хорошо работает зачем переписывать?

Были две основные причины почему проект был переписан на нужные технологии.

  1. Испробовать новые методы разработки
  2. Сложности при поддержки и масштабирование

Какие проблемы появились при масштабирования проекта

Любой проект должен расти и развиваться, если цикл сайта заказчивается при сдаче работы то сайт не сможет долгое время удерживать своих пользователей. Для реализации основных функции , нам хватило ресурсов WordPress . Но при внедрение новых , возникали проблемы и сложности.

Личный кабинет

Личный кабинет заложен в ядре WordPress и Woocommerc’а , но регистрация и авторизация на данном сайте , решили сдеать лишь по средствам мобильного телефона. Проблема в том что ядро движка сделано таким образом что регестрирует новых пользователей по Email. К тому же реализовать авторизацию по смс, сложнее чем по е-майлу, ибо требуется не только указать но и потвердить телефон. Если не соблюсти данную норму безопасности можно будет указать стороний номер на которые и будут приходить сообщения от ресторана.

Доствка по зонам + Яндекс Такси

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

Так же проблема была с Яндекс Такси. Ресторан захотел следующий функционал. Если адресс находиться не в пределах Москва Сити, сумма оплаты формируется из суммы продуктов + стоимость доставки которая запрашивается в настойщий момент времени, в Москве сильно вирируется стоимость в зависимости от времени суток.

Что мешало сделать интеграцию с данными сервисами, непосредственно на WordPress?

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

Поддержка проекта

Для внедрения новых функции требовалось “прицепить” к событиям Woocommerce, таким образом мы делаем наш функционал зависимым от хуков движка. При обновлении плагина и движка, старые функции могут быть заменены на новые, и соответственно наши зависимости перестанут работать.

К примеру мы могли переписать функцию регистрации и сделать объязательным не е-майл а телефон. Однако при обновлении WordPress, код мог перестать работать. Стоит помнить что сайт надо постоянно обвновлять , иначе могут появиться уязвимости.

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

Переписания проекта

Изначальное состояния

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

Для серверной части сайта( Back End ) мы использовали NodeJS и его фреймворк Express.js

Для хранения данных была использовано MongoDB, которая хранит данные в JSON формате.

Для создания пользовательского интерфейса был использован React, а для хранения состояние приложений – Redux

Вывод продуктов

Структура сайта предполагает следующий вывод. Цикличный вывод категорий и всех продуктов данной категории.
Изначално мы пошли неправильным путем и сделали следующий алгоритм вывода

  • Получения всех категории
  • При выводе категории отправляет POST запрос с ID категории для получения всех продуктов и их вывода

Данная концепция работала, однако количество запросов была равна количеством категории. Поэтому мы сразу переписали данный вариант и поступили следующим образом

  • Получения списка продуктов и списка категории
  • Вывод продуктов в нужной категории методом .filter()

Выбор адресса и странности Яндекса

Как и упоминалось ранее, большая проблема заключалось в том, что ранее мы проверяли зону доставки лишь по дистанции от ресторана. И за чего мы не могли покрыть всю зону Москвы ( до Мкада ) . В данной версии мы указали конкретные зоны доставки , методом нарисования объектов на яндекс карте, и экспортировали в JSON формате. Так же дописали JSON и указали для каждой зоны: минимальную стоимость доставки, время доставки, и координаты области. И за чего процесс поиска адресса происходит следующим образом

  • Пользователь выбирает адресс
  • Методом geoObjects мы получаем координаты пользователя
  • Методом searchContaining проверяем в какой зоне координаты
  • Получаем дополнительные данные, как время доставки и стоимость.
  • Через dispatch храним данные в Redux
  • Если координаты пользователя не попадают ни в какую зону, показываем попап что в данной зоны доставка не выполняется
  • Данные храним в localStorage, и при загрузке попадают в initialState

Какие проблемы были в реализации данного метода

В случае данной реализации пришлось потратить пару часов с проблемаи что случилось по вине Яндекса, скорее их неправильной документации

  1. Экспорт координат в обратной последовательности. К примеру если мы введем в Яндекс Картах запрос “Москва Сити” мы получим следующим координаты 55.749451, 37.542824. Все верно, и модуль поиска что мы установили на сайт такие же координаты выдает. Однако при экспорте зон доставок, координаты были написаны наооборот, т.е 37.542824, 55.749451. Как следствие координаты были введены правильные и поиск был по Москве, но зоны были установлены на границы Ирана. Простая ошибка, однако не было возможно заметить сразу. Зачем это было сделано не понятно.
  2. Установка яндекс карты и всех ее модулей на React в корне отличается от простой установки методом подключение script. Благо данный пакет хорошо поддерживается и на Github’e подсказали. Но к сожалению на некоторые вопросы нельзя был легко найти ответы. К примеру для того что бы поиск по зонам мог работать требуется использовать функцию searchContaining. Однако она не доступна пока не вызывается другая функция geoQuery(GeoCoordinates).addToMap(map.current), которая рисует зоны на карте. В моем случаем нарисовать зоны не требовалось ибо сама карта скрыта, но пока не будет использована функция geoQuery функция поиска не доступна. В программирование этот феномен называется Временна́я связность.

Яндекс такси

Как вы помните доставку мы осуществляли средством яндекс Такси. С реализации данного функционала были определенные сложности. Дело в том что если написать в Google Яндекс Такси API и перейти на страницу API яндекс Такси, вы увидите информацию что находится в бета тестирование и выдается лишь по заявкам. Мы оставили и заявку и пару сообщений но в течении 2 недель никто так и не ответил. Методом проб и ошибок, поиска и просмотр других примеров, удалось реализовать POST запрос к сервисам яндексам и получить нужную информацию. API ключ в данном случае не требуется.
Почему создатели данного API не написали о такой возможности не до конца понятно, однако данный функционал отлично работает.

Создания корзины

Честно сказать корзина была переписана с нуля около 3 раза. Предвидить все манипуляции изначально было сложно, поэтому когда код достигал отметки что “Это работает но я не понимаю как” – мы удаляли и переписывали заного, создавая прозрачную архитектуру для понимания.

Модуль корзины отвечает за множество перемен, такие как время доставки, применяется ли скидка, стоимость доставки, стоимости такси. Так же данный модуль отвечает за то какие данные попадут в оформление заказа. В данном случае все операции описаны по средствам Redux’a и легко меняются в зависимости от состояния приложения

Регистрация и авторизация по средствам телефона

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

Упрощенный процесс

  1. Пользователь указывает номер телефона
  2. Номер сессии + уникальный код доступа генерируется в базе данных. Так же код методом Stramedia API отправляется на номер телефона пользователя
  3. Пользователь вводит полученный код, и идет обращения в базу данных, где номер сессии и код должны совпадать.
  4. Если данный номер телефона не находится в базе , происходит процесс регистрации нового пользователя. Если же имеется – процесс авторизации

Процесс авторизации

  1. При регистрации/авторизации происходит процесс генерации JSON Web Token’a, который сохраняется в localStorage
  2. При повторном заходе на сайт, берется значения токена из хранилище
  3. Идет процесс проверки на валидность,
  4. При удачном ответе пользователь остается авторизированным

Данный подход считается одним из самых безопасных и используется на многих сайтов для процесса аутентификации

Оформление заказа

Финальное действие при заказе на сайте это оформление заказа. Здесь процесс прозрачно понятен однако и тут мы столкнулись с некоторыми проблемами. Как и в прошлом случае дело во внимательности, однако можно немного винить и сбербанк. Так как мы уже делали интеграцию с прошлой версии сайта, сбербанк выдает и тестовые доступы для тестирования платежей. Это очень удобно ведь позволяет тестировать процесс оплаты сколько угодно.

Если ввести в Яндекс либо Google запрос Сбербанк API, первым результатом выдается их старый сайт со старыми методами подключения. И за чего более 5 часов ушло на то что бы подключить новые доступы к старым API. Логично что ничего из этого не получилось . И лишь спустя пару часов, благодаря письмам от сбербанка где есть ссылки на новую документацю , удалось произвести интеграцию. Почему актуальную документацию так сложно найти – останется тайной.

В основном процесс формирование заказа состоит из несколько шагов

  1. Создания заказа как модель Order, со статусом оплаты 0 ( не оплачен )
  2. Перенаправления на страницу оплаты
  3. Оплата
  4. Перенаправления на страницу спасибо
  5. Смена статуса заказа на Оплачен

Интересные особенности на Node JS

Заказ может иметь 3 состояния

  1. Осуществляется доставка – саму доставку надо добавить в массив продуктов, дабы итоговая сумма и сумма всех продуктов совпадала. В инном случае ошибка
  2. Скидка если адресс доставки находится в Москва Сити. В данном случае мы заранее храним в состояния корзины цену продуктов со скидкой и без скидки, и в зависимости от статуса заказа( без скидки/ со скидкой) отправляем нужные цены.
  3. Обычная оплата

Экспорт товаров и импорт на новом сайте

Как вы помните , ресторан имеет более 200 позиции товаров, а если быть точными 230, разделенный на 29 категории. При создании прошлого сайта на WordPress мы наняли контент менеджера который вручную добавлял все товары на сайт. Я предположил что без особых проблем перенесем товары. Но добавив около 20 категории, я понял что это рутинная работа. Тогда я подумал почему бы не потратить время и придумать каким образом можно сделать экспорт со старого сайта и импортировать на новый. По стандарту функционал WordPress имеет возможность экспортировать товар. Он экспортирует в .CSV формате, однако он легко переводится в JSON. Сделав пару манипуляции, и заменив названия категории на ID категории что уже есть в базе, удалось сделать импорт. Самое интересное что Node сделал данную операцию за 1 секунду) в то время как я бы потратил более 5 часов для всех товаров. Автоматизация ключь ко всему

Процесс импорта очень прост

  1. Импортируем JSON по средствам require в NodeJS
  2. Проходимся методом map по массиву и делаем создание нового проукта при каждой итерации массива
  3. Продукты добавлены

P.S Стоит уточнить что запускать скрипто стоит лишь один раз, в противном случае есть шанс загрузить продукты 2 раза.

Финальные доработки и трудности

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

Оптимизация товаров на странице

При разработке сайта, для тестирования его функционала мы использовали в общей сложности 3 категории , и 12 товаров. Однако после успешного импорта на странице мы посмотрели что сайт стал работать не так быстро, и под слого быстро я имею ввиду не скорость загруки. Тут скорее вопрос Frame Rate и сама картинка стала не такой плавной. Что бы решить данную задачу давайте решим из чего состоит формируется скорость фреймрейта. Для этого мы сделали детальный аудит.

Загрузка товаров из базы данных

В нашем случае загрузка более 230 товаров не оказала большое влияение на саму работу сайту. MongoDB при хорошей оптимизации может выдавать в районе 100 000 товаров за секунду. Поэтому разницу между 12 и 230 не была видна. Как и показывает статистику, в средном время получения состовляет 11 мс( на бесплатной версии MongoDb).

Выполнение скриптов

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

Рендеринг страницы

А вот рендеринг можно и нужно оптимизировать. Сделал пару тестов , было заметно что показатели возросли в 3 раза при большом количестве продуктов. Это логично, но у нас относительно мало товаров, а проект требуется написать таким образом что бы он легко мог выдерживать нагрузку пару тыс товаров.
Исходя из вышеперечисленного мы решили сделать рендеринг продуктов по мере необходимости.

Как это работает?

  1. Идет вывод всех категории
  2. Вместо массива продуктов передается пустой
  3. При помощи метода Intersection Observer мы проверяем что если данная категория попала в зону видимости браузера, идет прорисовка продуктов

Оптимизация изображений

Изначально изображения ресторана имели разрешения 2к. Грузить такие изображения на сайте было бы сильно нагружаемо . Даже предварительно оптмизированная изображения в таком качестве имеет вес порядка 300 кб, а умножив данное значения на количество продуктов получаем немаленькую цифру.

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

Однако даже так на странице слишком много изображений, поэтому мы сделали загрузку фотографии методом lazy load. Если коротко он рабоатет в несколько шагов

  1. При первичной загрузке на странице фотографии нету, даже тега IMG нету
  2. Когда изображения попадает в зоне видимости, идет обращения к серверу ,и получается нужное изображения
  3. Заменяется preloader обычным изображением
  4. Используя хорошую базу данных , данные операции проходят моментально

Таким обрзом мы загружаем только нужный контент для пользователя. К примеру если он зашел на сайт, сделать заказ и выбрал продукты только из первой категории, он не получит информацию о других продуктах. Таким образом сохраняются ресурсы сервера + экономиться мобильный интернет пользователя. Насчет второго , в 2021 это не так важно, но если вы где то загородом и используйте 3G это очень ускорят работу.

Читать так же

19.01.2021
0
мин
Wiki
Контекстная реклама для начинающих

Если вы только начинаете делать свои первые шаги в Яндекс.Директе или Google Ads, welcome! В этой статье мы рассмотрим, что такое контекстная реклама и особенности ее работы, поговорим о ее специфике в[…]

SEO-продвижение медицинских сайтов
20.12.2020
0
14,6 мин
Wiki
SEO продвижение YMYL-сайты медицинской тематики

YMYL-сайты или Your Money or Your Life представляют собой особую категорию веб-ресурсов, содержание которых может оказать воздействие на здоровье пользователей и их финансовое состояние. Поэтому к ним и[…]

19.12.2020
0
5,9 мин
Wiki
Работают ли надо SEO

Определить, действительно ли агентство ведет активные работы над вашим сайтом еще до момента появления первых результатов и видимых изменений, можно довольно легко. Для этого следует только[…]

Самые частые вопросы по SEO
18.12.2020
0
10,7 мин
Wiki
10 самых распространенных вопросов о SEO

Каждый человек по-разному смотрит на процесс SEO-оптимизации. Клиенты часто не до конца понимают, как это происходит, какие меры нужно принять, чтобы повысить позиции сайта в поисковой выдаче, и[…]

5 шагов SERM
18.12.2020
0
6,5 мин
Wiki
5 шагов SERM для выстраивания идеальной репутации в интернете

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