Вступление: ловушка "Универсального языка"
Я в IT больше 10 лет. Начинал с верстки, вырос в фронтенд-разработчика, потом стал тимлидом, далее менеджером проектов и сейчас руковожу отделом разработки решений для бизнеса. Я знаю JavaScript. Я люблю JavaScript. И именно эта любовь сыграла со мной злую шутку.
В индустрии есть опасная иллюзия: «Если ты знаешь JavaScript, ты – фулстек». Node.js дал нам, фронтендерам, суперсилу. Мы можем писать серверный код на том же языке, что и клиентский. Мы можем использовать одни и те же типы, одни и те же библиотеки.
Нам кажется, что разница между фронтендом и бэкендом — только в том, где выполняется код.
В браузере нет fs (файловой системы), а на сервере нет window. Вот и вся разница, да?
Нет. Разница не в синтаксисе. Разница в . И я узнал это самым тяжелым способом – положив релиз.
"Я сам всё сделаю"
В то время я работал менеджером проектов в штате застройщика. Это было самое начало моего управленческого пути. Я допустил ошибку и не успел вовремя найти бэкенд-разработчика, а сроки запуска проекта внезапно сократили.
И тут во мне проснулся герой:
— «Да что там писать? Это же просто API! У меня 5 лет опыта в JS. Я сам всё сделаю!»
Стек я выбрал не сразу. Сначала смотрел в сторону облачных Headless CMS вроде Prismic.io или Contentful. Но это не покрывало все задачи, даже если часть логики и контента оставить жить на клиенте.
Мне нужно было решить бизнес-задачу: дать маркетингу гибкий инструмент, причем быстро.
И тут я наткнулся на Strapi.
Опенсорс, Node.js, ставится на свой сервер, полностью настраивается через админку. Это была любовь с первого взгляда.
Я никогда раньше с ним не работал, но документация обещала, что всё будет легко.
В итоге стек выглядел так:
-
Бэкенд: Strapi (Headless CMS).
-
Фронтенд: Nuxt.js (Vue).
-
База: MongoDB. Причина выбора была до смешного банальной: я просто не смог за час поднять MySQL локально. Моя самоуверенность («да что там эти базы») разбилась о первый же конфиг, а Mongo заработала из коробки без лишних вопросов.
Архитектура "конструктора"
Как мыслит фронтендер? Он мыслит Компонентами. У нас есть шапка, слайдер, блок "Преимущества", форма заявки, футер.
Я решил, что бэкенд должен быть зеркалом фронтенда. Я хотел сделать идеально для маркетологов. Я решил сделать Конструктор.
В Strapi я создал эндпоинты для отдельных сущностей. А внутри них – «Динамические зоны» (Dynamic Zones). Логика была такая:
-
Маркетолог заходит в админку.
-
Выбирает сущность, например, жилой комплекс.
-
Накидывает блоки: «Хочу здесь слайдер», «Хочу здесь текст», «Хочу здесь форму».
-
Заполняет поля (картинки, заголовки).
Фронтенд (Nuxt) получал этот JSON, проходился по массиву компонентов и рендерил их, примерно так:
<component
v-for="block in page.blocks"
:is="block.componentName"
:data="block.data"
/>
Это казалось гениальным. Полная гибкость! Маркетолог счастлив, он может собрать посадочную страницу без программиста. Фронтенд счастлив, компоненты переиспользуются.
Локально, на моем макбуке с боевыми данными, всё летало.
Холодный душ
День релиза. Сайт уже был наполнен контентом под завязку: сотни квартир, десятки планировок, рендеры, акции и новости. Мы запустили сайт. Пошли реальные пользователи.
Это не был Highload в понимании Яндекса. У нас было порядка 30–50 уникальных посетителей на сайте одновременно — нормальная цифра для регионального застройщика. Но этого хватило. Открываю главную страницу.
Жду. Секунда. Две. Три. ... Шесть секунд.
Белый экран. Браузер крутит спиннер. Эти шесть секунд показались мне вечностью. Я в ужасе представлял, как генеральный директор открывает сайт с телефона в лифте, где интернет ловит через раз, и видит эту бесконечную пустоту.
Потом контент резко вываливается.
Я в панике открываю DevTools. Время ответа сервера (TTFB) — 6.8 секунды. Почти 7 секунд сервер просто «думал», прежде чем отдать хоть байт данных. Для E-commerce и сайтов недвижимости это смерть. Клиент уйдет к конкуренту ещё на третьей секунде.
Что пошло не так?
Я полез в логи и профилировщик базы данных. То, что я увидел, заставило меня поседеть.
Мой «гениальный конструктор» породил монстра. Чтобы собрать одну страницу, Strapi должен был вытащить запись сущности, а потом через populate (аналог JOIN в SQL) подтянуть данные для всех 20 блоков. А некоторые блоки были сложные.
-
Блок «Похожие квартиры» ссылался на сущность
Apartment. -
Сущность
Apartmentссылалась наBuilding. -
Buildingссылался наDistrict.
MongoDB — это документоориентированная база. Она не любит джойны. А я заставил её делать рекурсивные джойны вложенностью в 5-6 уровней для каждого запроса.
Я спроектировал базу данных так, как проектируют React/Vue компоненты — вложенными деревьями. Но база данных — это не DOM-дерево.
Мои ошибки:
-
Отсутствие нормализации. Я хранил всё в одной куче.
-
Over-fetching. Я тянул всю базу, чтобы показать заголовок.
-
Непонимание стоимости операций. Для меня
db.find()было магией. Я не думал, как именно база ищет эти данные на диске.
Синяя изолента (Redis)
Переписывать архитектуру было поздно. Срочно нужен был рабочий сайт. Я искал решение «здесь и сейчас» и вспомнил про кэширование. Если база тормозит — не трогай базу, отдавай готовое.
Я поставил Redis. Нашел плагин для Strapi (он был сырой, мне даже пришлось отправить Pull Request с фиксом, чтобы он заработал полноценно).
Логика:
-
Первый юзер заходит — ждет 5 секунд (страдалец).
-
Мы сохраняем готовый JSON в оперативную память (Redis).
-
Второй юзер заходит — получает ответ за 50 миллисекунд.
Да, первый пользователь после каждого деплоя или обновления контента всё ещё страдал. Кэш нужно было прогревать. Но это было меньшее из зол, и бизнес это устроило.
Это спасло проект. Отдел маркетинга успокоился, сайт «летал» (для 99% пользователей). Но я знал правду. Под капотом этого «Феррари» стоял двигатель от мопеда, перемотанный синей изолентой.
Фронтенд ≠ Бэкенд
Этот опыт научил меня большему, чем все курсы и книги до этого.
Я понял, почему на собеседованиях сеньор-бэкендеров гоняют по «скучной теории»: нормальные формы БД, индексы, транзакции, CAP-теорема. Раньше я думал: «Зачем это? Мы же просто JSON перекладываем!».
Я ошибался.
1. Ментальная модель фронтенда — это "Состояние и Реактивность". Ты думаешь о том, как интерфейс реагирует на действия юзера. Компоненты, пропсы, ивенты.
2. Ментальная модель бэкенда — это "Данные и Надежность". Ты думаешь о том, как эффективно хранить данные, как их быстро достать, и что будет, если одновременно придут 1000 человек.
Знание синтаксиса async/await не дает тебе этой ментальной модели. Ты можешь выучить ноты, но это не сделает тебя композитором.
Эпилог
Node.js дал фронтендерам возможность заходить на территорию бэкенда. Но он не выдал нам визу на пмж.
Мы привыкли строить красивые фасады и декорации, которые можно легко перекрасить. Но бэкенд — это фундамент. Если залить его неправильно, перекрасить не получится — придется сносить дом.
И иногда, чтобы стать настоящим фулстеком, нужно перестать думать, как декоратор, и начать думать, как инженер.
P.S. Если эта история отозвалась болью – добро пожаловать в комментарии. Расскажите, как вы впервые положили прод. Это сближает.
Автор: MrTheFirst
