За межамі CRUD: дзесяць архітектурных прычын, якія дапамагаюць падтрымваць прыемлівыяя MERN-застаўкі
Выучыце прынцыпы на рэвэлі системы, якія дапамагаюць захаваць прыгожасць даплы MERN, калі яна расте: адпаведна прыналежнасць дадзейн, кантракты API, выведжаны стан, шараваны код, лёгкія пакеты дадзейн і аднообразныя спамы.
Большасць тыюторыяў па MERN заканчваюцца працуючым дапамоглівым прыкладам CRUD: MongoDB храніць даны, Express адкрывае калькі маршрутаў, React атрыбуе спіс, а можа і є экран для заходжання. Такі прыклад работае, але рэдка калі застаёцца незменным пад час росту. Калі з’яўляецца больш функцый і саавтараў, API становіцца важкім да змены, стан выходзіць за межы сінхроназаціі, сторанкі сповольняюцца, а дэбаггін займае больш часу, чым стварэнне. Інструменты рэдка бываюць прычыной. У гідзе рассказана аб дзесяці архітэктурных прычынках, якія рашаюць гэтыя проблемы, ўпрымкі да таго, каб вы моглі рассматрываць прыклад MERN як адну систему, а не чатырнаць канфігурацый.
Рассматрывайце MERN як поток даных, а не як спіс інструментаў
Звычны апіс стака — это проста яго складовыя:
MongoDB + Express + React + Node.js
Гэта правільна, але не паведамляе нічога пра тое, як часткі взаімаўпрацоўваюць, падобна да апісання автомабіля як двахрыльчатага мотора, чатырох колаў і керма. Болей корыстныя апісанні паказваюць, як даны праходзяць ад пользователя да базы дадзеных і зваротна:
User
│
▼
React
│
HTTP
│
▼
Express + Node
│
Database Queries
│
▼
MongoDB
Кожны стрэлкі ўособляе межу з сабойскімі правіламі: што можа адправіць браузер, што прыме сервер, шта зберагае база дадзеных. Большасць наведзеных выкладак стосуецца адлічэння таго, што вядзецься на гэтых межах.
1. Надаць кожнай часткі дадзеных аднае власніку
Пры стварэнні новай функцыянальнасці спачатку трэба выясніць, хто ў власнасці даных, якія выкарыстоўваюцца. У багатых моладых кодавых базах адказ часта неясны. Данні нынешняга корыстувальніка могу знаходзіцца ў стане компонента, у хранілышчы Redux, у localStorage, у адпаведзі на запыт API і ў іншай кэш-системе, адночасна. Раней чы пазней якая-небудзь копія адстае ад іншых, і інтэрфейс паказвае два суперасліжныя варыянты таго ж факту.
Яснае распадзелленне адпаведальнасці:
- MongoDB — гэта аднаковы источнік даных для стацыонарных дадзеных.
- Бэкэнд адпавядае за бізнес-правіла, якія вяду, як можна зменіць гэтыя данні.
- Фронтэнд паказвае данні і запрашвае змены через API; будзь-якая копія на стороне кліента є толькі кэшам, а не автантытетам.
Канонічным прынцыпам згідна ідея, што кожны фрагмент дадзейна мае толькі аднаго адпаведнага джерага, а ўсі іншы копіі знаюць, што яны можу быць застарэлымі. Бібліятекі для керавання станам сервера існуюць галоўная метай якраз для прымусовага керавання цім кэшаваннем; паказанае ў парадоксальнае падходжэнне да стану сервера з аднойчы React Query і Redux дапамага розбіраць гэты аспект проблемы.
2. Проектаванне API як кантрактаў
Тыповы першы канцэнтрык выглядае так:
app.get("/users", async (req, res) => {
const users = await User.find();
res.json(users);
});
Ён працюе, але таксама таямна обявляе, што адпаведны адказ завжды будзе масоўкай павных дакументаў корыстніка, незалежна ад таго, якія поля є у модэле. Калі мобільны дапыт, панелі керавання, інтеграцыя з партнерамі чы іншая команда завісяць ад такога формата, яго змена можа спанаваць ўсе гэтыя элементы.
Перш чым дадаць новы канцэнтрык, неабходна вырашыць:
- такі саме поля, які ён вяртае, а не всю первісную модэль (яка таксама можа выклікваць вытэчку внутраніх або чувствяглівых палей);
- чы можна яго пазніяй зменіць без наражання кліентаў на проблемы, чы для гэтага трэба версіяванне;
- какія іншыя системы, верагодна, будуць яго викорыстоўваць.
Апі, які быў рэштычна спроектаваны, можа прастаяць дольш чым калькі фронтэндаў; недбалы апі за калькі месца ператвараецца на тэхнічны борг.
3. Храніце як магчыма менш стану React
React представліваецца як бібліятэка для інтэрфейсу, але ў рэальных прыкладах выкарыстоўвання большая частка проблемаў стоіць у стане. Частыя памылкі — храненне значэнняў у стане, якія моглі бы быть вырахаваны:
const [users, setUsers] = useState([]);
const [filteredUsers, setFilteredUsers] = useState([]);
Хутчэй filteredUsers цалкам вылічваецца з users. Яго раздзельнае храненне значыць, што кожная апдэйтаванне павінна падтрымліваць сінхронізацыю обох, а якщо пра гэта забыць, то будзе стары список. Кращэ вырахаваць яго пад час рендарування:
const filteredUsers = users.filter(user => user.active);
Правілам згідна практыка — хаваць толькі тое, што нельга вырахаваць, а ўсё іншае — выводзіць. Якщо выведанне стае справды даскладным, useMemo можа яго зберагчыць у кэшы, але гэта застаецца выведзенымі данымі, а не другым джерелам правды.
4. Памярожце, што CRUD — гэта простая частка
Большасць проектаў зупіняецца на чатырох базовых операцыях:
Create
Read
Update
Delete
Продакшн-бэкенд уключае ў гэтыя операцыі ўсё больш функцый: перакананне данных, аутэнтыкацыю, автарызацыю, бізнес-правіла, обмежэння частоты запытоў, логіруванне і паведамленні. Пораўняйце просты спосаб создання:
await User.create(req.body);
з версіяй, якая спачатку пераканаецца ў вхідных данных
if (!isValid(req.body))
throw new Error("Invalid input");
і пераканаецца, што апытчык мае право на дзействіе, прытымкі пісаць:
if (!canCreateUser(req.user))
throw new Error("Unauthorized");await User.create(req.body);
Няўыклы варіант таксама нешта падазрэльны з точкы зору масовага прызначэння дадзеных: перадача req.body без перакантролю прымаць у функцію create дазволяе кліенту задаць будзь-якія полья, якія прымеяе схема, уключаючы нарыцайшы флаг role. Неабяжна явны чынам перакантролюваць і выбіраць толькі дазволеныя полья. Таксама важна згадаць, што невялікі чэк на правыя на адчыненне є, канцэптуальна, кодам 403 (забараненае), а не 401, незалежна ад таго, што пісаеся ў прыказцы пра адмоўку. Запіс дадзеных — гэта проста, але захаванне іх — тут і пачынаецца сложнасць у розрабоце бэкенду.
5. Раздзеленыя функцыі ў шарах
Найболей явныя разлікі межа кодавой базай для хобі і професыянальной заключаюцца ў тым, дзе знаходзіцца логіка. Сумешчанне разных функцый прыводзіць да таго, што хэндлеры маршрутаў заполнююцца запытамі да базы дадзеных, компоненты React — правіламі перакантролю, а кантролеры — бізнес-логікай, пры чым кожны з гэтых файлаў становіцца ўсё большым.
У бэкендзе з шарамі кожнаму шару прызначаецца адна задача:
Routes
│
Controllers
│
Services
│
Repositories
│
Database
Мапы маршрутаў супарабатваюць з URL-амі, пры чым хэндлеры та кантролеры перакладаюць HTTP-запыткі у вызывы функцый, сервісы выконваюць бізнес-правіла, а репазітарыі ведуць дыялог з базай маўлявіння. Маленькія, адзіноцелевыя елементы лёгчэе теставаць і зменяць. Чырвоныя падробнейшыя інструкціі можна знайсці за лінкам «Дыяграма проектавання API на Node.js з розпадам на шары: ад вялікіх кантролераў да чыстай архітектуры».
6. Спачатку вылечыце працэсавую скорасць у источніку дадзеных
Калі запытаюць, як прышвырнуць скорасць React-дзеяння, большасць разработчыкаў вядуцься на useMemo, React.memo і useCallback. Гэтыя элементы дапамагаюць, але багато проблем з працэсавой скорасцю пачынаюцца ўжо да таго, як уключаецца React. Уявіце сабе такую запытку:
GET /users
якая вяртае
50,000 users
адзін час, калі на экране паказваецца толькі
10 users
Ніяка мемаізацыя не можа компенсаваць перадачу і аналіз дзесяткаў тысяч непатрэбных записаў. Раскіяйце гэта на самай пачатковай стадыі:
- распісваць рэзультаты на сторанкі;
- фільтрацыя на сервере;
- перадаваць толькі тыя поля, якія патрэбны кліенту;
- компрэсавацыя адказоў;
- зберагачванне ў кэшы цярпяча, з чыстым планам анулювання.
Найшырэйшы компанент, які можа быць адразу выкарыстоўваны, — гэта той, які ніколі не прымець дадзеныя, якія ў яго непатрэбны.
7. Зробіце карыстоўванне з бядамі часткай дизайна
Код для разработкі часта прыхаввае такія бяды:
try {
...
}
catch(error){
console.log(error);
}
Логаванне і працэс продажу скрывае неудачу ад кліента і ад систем з монітарынгам. API для працы ў прымэтным режыме патрабуе бяды, якія будуць адноснымі і чытальнымі для машын:
return res.status(400).json({
message: "Invalid email address",
code: "INVALID_EMAIL"
});
Стабільная структура з людзькім для чытання message і машынным для чытання code дазволяе фронтэнду супарабелаваць коды з конкретнымі паведамленнямі UI, логі можна групаваць па кодах, а аўтазвясткі можна актываць у разе незвычайных тэндэнцый, пры чым адлагоджэнне пачынаецца з вядомай катэгоріі, а не з стэк-трэйсу. Перашкоды немагчымы; мета — працаваць з імі прыведамліва.
8. Арганізаваць код па функцыях
Пры двадзесяці файлах паслужыць будзь-яя структура папак. Пры пяцістах яна мае вельмі важлівое значэнне. Структура, арганізаваная па тэхнічным типе, распрадзеляе адную функцыю па всім дрэву:
routes/
controllers/
models/
Групаванне па функцыях дазволяе трываліваць усё, што стосуецца адной домэны, ў аднам месцы:
users/
routes.js
controller.js
service.js
validation.js
orders/
routes.js
controller.js
service.js
Калі змінюецца логіка ардэроў, вы ачынаеце толькі папку orders, больш нічога. Папкі функцый таксама значна спрыяюць кращаму кераванню правамі на код, яго перагляду і пазней выяўленню можлівасцяў для перакладу ў окремыя сервісы.
9. Думайце пра системы, а не пра заявкі
Заявка на функцыю, такая як „дадаць можлівасць заходу“, можа быць адпаведзена часткальна, за дапамою формы і маршрута. Падход, які уважае да системы, вымагае адпаведных запитанняў: як працюе аутэнтыкацыя з самага початку і да канца, дзе храняць токены, як апэлююцца да прав, што выходзіць, калі токен заканчываецца свой тэрмін, і як будучы мобільны кліент будзе заходзіць. Адпаведныя адказы з самага пачатку трэбуюць больш часу сёння, але запобегаюць перапісву коду пазней.
10. Рашуча выбірайце компрэсіі
Няма архітектуры, якая бы была найкращая ў кожной ситуацыі. Кожны варыянт дае якісь перавагі, але таксуеся певнымі зусіллямі:
- Простая архітектура: быстрее ў стварэнні, але складная для масштабавання.
- Мікросервісы: можлівасць незалежнага масштабавання, але значна вышэйшая складнасць у эксплуатацыі.
- Глобальны стан: лёгкая абмены между компонентамі, але складнейшая дыягностика.
- Нормалізаваныя дизайны базаў дадзеных: менша кальканасць, большая колькасць з’еднанняў аб вычысканняў.
- Актыўнае кэшаванне: быстрэйшыя адпаведзі, але постаючая проблема неваліднасці кэшу.
Суперняхітныя інжынеры — это не тые, хто знае ўсі шаблоны, а тые, хто можа пасвядоміць, калі кожны шаблон варты своих затрат.
Як праекты MERN зазвычай паслабляюцца пад час росту
Этап 1: усё проста
Першая версія пакрывае найбольш неабходнае:
CRUD
Authentication
Dashboard
Deployment
Код маленькі, і всі яго розумеюць.
Этап 2: рост адкрывае шляхавыя способы
Праўіцца больш выкарыстоўвальнікаў, функцыяй і разрабоў. У всім кодбэсе з’являецца дуплікаваная логіка, нэўсастойчывыя канцэнтры, медленныя сторункі, заплутаны стан і складная дэбаггаванне.
Этап 3: адпаведальнасць пакладаюць на інструменты
Команда вырабляе заключэнне, што React не падае пад навантажэння або што выбор MongoDB быў памылкай. Зазвычай ніткога з гэтага не ёсць. Архітектура проста ніколі не развивалася разам з аплікацыёй.
Аналагія рэстарана для шароў
Уявіце стак як ресторан. MongoDB — це кладовая, дзе зберагаюцца всі інгрэнты. Express і Node — гарніця: яны вяршаюць выбор, што будзе гатована, як гэта будзе прадаравана і хто можа замовляць. React — це робочая зона, яка прадстаўляе гатовыя стравы гасцям. Гасцям не трэба ведаць, як працюе гарніця, а гарніцы не інтэресуе, як расставлены тараліцы на столе. Кожны элемент добра выканае свою задачу, і гэта самэ тое раздзеленне, якое патрэбнае для аплікацыі MERN.
Ключовыя моманты
Знанне MERN — цэла не столькі у напісанні запытоў, маршрутаў і компонентаў, сколькі у розуменні шляхоў, якімі прайшаюць даны, размешчанні бізнес-правіл у правам шаре, развіцці API без парадків для кліентоў, зменшэнні колькасці стану і розуменні таго, як ранніяя рашэнні вплываюць на апошнія рэзультаты.
- Да кожнай часткі даных назначыце адного власніка і спрыявайце кожной іншай копіі як кэшу.
Калі з’яўляецца новая функцыя, найболей корыстны вопыт — не як яе створыць, а дзе кожная адпаведальнасць належыць.
Спадні матэрыялы
- Дзесяць архітэктурных прычынакоў, якія дапамагаюць кодбазам фронтэнду застаўацца падтрымванымі гадамі — Адказвае на структурныя прычынакі, такія як оптымізацыя для выдалення, чысты поток дадзэнняў і ізоляцыя бізнес-логікі, якія дапамагаюць кодбазам застаўацца падтрымванымі працэсамі змен.
- Адміністратыўная архітэктура: проблемы, якія ствараюць труднасці для команд, якія працуюць пераважна з фронтэндам у React — Ішчырая аналіз пяці распашчаў у дызайне адміністратывной часткі, якія ўзніклі ў проектах на базе React — ад неправильнага викорыстоўвання парадыгмы API да нестабільных розгортанняў — а таксама архітэктурныя рашэнняя для забезпечэння надзеі на роботу у прыметных умовах.
- Localhost — гэта не прыметныя умовы: діагностика React-дзеянняў, якія зламваюцца пасля розгортання — Дазвольце дазнацца, чаму React-дзеянне, якое працуюць нормальна на вашым комп’ютеры, зламваецца пасля розгортання, і як выявіць прычыны ў API-адрэсах, зменных сераўніка, механізмах CORS, маршрутах, ресурсах і системах аутэнтыкацыі.