Мазмұнға өту

Аз болса да — күн сайын.

Көшірілген 10+ Angular-жобадан бір Nx-монорепозиторийге

5 мин оқу

Осы бетте
Қысқаша
Бізде бір Angular-қолданбаның оннан астам көшірмесі болды — әр аймақтық тапсырыс берушіге біреуден. Багты он рет түзейтінбіз, фичаны он рет қосатынбыз, әр жолы бір жерде ұмытып кететінбіз. Бәрін бір Nx-монорепозиторийге жинадық: ортақ код кітапханаларда, ал аймақ қолданбасы ~25 файлдан тұратын жұқа қабыққа айналды. Төменде — қандай ретпен көшіру керек, кітапханалар арасындағы шекараны қалай жүргізу керек және неден күйіп қалдық.

Мәселе: көшірме — өсіп отыратын қарыз

Сызба таныс. Бірінші тапсырыс берушіге арналған қолданба бар. Екіншісі келеді: «бізге де дәл осындай, бірақ біздің логотиппен және бір-екі айырмашылықпен». Ең жылдамы — папканы көшіру. Бір жылдан кейін көшірмелер он болады, және:

  • Баг 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 минут

Бүкіл код ортақ болғанда, жаңа аймақ дегеніміз:

  1. Ең жақын аймақтың қабығын көшіру (≈25 файл, оның ішінде коды — бір-екі жүз жол конфигурация).
  2. Атын, логотипін, API мекенжайын, аудармаларын өзгерту.
  3. Деплой тізіміне жол қосу.

Бұл файлдарда ешқандай бизнес-логика жоқ, сондықтан оларды көшіру қорқынышты емес: олар алшақтамайды, алшақтайтын ештеңесі жоқ.

Келесі қисынды қадам — Nx-генератор: қабықты үлгі мен параметрлер бойынша жасайтын команда (nx g region --name=... --api=...). Сонда тіпті осы 5 минут бір командаға айналады, және ештеңені ұмыту мүмкін емес.

Мен не істер едім басқаша

  • Шекаралар мен тегтер — алғашқы күннен, алғашқы NG0919 қатесінен кейін емес. Бес кітапхананы белгілеу кейін отыз кітапханадан тұратын сақинаны шешуден арзан.
  • Көшірмелердің алшақтығын кестеге жазу, басыңда шешпеу. «Тапсырыс берушінің ерекшелігі» ме, әлде «баг» па — кейін табуға тура келетін шешім.
  • Бүкіл репозиторийге тәуелділіктердің бір нұсқасы — бұл шектеу емес, монорепоның басты пайдасы. Бұл туралы Angular-ды жаңарту туралы жазбада: angular-6-to-22.

Қорытынды

Монорепозиторий кодты өздігінен жақсартпайды. Ол өзгерістің бір рет жасалуын қамтамасыз етеді, ал тәуелділіктер графы айқын болады — және оны тексеруге болады. Қалғаны (тек қозғалғанын құрастыру, бірыңғай CI) соның үстіне құрылады. CI туралы — ci-nx-affected жазбасында.