Strona główna / Artykuły

Reagowanie bez meta-frameworka: ukryte koszty backendu w Pythonie

Dlaczego użycie Reacta w połączeniu z Vite na backendzie nieopartym na JavaScriptze omija dyskusję o Next.js, jakie funkcje innych frameworków musisz odtworzyć i jak zarządzać połączeniami między nimi.

Wydawca: REACTAPP.TOP7 min czytania1689 słów

Źródła: Everyone’s Leaving Next.js. I Never Showed Up.

Większość debat na temat frameworków React zakłada istnienie tylko dwóch opcji: pozostanie przy Next.js lub przejście na to, co obecnie promuje się jako jego następca – czy to Astro, Remix, czy TanStack Start. Duży odsetek rzeczywistych aplikacji React nie obiera żadnej z tych dróg. Tworzą one jednostronicowy klient renderowany za pomocą Vite i komunikują się z backendem napisanym w Pythonie, Ruby lub PHP, który już zarządza bazą danych, regułami biznesowymi oraz API. Ten artykuł szczerze przygląda się tej trzeciej opcji: jakie zarzuty wobec meta-frameworków po prostu nie mają zastosowania, które obowiązki cicho przenoszą się do twojego własnego kodu oraz jak utrzymać stworzony w ten sposób framework w dobrym stanie.

Konkretne przykład trzeciej opcji

Załóżmy system zdrowia publicznego, który monitoruje wzbogacanie żywności. Warstwą frontendową jest React z TypeScript, a biblioteką komponentów Ant Design. Stroną serwerową jest Frappe – framework w języku Python, który napędza ERPNext. Frappe definiuje modele danych jako „doctype’y”, zarządza bazą danych i udostępnia wybrane metody w języku Python jako punkty końcowe API. W środowisku produkcyjnym nie ma środowiska Node.js, routera aplikacji ani komponentów serwerowych decydujących o tym, które części interfejsu mogą uzyskać dostęp do bazy danych. React rysuje ekran; Frappe zajmuje się wszystkim innym.

Dla zespołu w takiej sytuacji wiele najgłośniejszych zarzutów przeciwko Next.js jest bez znaczenia. Utrzymanie aplikacji wyłącznie na Vercel nie ma znaczenia, gdy nic tam nie jest rozpakowywane. Powolna wymiana modułów w ramach routera aplikacji nie stanowi problemu, gdy Vite uruchamia serwer rozwojowy i ładowa aplikację na tyle szybko, że nikt o tym nie myśli.

Ta sama architektura jest stosowana zawsze wtedy, gdy React jest łączony z Django, Rails, Laravel lub jakimkolwiek innym stackiem, który już posiada własną strukturę po stronie serwera. Jednakże pozostałe zarzuty dotyczące meta-frameworków nadal się pojawiają – po prostu przychodzą z innej strony.

Nadal budujesz framework, jeden narzędzie po drugim

Pominięcie meta-frameworka nie eliminuje problemów, które rozwiązuje. Przenosi je do twojego kodu, gdzie musisz je sam rozwiązać, zazwyczaj stopniowo i bez określenia nazwy tego, co tworzysz. Trzy przykłady z aplikacji fortification ilustrują ten wzorzec.

Formularze oparte na schematach zamiast konwencji frameworka

Zespół stworzył komponent DynamicForm, który renderuje formularze bezpośrednio na podstawie definicji doctype w formacie JSON dostarczanych przez serwer. Komponent ten odczytuje schemat, określa, jakie pola istnieją i jaki mają typ, oznacza pola obowiązkowe, obsługuje pola, których widoczność lub wartość zależy od innego pola, oraz renderuje pełny formularz. Dodawanie nowego doctype nie wymaga już ręcznego pisania formularza.

To jest zasada konwencji nad konfiguracją – podstawowa obietnica każdego meta-frameworka, zrealizowana wewnętrznie. Również nie udało się to od razu: komponent przeszedł przez trzy pełne przeredagowania, zanim stał się stabilny. Framework załatwiłby te iteracje za Ciebie, wśród tysięcy użytkowników.

Ładowanie paginowane bez warstwy danych

