Table of Contents
Az elmúlt években elég sok módszert kipróbáltam, annak érdekében, hogy egy adott szoftverren való munka egyszerű és stresszmentes legyen. Számomra mindig az a legfontosabb, hogy a programozás egy “művészet” legyen, ebből kifolyólag, úgy állok a kód megírásához, hogy az mindenki számára egyszerű és élvezet legyen azt használni (mint egy jó könyv).
A következőkben bemutatok egy megközelítést, amelyet az utóbbi két évbe használok. Ezzel nagyon sok idegeskedést és hibát meg lehet spórolni. Megláthatod, hogy miért is lehet ez az egész fontos.
Ennek a cikknek az értelmezése valószínűleg nagyobb energia befektetést fog igényelni a technikai részletek miatt, ezért kérlek koncentrálj.
Nézzük először azt, hogy miért kezdtem el gondolkodni egy esetleges másik megközelítésen/megoldáson. Tulajdonképpen, a legnagyobb probléma mindig az volt, hogy amikor a projekt elért egy bizonyos méretet, képtelen voltam (nagyon sok munka árán) új funkciókat hozzáadni, úgy, hogy az előző funkciók ne sérüljenek. Ez sok mindennek betehető, de én a rendezetlenségnek és a proaktivitás hiányának tudom be. Ha a kódbázis nem rendezett és magától értetődő, egyszerűen abban a pillanatban elveszíted a kontrollt felette. Ami a későbbiekben meg fogja bosszúlni magát, mégpedig úgy, hogy át kell írni a rendszer nagy részét, ez pedig idő, pénz és energia igényes. Az biztos, hogy a projektet jól kell tudni elkezdeni ahhoz, hogy jó legyen a vége.
Elég sok megoldást kipróbáltam már:
- több repository minden applikációnak
- egy repository-ban több applikáció, de úgy, hogy nincs a kód megosztva.
Ezek a megközelítések nem rosszak, de nem elég hatékonyak. A karbantartás és a fejlesztések költségei exponenciálisan fognak nőni.
A jó hír az, hogy ezt is lehet okosabban csinálni.
Monorepo előnyei és hátrányai
A monorepo “stratégia” egy olyan alapvető megközelítésen alapszik, amely szerint minden kódbáziban mindennek meg kell legyen a saját helye. Ezzel egy olyan struktúrát tudunk létrehozni, amely rendezettség érzését adja meg a programozóknak.
A monorepo számos előnyt nyújt a használóinak, további eszközökkel (későbbiekben) kiegészítve egy hatékony megoldás lehet arra, hogy hogyan érdemes kezelni a kódbázist.
- Minden egy helyen
Nagyon fontos dolog az, hogy a kritikus dolgokat egyszer írjuk csak meg, anélkül, hogy duplikálnánk őket. Amennyiben jelentenek egy hibát, sokkal gyorsabban ki lehet azt javítani, mintha egyszerre több helyen is szerepelne ez az adott kód.
Mivel egy repository-ban található meg a projekt, ezért a verziókezelés is egy helyen történik. Ezzel egy átfogóbb képet fogunk kapni a projekt fejlődéséről.
- Kód megosztás
A legtöbb esetben a kód duplikálódhat, viszont, ha monorepot használunk, akkor az applikációkban szereplő funkciókat, konstansokat, típusok sokkal egyszerűbben meg tudjuk osztani egymás között (későbbiekben bemutatom hogyan), anélkül, hogy duplikálnánk őket. Ezzel szinte egy kalap alá lehet hozni a rendszer részeit.
- Standardok
Mivel minden egy helyen van, ezért a kód struktúrája, elrendezése és formázása ugyanolyan lehet (későbbiekben bemutatom hogyan). Ebből kifolyólag, ha fájlok között mozgunk, akkor a szemünk a megszokott formátumot láthatja, csak a kód más.
- Átláthatóság és hibaellenőrzés
Az egész projekt átláthatóbb lesz, hiszen több programozó fogja látni azt, hogy ha valaki rosszul csinál valamit. Ezzel kevesebb a hiba lehetősége.
Amennyiben foglalkoztál már olyan kóddal, ami nem volt jól rendszerezve, vagy nem volt dokumentálva, akkor valószínűleg tudod, hogy milyen megterhelő is az, hogy kitaláld, hogy mi hol van.
- Skálázhatóság
Képzeld azt el, hogy egy repository-ban több száz termék/applikáció található meg és ezek mind egy elven alapulnak. Ebben az esetben egy új applikáció hozzáadás nem lesz nagy gond. A Google pont így csinálja.
Bár az előnyök ellenére van egy pár hátránya is!
- Méret
Mivel minden egy helyen van, ez azt eredményezni, hogy a repository mérete nagyobb lesz.
- Összetettebb build folyamatok
Megfelelő eszközök és automatizáció nélkül elég nehéz kezelni a build folyamatokat.
- Megterhelő a kezdőknek
A kód mennyiségéből fakadóan az új csapattagok lassabban vehetik fel a ritmust.
Kód megosztás
A monorepo legnagyobb előnye az, hogy az egyszer megírt kódot újra fel tudjuk használni, akár másik applikációkban és biztosak lehetünk benne, hogy a végeredmény mindenhol ugyanaz lesz. Emellett, ha hiba jelentkezik valahol, akkor azt könnyebben azonosítani lehet.
Amit érdemes megosztani az applikációk között:
- stílusok
- típusok
- konstansok
- külső/belső könyvtárak (ha szükséges)
- környezeti változók
Kódbázis szervezése/felépítése
Annak érdekében, hogy a lehető legtöbbet tudd kihozni belőle, érdemes pár kiegészítő eszközt használni. Azt ki szeretném hangsúlyozni, hogy az említett eszközök leginkább JavaScript-hez kapcsolódnak, viszont biztos vagyok benne, hogy más nyelveknek is meg vannak az adott problémára a megoldásai.
Monorepo + PNPM
Nagyjából két évig Yarn-t használtam mint könyvtárkezelő rendszer a monorepo-ban, amely jól is működött, addig a pontig, ameddig nem volt szükségem olyan információra, miszerint melyik könyvtár milyen függőségekkel rendelkezik. Egyszerűen nem lehetett megtudni ezeket hatékonyan. Továbbá, gondot okozott még az is, hogy megengedett olyan könytárak beimportálását, amelyek nem voltak telepítve. Ekkor váltottam PNPM-re.
Ez az eszköz alapjában véve megváltoztatta, hogy hogyan kezelem a függőségeket, hiszen attól a ponttól kezdve nem tudtam használni olyan könyvtárakat amelyek nem voltak telepítve az adott könyvtárban. Ezzel biztosított az, hogy minden könyvtár független a környező könyvtáraktól. Azóta a PNPM a könyvtár kezelő rendszerem.
Én azt javaslom, hogy kezd el a PNPM használatát, amennyiben már Yarn-t használsz, akkor érdemes lehet elgondolkodni a migráláson.
A PNPM és Yarn legfőbb előnyei:
- Egyszerűsített függőségkezelés
Lehetővé teszik, hogy több csomag függőségeit is kezeld egyetlen helyen, ami csökkenti a duplikációkat és biztosítja a konzisztens verziókat a csomagok között.
- Hatékony linkelés
Az egyik könytárban végrehajtott változtatás azonnal megjelenik a többi könytárban, amelyek függnek tőle. Ez felgyorsítja fejlesztési időt.
- Jobb projekt-szervezés
A könyvtárak logikusan csoportosíthatóak, így könnyebbé/gyorsabbá válik a navigálás és a projekt megértése.
Felépítése
A következő felépítést ajánlom a használatához. Ennek a cikknek a keretein belül nem fogok részleteibe menően elmagyarázni minden konfigurációt, viszont arra koncentrálok, hogy megértsd és lásd, hogyan is működik.
Nézzük a felépítését:
project
├── .git
├── .husky
│ ├── pre-commit # commit előtti kódok futtatása
│ └── prepare-commit-msg # commit üzenet előkészítése commitlint segítségével
├── apps # applikációk helye
│ ├── application1
│ │ ├── tests
│ │ ├── tsconfig.json # TypeScript konfigurációja
│ │ └── package.json
│ └── application2
│ ├── tests
│ ├── tsconfig.json # TypeScript konfigurációja
│ └── package.json
├── packages # belsőleg megosztott könyvtárak helye
│ ├── common # könyvtárak, amelyeket mindkét applikáció használhat
│ │ ├── constants
│ │ │ ├── tsconfig.json # TypeScript konfigurációja
│ │ │ └── package.json
│ │ ├── tests
│ │ │ ├── tsconfig.json # TypeScript konfigurációja
│ │ │ └── package.json
│ │ └── types
│ │ ├── tsconfig.json # TypeScript konfigurációja
│ │ └── package.json
│ └── application1 # könyvtárak, amelyeket az application1 fog használni csak
│ ├── package1
│ │ ├── tests
│ │ ├── tsconfig.json # TypeScript konfigurációja
│ │ └── package.json
│ └── package2
│ ├── tests
│ ├── tsconfig.json # TypeScript konfigurációja
│ └── package.json
├── .commintlintrc.js # CommitLint konfigurációja
├── .eslintrc.js # ESLint konfigurációja
├── .prettierrc # Prettier konfigurációja
├── pnpm-workspace.yaml # PNPM konfigurációja
├── tsconfig.base.json # TypeScript konfigurációja
├── tsconfig.json # TypeScript konfigurációja
└── package.json
A felépítés első ránézésre komplikáltnak tűnhet, viszont egy rövid magyarázatot követően világos lesz.
A belső könyvtárak és applikációk definiálásáért a package.json
fájlok felelnek.
- a fő
package.json
fájl, amely aproject/package.json
útvonalon található, felel az egész monorepo rendszer definiálásáért és kezeléséért. Ebben szerepelnek az olyan külső könyvtárak, amelyek a fejlesztés folyamatát segítik (általában a következő fejezetben taglalt eszközök) - a
project/packages/**/package.json
útvonalon található fájlok felelnek a belső könyvtárakért. Itt fogjuk írni az azokat a kódokat, amelyek megoszthatóak. Azt javaslom, hogy két szintes csoportosítást végezz, amit úgy értek, hogy készíts egycommon
nevezetű mappát, amibe csak a közös kód megy, és egy mappát az applikációknak. Például, ha több mobil applikáció szerepel a kódbázisban, akkor azoknak a belső könyvtárait érdemes egy mappába tenni, mondjukapplication
néven. Továbbá, a belső könyvtárak neveit csoportosítsd, például:project-common/constants
,project-application/styles
- a
project/application/**/package.json
útvonalon található fájlok felelnek az applikációkért. Amennyiben, több applikációról van szó érdemes jól elhatárolható neveket adni nekik.
A pnpm-workspace.yaml
fájlban tudjuk konfigurálni, hogy a PNPM hol fogja keresni a könyvtárakat a kódbázison belül. Ezeket tudjuk majd megosztani az applikációk és könyvtárak között.
Amennyiben alkalmazunk unit vagy e2e (end to end) teszteket akkor azokat érdemes könyvtár szinten tartani, viszont ha van olyan teszt amit duplikálnánk, azt át lehet rakni a közös tesztek közé. A tesztek a **/tests
mappába kerülnek.
Husky, Prettier, ESLint, Typescript, CommitLint
A monoreponak hála a következő eszközök működését elég egyszer beállítani és érvényesek lesznek az egész kódbázisban. Ezek az eszközök szinte elengedhetetlenek, ha egy hatékony környezetben szeretnénk programozni.
Az előző mappa struktúrában szerepelnek az eszközök beállításáért felelős fájlok a szemléltetés kedvéért, de ennek a cikknek a keretein belül nem térek ki a konkrét konfigurációra.
- ESLint
Az ESLint egy népszerű JavaScript kódot vizsgáló eszköz. Elemzi a JavaScript kódot a lehetséges hibák, a kódolási stílus, a következetlenségek és a legjobb gyakorlatok betartása érdekében. Célja, hogy segítse a fejlesztőket a kód magas szintű minőségének fenntartásában, az olvashatóság javításában és a gyakori programozási hibák felismerésében. Konfigurációja a .eslintrc.js
fájlban történik.
- Husky
A Husky egy népszerű Git-hez kapcsolódó keretrendszer, amelyet feladatok automatizálására és a fejlesztési munkafolyamatok eszközöléséhez használnak. Konfigurációja a .husky
mappában történik.
- Prettier
A Prettier egy széles körben használt kódformázó eszköz. Azért használjuk ezt az eszközt, mert segít a következetes kódformázás biztosításában azáltal, hogy automatikusan alkalmaz egy előre definiált formázási szabálykészletet a kódbázisunkra. Konfigurációja a .prettierrc
fájlban történik.
- Typescript
A TypeScript nélkülözhetetlen a modern JavaScript világában, mivel statikusan tipizált réteget biztosít. A statikus tipizálás bevezetésével növeli a kód megbízhatóságát, olvashatóságát. A hibák fejlesztés közbeni felismerésének lehetősége csökkenti a hibákat és elősegíti a rugalmasabb kódot. Konfigurációja könyvtár vagy applikáció szinten történik a tsconfig.json
fájlokban. A tsconfig.base.json
fájl szolgál a konfiguráció alapjául az összes tsconfig.json
fájlban, így minden egyes könyvtár és applikáció saját kontextussal rendelkezik.
A típusok a types
könyvtárba kerülnek, amelyeket egy egyedi formában érdemes elnevezni. A típusok egy T
betűt kapnak a nevük elé, az interface-ek, pedig egy I
betűt, ezzel a kódban ki fognak emelődni.
Például:
export type TPrimaryButton = {};
export interface IPrimaryButton {};
- CommitLint
A szabványosított commit-üzenetek kikényszerítésével egyszerűsíti a kódellenőrzéseket, és biztosítja a világos és rendezett verziótörténetet. A CommitLint elősegíti a helyes gyakorlatokat, ami jobb kódminőséget, jobb kommunikációt és hatékony fejlesztési munkafolyamatot eredményez. Konfigurációja a .commintlintrc.json
fájlban történik.
Legnagyobb előnye az autómatizálásnál van, hiszen egységesített módon van rendszerezve a git előzmény, így ezt fel lehet használni egy másik eszköz segítségével. (semantic release)
Merge request
Azt javaslom, ha legalább ketten dolgoztok egy monorepoban, akkor minden változtatást egy külön git branch-ben végezzetek el, majd ezt csak úgy mergelitek be ha a másik fél átnézte. Így kisebb az esélye, hogy hiba kerüljön a rendszerbe. Amennyiben van egy teszter a csapatban, akkor még mielőtt megtörténik a merge, érdemes a teszteket abban az branch-ben létrehozni.
Gyakori kérdések
- Mi az a monorepo?
A monorepo, a monolitikus tároló rövidítése, egy olyan verziókezelő rendszer, amelyben több projekt vagy komponens egyetlen tárolóban van tárolva. Ez egy alternatívája annak, hogy egy szoftverfejlesztő szervezeten belül minden egyes projekthez vagy modulhoz külön tároló legyen.. - Miért választ egy csapat monorepot?
A monorepókat különböző okokból választják, többek között az egyszerűsített függőségkezelés, a kódmegosztás megkönnyítése, az egységes verziókezelés és a csapatok közötti jobb együttműködés miatt. Emellett megkönnyíthetik a következetes kódolási szabványok érvényesítését.
Következtetés
Összességében ez a megközelítésbeli váltás meghozta azt a minőségi változást a projektek életébe, amely már magáért beszél. Örülök annak, hogy megléptem ezt a lépést, hiszen így egy új dimenziót nyitottam ki.