Головна / Статті

Реагування без мета-фреймворку: приховані витрати на Python-бекенд

Чому використання React разом із Vite на не-JavaScript-бекенді уникає дискусій про Next.js, які функції фреймворку доводиться перебудовувати, та як керувати з’єднаннями між компонентами.

Видавець: REACTAPP.TOP7 хв читання1689 слів

Джерела: Everyone’s Leaving Next.js. I Never Showed Up.

Більшість дискусій про фреймворки React передбачають лише два варіанти: залишитися з Next.js або перейти на те, що наразі просувається як його наступник — чи то Astro, Remix чи TanStack Start. Велика кількість реальних React-додатків не обирає жодного з цих шляхів. Вони створюють односторінковий клієнт за допомогою Vite та взаємодіють із бекендом, написаним на Python, Ruby чи PHP, який вже містить базу даних, бізнес-правила та API. У цій статті чесно розглядається цей третій варіант: які скарги на мета-фреймворки просто не стосуються його, які обов’язки тихо переходять у ваш власний код та як підтримувати створений таким чином фреймворк у належному стані.

Конкретний приклад третього варіанту

Та сама архітектура застосовується щоразу, коли React інтегрується з Django, Rails, Laravel чи будь-якою іншою платформою, яка вже має власну серверну структуру. Проте залишаються скарги на мета-фреймворки; вони просто з’являються з іншого боку.

Ви все одно створюєте фреймворк, по одній утиліті за раз

Пропуск мета-фреймворка не усуває проблем, які він вирішує. Він лише переміщує їх у ваш кодовий базис, де ви самі їх розв’язуєте, зазвичай поступово та без чіткого найменування того, що ви створюєте. Три приклади з додатку fortification ілюструють цю схему.

Форми, засновані на схемах, замість конвенцій фреймворку

Команда створила компонент DynamicForm, який відображає форми безпосередньо з визначень doctype у форматі JSON, що надсилаються сервером. Він читає схему, визначає, які поля існують та який у них тип, позначає обов’язкові поля, обробляє поля, видимість чи значення яких залежить від іншого поля, та відображає повну форму. Для додавання нового doctype більше не потрібно вручну писати форму.

Це і є принцип „звичаю замість налаштувань“ — основна обіцянка кожної мета-фреймворк, реалізована власними силами. Це також не спрацювало з першої спроби: компонент пройшов три повні переписування, перш ніж став стабільним. Фреймворк би взяв на себе ці ітерації замість вас серед тисяч користувачів.

Пагінація завантаження без шару даних

У звіті йшлося про необхідність завантажувати рядки пакетами під час прокручування користувачем. Без getServerSideProps, засобу для завантаження маршруту чи будь-якого іншого інструменту отримання даних, схваленого фреймворком, рішення довелося створювати з окремих елементів: IntersectionObserver, який стежив за елементом біля кінця списку, вручну відстежуваний курсор сторінки та стан завантаження, поведінку якого довелося розробляти з нуля. Чи має з’явитися індикатор завантаження внизу, чи нові рядки просто мають з’являтися поступово? Не маючи підтримки фреймворку, один розробник ухвалює це рішення посеред звичайного робочого дня.

У середовищі React-plus-Vite бібліотека на кшталт TanStack Query може забезпечити більшу частину цих функцій (нескінченні запити, курсори, позначки завантаження), і варто її розглянути перед тим, як писати логіку самостійно. Основна ідея залишається незмінною: хтось мусить обрати та взяти на себе відповідальність за шар даних.

Шар адаптера для неоднорідних даних сервера

У списках вибору для приблизно десяти типів документів потрібно було відображати мітки, зрозумілі людині, проте кожен тип документа називав своє поле мітки по-різному. Деякі використовували label, інші — title, а один застарілий тип документів — display_name. Невелика утиліта mapOptions нормалізує їх усі до однакового формату, щоб кожен компонент списку вибору міг ігнорувати цю неоднорідність.

Ніхто б не назвав це архітектурою, проте саме така нормалізація і є тим, що зазвичай нав’язує конвенція фреймворку. Кожна команда зрештою пише ту основу, яку надав би мета-фреймворк. Єдиний вибір — чи платити за це, приймаючи погляди фреймворку, чи витрачати власний інженерний час.

Коли саморозроблений фреймворк стає проблемою

Справжня вартість проявляється пізніше. Через кілька років компонент DynamicForm був виявлений SonarQube як занадто складний для розуміння. Це не була помилка бібліотеки чи несумісність версій. Це було звичайне зростання компонента: один файл мовчки став водночас шаром маршрутизації, шаром форм та шаром перевірки даних у додатку.

Рефакторинг показав, наскільки заплутаним стало кодове середовище. Вилучення логіки перевірки у окрему функцію виявило чотири незалежні шляхи виконання коду, які залежали від побічних ефектів, прихованих усередині компонента. Це та „податкова ноша“, яку майже не бачиш у рахунку-фактурі, коли ігноруєш мета-фреймворк. Ви створюєте інфраструктуру, а водночас стаєте її єдиною документацією. Не існує популярних систем відстеження проблем, де хтось інший би зіткнувся з тією самою проблемою та опублікував її рішення.