Raport wskazuje, że konieczne jest ładowanie wierszy partiami w miarę przewijania przez użytkownika. Bez getServerSideProps, narzędzia do ładowania tras lub jakichkolwiek innych dostępnych w frameworku metod pobierania danych, rozwiązanie musiało zostać skonstruowane z poszczególnych elementów: IntersectionObserver obserwującego element znajdujący się na dole listy, ręcznie śledzonego kursora strony oraz stanu ładowania, którego zachowanie trzeba było zaprojektować od zera. Czy na dole powinien pojawić się ikonka spinowania, czy nowe wiersze po prostu powinny się pojawiać stopniowo? Bez żadnych wskazówek dostarczanych przez framework, jeden programista musi podjąć tę decyzję w trakcie zwykłego dnia pracy.

W środowisku React-plus-Vite biblioteka taka jak TanStack Query może zapewnić wiele z tych funkcji (zapytania nieskończone, wskaźniki pozycji kursora, flagi ładowania) i warto ją rozważyć przed pisaniem własnej logiki. Zasadniczy problem pozostaje niezmienny: ktoś musi wybrać i przejąć odpowiedzialność za warstwę danych.

Warstwa adaptera dla niejednolitych danych z backendu

Spadające listy w około tuzinie typów dokumentów musiały wyświetlać etykiety czytelne dla człowieka, ale każdy typ dokumentu nazywał swoje pole etykiety inaczej. Niektóre używały label, inne title, a jeden starszy typ dokumentów używał display_name. Mała funkcja pomocnicza mapOptions normalizuje je wszystkie do jednego formatu, dzięki czemu każdy komponent spadającej listy może zignorować tę niejednolitość.

Nikt nie nazwałby tego architekturą, a jednak to dokładnie ten rodzaj normalizacji, który w przeciwnym razie narzuciłaby konwencja frameworka. Każdy zespół ostatecznie pisze te elementy nośne, które zapewniłby meta-framework. Jedynym wyborem jest to, czy zapłacisz za to, akceptując opinie frameworka, czy wydając swój własny czas inżynierski.

Gdy framework stworzony we własnym zakresie staje się problemem

Prawdziwy koszt ujawnia się później. Po kilku latach komponent DynamicForm został zauważony przez SonarQube ze względu na nadmierną złożoność poznawczą. Nie była to błąd biblioteki ani niezgodność wersji – był to zwykły rozwój komponentu: jeden plik w milczeniu stał się jednocześnie warstwą routingu, warstwą formularzy oraz warstwą walidacji aplikacji.

Refaktoryzacja ujawniła, jak skomplikowany stał się ten kod. Wyodrębnienie logiki walidacji do osobnej funkcji odkryło cztery niepowiązane ze sobą ścieżki kodu, które polegały na efektach ubocznych ukrytych wewnątrz komponentu. To właśnie jest ta „podatek”, którego rzadko widzi się na fakturze, gdy pomija się meta-framework. Sami tworzysz infrastrukturę i jednocześnie stajesz się jej jedyną dokumentacją. Nie ma popularnego narzędzia do śledzenia problemów, w którym ktoś inny napotkałby tę samą trudność i opublikował rozwiązanie.

Rozwiązanie opierało się na znajomej metodzie dekompozycji:

  • Przeniesienie rozwiązywania zależności pól do dedykowanego, dostosowanego hooka.

Po podziale wynik wskaźnika złożoności spadł, a komponent stał się łatwiejszy do odczytania. Szersza lekcja polega na tym, że rozdzielenie zadań, które meta-framework narzuca ze względu na konwencję, w niestandardowej architekturze musi być realizowane z dyscypliną za każdym razem. Żaden narzędzie do sprawdzania kodu nie ostrzeże cię, że źle budujesz warstwę danych frameworka. Przydatnymi nawykami są okresowe raporty o złożoności, przeglądy kodu pytające „czy ten komponent zamienia się w infrastrukturę?”, oraz krótka dokumentacja wewnętrzna dla każdego narzędzia, od którego zależy więcej niż kilka ekranów.

Błędy istniejące w styku dwóch ekosystemów

Framework taki jak Next.js obejmuje zarówno stronę serwera, jak i klienta, więc granica między nimi jest projektowana jako jeden system. Gdy React działa z backendem w Pythonie, tę granicę musisz sam określić, co prowadzi do zupełnie innej kategorii błędów.

