Галоўная / Артыкулы

Ператворэнне працоўнікаў маршрутаў Next.js у спецыяльны шар BFF

Дазвольце дазнацца, што рашыяе проблему патэрну Backend for Frontend, чаму ён з’яўляецца знова у аплікацыях Next.js, і як утримацца ад таго, каб прыемнікі маршрутаў сталі вялікімі, надмоцнымі об’ектамі.

6 хв чытання1435 слоў

Адміністратар аплікацыі з команды, якая працуе над мобільнымі прыстроямі, калі-то запытаўся, чаму для адобразавання экрана пракупкі трэба было здзейсніць шасць окольных вызоў API, пры чым тры з іх вярталі перакрытыя поля, якія былі запрашаны з той самай базовай службы. Чыстая адпаведзь была такой: ніхто не контролюўаў тое, канкрэтна патрабуе фронтэнд, таму кожная команда выклікала сваю версію рашэння і заставляла кліента ўсё гэта склеіць самога. Гэта насправды не тэхнічная проблема, а організацыйная, і самэ гэта ёсьць тое, на што была спроектавана модэль Backend for Frontend. Оерагвазды самэ гэта ёсьць прычыной, чаму гэтая модэль знова з’явілася ў калькі працэў недзе праз нешта часоў, пасля таго як працэўнікі Netflix і SoundCloud прыблізна дзесяць гадоў таму пісалі пра яе як пра ідэю, якая здзейчылася забутая.

BFF зовсім не ўжо новая ідея. Іншыя інжынеры вже стваралі такі рашэння пад іншымі назвамі ўчора, перш чым „BFF“ стала стандартным тэрміном. Новым є тое, што гэта панявленне знова вводзіцца у спецыфічны контекст аплікацый Next.js, якія поступова ператварыліся на шары агрэгаціі API, часта без якога-лібо намеру з боку разработчыкаў. У светле гэтага трэнду цікава намерова застосоўваць гэты падход, а не проста випадкова на ўсё гэта натрапіць.

Форма проблемы

Гэты сценарый продовжвае павтарацца ў адносна разных формах. У вас є калькі служб бэкенду: дзеяныя внутршняя, а іншыя — API-і вантажоўцоў, якія дадаўшыся пазней. Таксама у вас є калькі кліентав: веб-дзяловая прыемлівка, мобільная прыемлівка, можа ўсероўнішчы каледж адарбавання. Кожны з гэтых кліентав мае разныя трэбаванні. Мобільны кліент хочаць лёгкі пэйлоад, адтолькі ён часта працуе на ненадзейным сеті. Веб-дашборд хочаць абсалютна дэнормалізаваны адказ з усім, што ўжо з’еднанае, каб утрымацца ад ланцоўка параднага запытавання. Каледж адарбавання хочаць сырыя, непрактыкованыя данні, адтолькі людзі, якія імі корыстуюцца, зазвычай вылучаюць проблемы.

Якщо прыняць рашчынанне без викорыстоўвання шара BFF, будуць толькі два паследкі, і якія ўсе адны разу паслядні. Або вашы основныя сервісы стануць занадта великімі, універсальнымі канцэнтрамі дадзеных, якія намагаюцца задовольніць усіх можлівых корыстуначаў адразу, што фактычна прыводзіць да ситуацыі, калі патрэбна проста дадаць ўсё новы неабавязковы параметр запиту, або кожны кліент вынужаны пераўтвараць свою сэрберную логіку аграгавання на базе тых сабе задных сервісаў. Так і выходзіць, што тры адны разу незначна разныя і часткова некоректныя вакларызацыі панямі «актыўны корыстунач» распадзяюцца між трыма разнымі кодавымі базамі.

Што на самай працэ ёсць BFF

Бэкенд для фронтэнда — это лёгкія серверныя слой, як правила, аднолькі на кожны тип кліента чыста каліграфія кліентскай групы, які знаходзится межа інтэрфейсам і справжнімі бэкенд-сервісамі. Яго ўзгалі едынственная задача — адформаваць даныя для конкрэтнага корыстувальніка, якому ён служыць. BFF для веб-працоўкі і BFF для мобільных працоўак можа вызываць ідэнтычныя базовыя сервісы, але вяртаць абсалютна разныя форматы адпаведзей, таму што кожны з іх рашае аднолькі іншую проблему прадставлення даных.

Гэта ідея часта ў размовах пра Next.js, таму што App Router практычна безкоштовна дае вам шар BFF, якщо толькі вы яго так і выкарыстоўваеце. Обробнікі маршрутаў і серверныя компоненты вже выкананы на сервере, вони вучора між кліентам і бэкендам, і ўжо маюць можлівасць адправляць кальколькі паралельных запыткаў і пераформатаваць сумарны рэзультат прычаму ён нават не дасягне браузера. Багато команд, якіе перайшлі на App Router, вучора выкааняюць задачі у стыле BFF, не называючы іх формальна чы не застосоўваючы їх адносова стабільна.

// app/api/checkout-summary/route.ts
// A web-specific BFF endpoint that aggregates three backend calls
// into the one shape the checkout screen actually needs
export async function GET(req: Request) {
  const userId = getUserIdFromSession(req);  const [cart, pricing, shippingOptions] = await Promise.all([
    fetch(`${CART_SERVICE}/users/${userId}/cart`).then(r => r.json()),
    fetch(`${PRICING_SERVICE}/quote?userId=${userId}`).then(r => r.json()),
    fetch(`${SHIPPING_SERVICE}/options?userId=${userId}`).then(r => r.json()),
  ]);  // Reshape into exactly what the checkout component renders,
  // nothing more. This is the whole point.
  return Response.json({
    items: cart.items.map((i: CartItem) => ({ id: i.id, name: i.name, qty: i.qty })),
    total: pricing.total,
    currency: pricing.currency,
    shipping: shippingOptions.filter((o: ShippingOption) => o.available),
  });
}

