Көшірілген 10+ Angular-жобадан бір Nx-монорепозиторийге
Осы бетте
Мәселе: көшірме — өсіп отыратын қарыз
Сызба таныс. Бірінші тапсырыс берушіге арналған қолданба бар. Екіншісі келеді: «бізге де дәл осындай, бірақ біздің логотиппен және бір-екі айырмашылықпен». Ең жылдамы — папканы көшіру. Бір жылдан кейін көшірмелер он болады, және:
- Баг N жерде өмір сүреді. Бір көшірмеде түзедің — қалғандарында қалды. Бұл туралы пайдаланушыдан білесің.
- Көшірмелер алшақтайды. Бірінде форманы түзеді, екіншісінде — кестені. Жарты жылдан кейін бұл файл атаулары ұқсас он түрлі қолданба.
- Фреймворкті жаңарту N-ге көбейеді. Әр көшірме бөлек жаңартылады, және әрдайым дерлік біреуі бірнеше нұсқаға артта қалады.
Негізгі ой: көшірменің құны көшіру сәтінде емес, әр келесі өзгерісте. Өзгерісті N жерде жасау керек болса, оның бағасы — N × (жұмыс + ұмыту мүмкіндігі).
Nx-монорепозиторий дегеніміз не, қарапайым тілмен
Монорепозиторий — ішінде бірнеше қолданба мен кітапхана тұратын бір git-репозиторий. Nx — кім кімге тәуелді екенін білетін және құрастыруды, тесттерді және линтті тек өзгеріс қозғаған нәрсеге ғана іске қоса алатын құрал.
Біз келген құрылым:
apps/
region-a/ ← тонкая оболочка: конфиг, логотип, переводы, окружения
admin-region-a/
region-b/
...
shared/
assignments/
data-access/ ← сервисы, HTTP, сторы
feature-assignments/ ← страницы и диалоги
meetings/
users/
...
ui/ ← презентационные компоненты без данных
ui-widgets/ ← компоненты, которым нужны данныеАймақ қолданбасында ештеңе дерлік жоқ. Ол ортақ маршруттарды, ортақ интерцепторларды және ортақ UI-ды қосады. Айырмашылықтары — логотип, API мекенжайы, аудармалар және қосылған модульдер жиыны.
Алдымен нені көшіру керек
Ең жиі қате — бәрін бірден көшіруге тырысу. Нәтиже берген рет:
1. Алдымен ең «жапырақтысы»: UI-кит
Батырмалар, инпуттар, кестелер, диалогтар. Оларда бизнес-логикаға тәуелділік жоқ, сондықтан оларды ештеңені бұзбай шығаруға болады. Сонымен бірге бір батырманың жылдар бойы қанша көшірмесі жиналғаны бірден көрінеді.
2. Содан кейін data-access: сервистер мен модельдер
HTTP-сервистер, жауап интерфейстері, сторлар. Мұнда көшірмелер ең қатты алшақтайды: бір жерде өрістің атын өзгерткен, бір жерде параметр қосқан. Әр алшақтық — «бұл тапсырыс берушінің ерекшелігі ме, әлде біреудің багы ма?» деген сұрақ. Әдетте — баг.
3. Содан кейін feature: беттер толығымен
UI мен деректер ортақ болғанда ғана бет дерлік механикалық түрде көшіріледі.
4. Ең соңында — қабық-қолданбалар
Барлық модульдер кітапханаларда болғанда, аймақ қолданбасы конфигурацияға келіп тіреледі.
Неге дәл осы рет: тәуелділіктері көшіріліп қойған нәрсені ғана көшіруге болады. Бұл графтың қарапайым топологиялық сұрыптауы: алдымен басқалар тәуелді түйіндер, содан кейін соларға тәуелділер.
Кітапхана шекаралары: тәуелділіктер тек бір бағытта
Бәрі соның үшін басталған ереже:
apps / feature-* → ui-widgets → data-access → uiНұсқар «импорттай алады» дегенді білдіреді. Кері нұсқарлар жоқ. ui — графтың жапырағы: ол сервистер туралы да, сторлар туралы да білмейді.
Жаңа компонентті қайда салу керегі үш сұрақпен шешіледі:
| Сұрақ | «Иә» жауабы → қайда |
|---|---|
| Тек бір экранға керек пе? | өз feature-кітапханасының components/ ішіне |
| Сервистер не сторлар керек пе? | ui-widgets ішіне |
| Таза презентация, бірнеше жерде керек пе? | ui ішіне |
Қалай күйіп қалдық: импорт сақинасы
Алғашында ui-widgets болған жоқ, деректері бар компоненттер тікелей ui ішінде жататын. Сақина пайда болды: ui data-access-ден сервис импорттайды, ал data-access ui-ден тип импорттайды.
Сақина кішкентай болғанда құрастырушы шамасы келеді. Ол өскенде рантаймда Angular-дың NG0919 («Cannot read @Component metadata») қатесі пайда болды: imports: [...]-ден алынған компонент класы оқылған сәтте әлі инициализацияланбаған еді, өйткені сақинадағы модульдер болжауға келмейтін ретпен жүктеледі.
Емі — «импорттардың ретін ауыстыру» емес, сақинаны үзу: деректері бар компоненттерді ui мен feature-кодтың арасында тұратын бөлек ui-widgets кітапханасына шығару.
Шекараларды машина тексеруі керек
Nx-те @nx/enforce-module-boundaries ESLint-ережесі бар. Тегтермен қалай көрінетінінің оқу мысалы:
// eslint.config.mjs (учебный пример)
'@nx/enforce-module-boundaries': ['error', {
depConstraints: [
{ sourceTag: 'type:ui', onlyDependOnLibsWithTags: ['type:ui'] },
{ sourceTag: 'type:data-access', onlyDependOnLibsWithTags: ['type:ui', 'type:data-access'] },
{ sourceTag: 'type:widget', onlyDependOnLibsWithTags: ['type:ui', 'type:data-access'] },
{ sourceTag: 'type:feature', onlyDependOnLibsWithTags: ['*'] },
],
}],Тегтер әр кітапхананың project.json файлында қойылады: "tags": ["type:ui"].
Шынын айтсам: бізде ереже қосулы, бірақ тегтер әзірге белгіленбеген, ал шектеулер «бәріне бәрі болады» режимінде тұр. Бағыт README мен ревьюге сүйенеді. Бұл келесі қадам, және дәл ереже ревьюден арзан болатын жағдай — толығырақ custom-eslint-rules жазбасында.
Жаңа аймаққа 5 минут
Бүкіл код ортақ болғанда, жаңа аймақ дегеніміз:
- Ең жақын аймақтың қабығын көшіру (≈25 файл, оның ішінде коды — бір-екі жүз жол конфигурация).
- Атын, логотипін, API мекенжайын, аудармаларын өзгерту.
- Деплой тізіміне жол қосу.
Бұл файлдарда ешқандай бизнес-логика жоқ, сондықтан оларды көшіру қорқынышты емес: олар алшақтамайды, алшақтайтын ештеңесі жоқ.
Келесі қисынды қадам — Nx-генератор: қабықты үлгі мен параметрлер бойынша жасайтын команда (nx g region --name=... --api=...). Сонда тіпті осы 5 минут бір командаға айналады, және ештеңені ұмыту мүмкін емес.
Мен не істер едім басқаша
- Шекаралар мен тегтер — алғашқы күннен, алғашқы NG0919 қатесінен кейін емес. Бес кітапхананы белгілеу кейін отыз кітапханадан тұратын сақинаны шешуден арзан.
- Көшірмелердің алшақтығын кестеге жазу, басыңда шешпеу. «Тапсырыс берушінің ерекшелігі» ме, әлде «баг» па — кейін табуға тура келетін шешім.
- Бүкіл репозиторийге тәуелділіктердің бір нұсқасы — бұл шектеу емес, монорепоның басты пайдасы. Бұл туралы Angular-ды жаңарту туралы жазбада: angular-6-to-22.
Қорытынды
Монорепозиторий кодты өздігінен жақсартпайды. Ол өзгерістің бір рет жасалуын қамтамасыз етеді, ал тәуелділіктер графы айқын болады — және оны тексеруге болады. Қалғаны (тек қозғалғанын құрастыру, бірыңғай CI) соның үстіне құрылады. CI туралы — ci-nx-affected жазбасында.