Рішення було засноване на знайомому підході до розбирання коду:

  • Перенести обробку залежностей між полями у спеціальний кастомний хук.
  • Розмістити логіку перевірки у окремому модулі, який не має жодного уявлення про те, як відображається форма.
  • Залишити сам компонент відповідальним лише за її відображення.

Після розділення скоринг складності знизився, і компонент став легшим для читання. Загальний урок полягає у тому, що розділення обов’язків, яке мета-фреймворк забезпечує за замовчуванням, у користувацькій стеку має забезпечуватися дисципліною щоразу. Жоден інструмент для перевірки коду не попередить вас про те, що ви неправильно будуєте шар даних фреймворку. Корисними звичками тут є періодичні звіти про складність, перегляд коду з питанням «Чи перетворюється цей компонент на інфраструктуру?», а також коротка внутрішня документація для будь-якого інструменту, від якого залежить більше кількох екранів.

Баги, які існують у з’єднанні двох екосистем

Фреймворк на кшталт Next.js контролює як сервер, так і клієнт, тому межа між ними проектується як єдина система. Коли React працює з Python-бекендом, цю межу доводиться визначати самостійно, і це призводить до окремого класу багів.

Один приклад: помилка MySQL 1292, виявлення причини якої зайняло майже цілий день. Ця помилка вказує на недійсне або неправильно сформатоване значення дати чи часу. Форма у React надсилала дати в одному форматі, тоді як ORM Frappe очікував інший. Ні фронтенд, ні бекенд самі по собі не мали помилок; проблема виникла через розбіжності у форматуванні даних між двома екосистемами, які ніколи не розроблялися разом та були поєднані лише за допомогою конвенцій, винайдених кілька місяців раніше та частково забутих.

Ще один приклад: несподівана робота валідатора міцності пароля у бекенді під час виконання хука after_insert, спричинена формою даних, яку надсилала форма та яку ніхто не тестував. Жоден чарт змін фреймворку не описує такої взаємодії, адже жоден мета-фреймворк не знає про існування Frappe. Для відлагодження необхідно читати як власний код, так і вихідні коди бекенд-фреймворку, поки обидві сторони ситуації не дадуть згоди.

Деякі практичні заходи захисту від проблем із формами:

  • Серіалізуйте дати та часові моменти лише в одному місці на клієнті, у форматі, визначеному серверною частиною, а не для кожної форми окремо.
  • Розглядайте вміст запиту як контракт: оформіть його у TypeScript та, за можливості, перевіряйте на обох кінцях межі взаємодії.
  • Створюйте тести інтеграції, які надсилають справжній вміст форм до справжнього сервера, включаючи незвичайні формати, адже хуки на серверній стороні реагують на дані, яких ви можете не очікувати.
  • Фіксуйте необроблений вміст запиту у разі невдачі перевірки на сервері, щоб розбіжності ставали видимими негайно.

Якщо ваш бекенд також написаний на JavaScript, спільна схема валідації значно зменшує кількість проблем у цьому процесі; дивіться способи спільного використання однієї схеми Zod між фронтендом на React та бекендом на Node. З бекендом на Python код не можна поділитися, але контракт все одно можна поширювати.

Правильне формулювання рішення

Дискусії про „який метафреймворк“ передбачають, що бекенд можна переговорювати. Для багатьох команд це не так. Якщо існуючий додаток на Django, Rails, Laravel чи Frappe вже контролює сервер, то використання React разом із Vite — це не компроміс, а природна архітектура. Справжнє питання полягає у тому, скільки функцій фреймворку ви готові самостійно реалізувати та підтримувати над вже існуючим бекендом.

У відповідь ви можете обрати бекенд, який підходить до вашої домени. Для системи, побудованої на doctypes та Python hooks, така свобода є цінною. Ціною є необхідність брати на себе всі наслідки, які виникають внаслідок такого вибору.

Ключові висновки

  • Аргументи щодо прив’язки до Vercel та міграції app router є реальними, але вони не стосуються клієнтських частин React, які обслуговуються не-JavaScript бекендом.
  • Навіть без мета-фреймворку ви все одно створюєте шари для форм, завантаження даних та їх нормалізації; ви просто робите це самостійно та залишаєте їх у своєму володінні на невизначений час.
  • Інфраструктура, створена власноруч, поступово перетворюється на складні компоненти; свідомо дотримуйтесь принципу розділення обов’язків та документуйте спільні інструменти.
  • Межа між клієнтом та сервером — це ваша відповідальність: централізуйте процес серіалізації, визначте формат даних та тестуйте їх на справжньому бекенді.
  • Жоден з цих підходів не є простішим. Вони обмінюються думками щодо фреймворку на час розробки, і правильний вибір залежить від того, якому бекенду ви насправді присвячуєтеся.