Тепер уявіце, калі мобільны даплык замест таго безпасовае безпосередна да гэтых трох сэрвісаў і сам стараецца аграгаваць данні на языку Swift чытае Kotlin, самастоятельна рэалізуючы логіку таго, што ляжыць у межах доступных вароў доставкі. Самэ так і пачынаецца адхіленне. Две каманды, два языкі, два розныя тлумачэння адной і той жа бізнес-правілы, і раней чы пазней гэтыя тлумачэння стаюць несувязнымі, пры чым ніхто гэтага не з’являе, пакуль на стол каму-небудзь не прыйдзе заява пра падтрымку.

Дзе справа становіцца політычной, а не толькі тэхнічной

Складна частка правильнага адаптавання BFF малая стосунакоў да напісвання коду. Йдзе пра тое, хто яго керуе. BFF, створаны толькі для веб-команды, будзе працаваць толькі тады, калі адповядае за ўсё той, хто мае права на шырокія змены, ідеальна — сама веб-команда, а не група на спільнай платформе, прыкрепленая да двахтыжневага графіка выпуску. Якщо шар BFF атрымуе керованне ад окремай бэкенд-команды, якая ставіцца да ўсьго гэтага як да ўзвышанага мікросервісу, які павінен працаваць за тым самым процесам затверджэння, як і весь іншы код, тады вы не рашылі первасныя проблемы — вы проста перанеслі іх на адзин крок ближэй да кліента.

Гэта таксама прычына, чаму такі патэрн так натуральна паслужыць у кодбазе Next.js: разработчыкі, якія пішу обробнікі маршрутаў, зазвычай яны ж і є тыя, хто піша компоненты, якіе іх вызываюць. Не трэба падаваць жадных заявак, няма перадачы заданняў між камандамі, няма аддзельнага процесу развёртвання, які трэба координаваць. Якщо вам патрэбна змяніць формат адпаведзення пасля процэсу оплаты, вы вносіце гэю змяну ў той жа pull request, дзе знаходзіцца компонент, які яго адображае.

Пастка: ператварэнне кожнага обробніка маршрута ў «бога-об’ект»

Існуе іншы спосаб выклікання проблем, пра які рэдкая калі гаворуць. Калі команда з’являе, што обробнікі маршрутаў можаюць агрэгаваць любыя даны, якія толькі пажадаюць, вырашэнне стварыць адны вялікі канечны пункт, який у аднам вызыве вяртае всю пачатковую стан аплікацыі, стае вельмі прывабным, адколі менш колькасць пераходаў выглядае як явны прынт. Інодзе так і ёсць. Але гэта таксама значыць, што адны обробнік маршрутаў вынужаны падтрымліваць шасць незв’язаных функцый адразу, і якой-небудзь змяне ў модулі доставкі тепер патрэбна ўключыць яе ў той самы файл, што і віджет з баламі лояльнасці, адколі оба ўключаныя ў такі код, як getInitialAppState.

Гэты прынцып застаецца актуальным, калі канцэнтр партнёра адпавядае ад однаго экрана чыста адной з часткаў функцыоналу з чыста вялікім дыапазонам, а не да „всаго, што пры запуску можа знадобіцца аплікацыі“. Якщо вам крыху складна даўаць назву канцэнтру партнёра на адной з конкрэтных частак інтерфейсу, гэта зазвычай ўскладненне таго, што ён стаў занадта шырокі.

Існуе ўзаема аднаязначны знак апавяшэння, на які таксама варта звернуць увагу: канцэнтры BFF, якія замест таго, каб пераформатавалі даны, прымушаны занова реалізаваць бізнес-логіку. Кантролер маршрута, які сам адлучае, чы рэчоўпакавальнік падходзіць для безкоштовной доставкі, замест таго, каб проста запытаў службу цэнаў і перадаў адпаведны адказ, таямна стае другым источнікам правды для правіла, якое вяліка частка ўжо існуе ў іншай частыне системы. Задача BFF — перакладваць і спяўляць даны, а не прымушаць рашэння. Як толькі ён пачынае прымушаць рашэння, мы знову сталкваемся з тым самым проблемам дэрываціі, якія обгавароўвалі раней, толькі ён знаходзіцца на адны слой ближэй да кліента, дзе яго гораздка легча знайсці пад час перагляду коду.

Мае мненне па гэтаму

BFF не павінен спрыяць як якась формальная архітектурная прыявежнасць, яка включае дыяграму і стороннюй сайт, хоча немае нічога пакладнага ў таком падходзе, якщо гэта пасуе да спосабу работы вашай арганізацыі. Для большасці команд, якія вже працуют з Next.js, ўзятак гэтага падходу — не столькі про введэнне чагось новага, сколькі пра названне таго, што вже існуе: прызнаць, што вашы обробчыкі маршрутаў функцыонуюць як BFF, адмовіцца ад ілюзійы, што яны ўсё тое ж універсальныя маршруты API, і структураваць іх так, каб яны відпавалі рэальным патрэбам кліента, а не межам службы сервера. Сам патэрн ніколі не быў складным. Складнае, і гэта тое, чаго большасць каманд адкладае, — гэта прызнанне таго, што логіка агрэгацыі павінна маты конкретную ролю, і перадача гэтай ролі команде фронтэнду, якая насправды за ёю адпавядае, замест таго, каб чакаць, пакуль на экране адпаведнага процеса знадобіцца шасць окремых запытоў для адрасавання, перш чым хтось зупініцца і запытаеся, чаму так.