Jednym z przykładów jest błąd MySQL 1292, którego rozwiązanie zajęło prawie cały dzień. Błąd ten wskazuje na nieważną lub błędnie sformatowaną wartość daty lub czasu. Formularz React wysyłał dane w jednym formacie, podczas gdy ORM Frappe oczekiwał innego. Ani frontend, ani backend sam w sobie nie popełniły błędu; problem wynikał z różnic w formacie danych pomiędzy dwoma ekosystemami, które nigdy nie zostały zaprojektowane razem, połączonych jedynie konwencjami stworzonymi miesiące wcześniej i częściowo zapomnianymi.

Kolejnym przykładem jest niespodziewane uruchomienie narzędzia weryfikującego moc hasła w hooku after_insert backendu, spowodowane formatem danych wysłanych przez formularz, który nikt nie przetestował. Żaden changelog frameworka nie opisuje takiej interakcji, ponieważ żaden meta-framework nie wie o istnieniu Frappe. Debugowanie polega na analizowaniu zarówno własnego kodu, jak i źródeł frameworka backendowego, aż obie strony historii będą zgodne.

Kilka praktycznych metod obrony przed błędami związanymi z formularzami:

  • Serializuj daty i czasy w dokładnie jednym miejscu po stronie klienta, w formacie określonym przez serwer, a nie dla każdego formularza osobno.
  • Traktuj treść żądania jak umowę: zapisz ją w TypeScript i, jeśli to możliwe, waliduj na obu stronach na granicy przekazywania danych.
  • Napisz testy integracyjne, które wysyłają rzeczywiste dane formularza do prawdziwego serwera, włączając te o nietypowej strukturze, ponieważ mechanizmy po stronie serwera reagują na dane, których się nie spodziewasz.
  • Zapisuj surową treść żądania w przypadku niepowodzeń walidacji po stronie serwera, aby rozbieżności były natychmiast widoczne.

Jeśli twoje tło serwerowe również jest napisane w JavaScript, wspólne schematy walidacji eliminują dużą część tych trudności; zobacz dzielone schemat Zod pomiędzy frontendem React a backendem Node. W przypadku tła serwerowego w Pythonie nie można dzielić kodu, ale nadal można dzielić umowę.

Prawidłowe sformułowanie decyzji

Dyskusja na temat „którego meta-frameworka” zakłada, że tło serwerowe podlega negocjacjom. Dla wielu zespołów tak nie jest. Jeśli istniejące aplikacje Django, Rails, Laravel lub Frappe już kontrolują serwer, React w połączeniu z Vite nie stanowi kompromisu – to naturalna architektura. Prawdziwym pytaniem staje się to, jak dużą część zadań związanych z frameworkiem jesteś gotów samodzielnie opracować i utrzymywać na istniejącym już tle serwerowym.

W zamian możesz wybrać backend, który pasuje do danego domeny. Dla systemu opartego na doctype’ach i hookach w Pythonie ta swoboda jest cenna. Cena to konieczność radzenia sobie ze wszystkimi problemami, które wynikają z takiego wyboru.

Główne wnioski

  • Argumenty dotyczące przywiązania do Vercel i migracji routera aplikacji są prawdziwe, ale nie odnoszą się do klientów React obsługiwanych przez backend inny niż JavaScript.
  • Nawet bez meta-frameworka nadal musisz tworzyć warstwy do budowania form, ładowania danych i ich normalizacji; po prostu robisz to sam i pozostajesz ich właścicielem na stałe.
  • Infrastruktura stworzona we własnym zakresie prowadzi do skomplikowanych komponentów; celowo zapewnij oddzielenie zadań i udokumentuj wspólne narzędzia.
  • Granica między klientem a serwerem to twoja odpowiedzialność: scentralizuj proces serializacji, zdefiniuj kontrakt danych i testuj go na rzeczywistym backendzie.
  • Ani jedna z tych ścieżek nie jest prostsza. Zastępują one opinie dotyczące frameworków czasem poświęconym na pracę inżynierską, a właściwy wybór zależy od tego, któremu backendowi faktycznie się przywiązujesz.
  • Literatura pokrewna