Articles

siirtyminen useista arkistoista Lerna-js mono-repoon

At mitter.io, meillä on pari julkista npm pakettia, jotka meidän on julkaistava, ja siirryimme hiljattain Lernan hallinnoimaan mono-reporakenteeseen, josta jokaisella on erilliset arkistot. Tänään haluan jakaa kokemuksemme tästä muuttoliikkeestä ja asetelmastamme uuden monorepo-rakenteen kanssa. Kaikki pakettimme ovat joko SDK-kohteita tai riippuvaisia SDK – tavoitteistamme:

  • @mitter-io/core – Mitterin ydintoiminnot.io SDKs
  • @mitter-io/models – konekirjoitusmallit (luokat, tyyppinimet, liitännät jne.)
  • @mitter-io/web – web SDK

  • @mitter-io/react-native – React Native SDK
  • @mitter-io/node – the node.JS SDK (käytetään solmua.js backends)
  • @mitter-io/react-scl – ReactJS-sovellusten vakiokomponenttikirjasto

kaikki pakettimme on kirjoitettu Konekirjoituksella ja typingit on niputettu itse pakettien kanssa, emmekä jaa erillisiä konekirjoituspaketteja (niitä, jotka yleensä alkavat @types/). Käytämme rollup niputtaa nämä paketit sekä UMD ja ES5 moduuli formaatteja. Tämän lisäksi käytämme Typedocia tuottaaksemme dokumentaatiomme, joka sitten julkaistaan julkisella ämpärillä AWS S3: ssa.

ennen Lernan käyttöä Meillä oli erillinen arkisto jokaiselle paketillemme ja se toimi hyvin, kun meillä oli vain web SDK. Kun edistyimme ja SDK: n parissa työskenteli lisää kehittäjiä, meillä alkoi olla muutamia ongelmia asennuksessamme:

  1. ottaen huomioon, että suurin osa SDK: n logiikasta on @mitter-io/core, lähes kaikki muutokset, jotka tapahtuivat core paketissa ja kaikki muut paketit jouduttiin päivittämään uuden version osoittamiseksi. Eli vaikka olisi vika, joka piti korjata, sano React Native, muutos menisi core, mutta päivityksen piti nyt heijastua kaikkiin muihin kohteisiin, eli webnode ja react-native. Oli melko tavallista, että rakennuttaja ei osunut kohteeseen.
  2. lähes jokainen SDK: n muutos johtaisi muutoksiin vähintään kolmessa viidestä paketista.
  3. näimme valtavan hyödyn siitä, että sama versio säilyi paketeissa (kehittäjien on helpompi arvata, mikä Targetin uusin versio olisi), mutta manuaalisesti tämän seuraaminen kävi hankalaksi.
  4. npm link (tai yarn link jos haluat) oli oma ongelmakokonaisuutensa, jossa varmistettiin, että kaikki riippuvuudet olivat yhteydessä toisiinsa, sitten ei voitu käyttää oikeaa npm ja takaisin paikalliseen linkkiin kehitystä varten.
  5. oli melko yleistä suorittaa skriptejä eri paketeissa (esim., julkaista typescript docs), ja käytimme hauras tukiranka symlinks ja bash skriptejä hallita samaa.

noihin aikoihin törmäsimme Lernaan ja se tuntui sopivan täydellisesti vaatimuksiimme.

päätimme seurata yksinkertaisinta polkua, joka on olemassa, yrittäen käyttää oletuksia niin paljon kuin mahdollista. Kokemamme perusteella muutto Lernaan oli helppoa. Aloita luomalla uusi Lerna repo:

mkdir my-new-monorepo && cd my-new-monorepo
git init .
lerna init

vastaa pariin yksinkertaiseen kysymykseen (jossa turvauduimme aina oletukseen) ja olet valmis. Vanhojen pakettien siirtäminen repoistaan uuteen (jota pelkäsimme, koska luulimme sen olevan valtava tuska) oli paljon odotettua helpompaa:

lerna import ~/projects/my-single-repo-package-1 --flatten

huomaa, että --flatten voi olla tai ei tarvita, mutta kohtasimme ongelmia ilman sitä.

hämmästyttävää Lernassa on se, että se tuo mukanaan kaikki git-toimitukset (saatat menettää jonkin verran historiaa --flatten) siten, että Uuden repon kohdalla historia näyttää siltä, että kehitystä on tapahtunut aina tässä monorepossa. Tämä on aivan välttämätöntä, koska joudut git blame jonkun vian takia, jonka löysit monorepoon muuton jälkeen.

nykyiset asetukset

Lernan avulla hallinnoimme nyt kaikille paketeille yhtä arkistoa, jonka hakemistorakenne näyttää tältä:

packages/
core/
models/
node/
react-native/
web/
lerna.json
package.json

muuttuneiden pakettien julkaisemiseksi on nyt yksinkertaisesti pakko:

lerna boostrap
lerna publish

sinun ei tarvitse tehdä lerna bootstrap joka kerta; vain jos tämä on ensimmäinen kerta, kun olet tarkistamassa Repoa. Mitä se tekee on yksinkertaisesti asentaa kaikki riippuvuudet kunkin paketteja tämän repo.

samalla päätimme myös hieman virtaviivaistaa prosessiamme ja lisäsimme kaikki pakkaustehtävät npm itse elinkaaren sisällä. Huomaa, että tällä ei ole mitään tekemistä Lernan kanssa.; tämä on jotain, jonka pitäisi mieluiten olla missä tahansa npm-paketissa reporakenteesta riippumatta. Kunkin paketin kohdalla seuraavat skriptit ovat yksittäisessä pacakge.json:

"scripts": {
...
"prepare": "yarn run build",
"prepublishOnly": "./../../ci-scripts/publish-tsdocs.sh"
...
}

Tämä rakentaa paketin konekirjoituksella, niputtaa sen rullalla ja luo docs typedoc:

"scripts": {
...
"build": "tsc --module commonjs && rollup -c rollup.config.ts && typedoc --out docs --target es6 --theme minimal --mode file src"
...
}

, joilla on yksi reporakenne, mahdollistaa myös yhteisten skriptien pitämisen yhdessä paikassa siten, että muutokset koskevat kaikkia paketteja (meidän pitäisi myös siirtää build script erilliselle skriptille, koska siitä on nyt tullut melko monimutkainen bash-komento).

kehittäjävirta

kehittäjävirta julkaisuja lukuun ottamatta on ennallaan. Kehittäjä luo ongelman Gitlabiin (tai sille annetaan sellainen), luo uuden haaran ongelmalle ja yhdistää muutokset masteriksi koodin tarkastelun jälkeen. Julkaisun elinkaari seuraa nyt erittäin jäsenneltyä prosessia:

  1. kun virstanpylväs on valmis ja suunnittelemme uuden julkaisun tekemistä, yksi kehittäjistä (vastaa kyseisestä julkaisusta) luo uuden version ajamalla lerna version.
  2. Lerna tarjoaa erittäin hyödyllisen ja helppokäyttöisen kehotuksen seuraavan version hahmottamiseen
(master) mitter-js-sdk ツ lerna version --force-publish
lerna notice cli v3.8.1
lerna info current version 0.6.2
lerna info Looking for changed packages since v0.6.2
? Select a new version (currently 0.6.2) (Use arrow keys)
❯ Patch (0.6.3)
Minor (0.7.0)
Major (1.0.0)
Prepatch (0.6.3-alpha.0)
Preminor (0.7.0-alpha.0)
Premajor (1.0.0-alpha.0)
Custom Prerelease
Custom Version

kun uusi versio on valittu, Lerna vaihtaa pakettien versiot, luo tunnisteen etärepoon ja työntää muutokset GitLab-instanssiimme. Tämän lisäksi kehittäjien ei tarvitse tehdä mitään muuta. Meidän CI on setup rakentaa kaikki tunnisteet, joilla on nimi samanlainen semanttinen versioitu numero.

huomaamme, että ajamme lerna version kanssa --force-publish, koska haluamme, että kaikilla paketeilla on täsmälleen sama versiolinja. Joten joskus meillä on paketteja, jotka eivät eroa eri versioiden välillä. Riippuen mieltymyksestäsi, saatat päättää olla tekemättä sitä.

CI setup

käytämme gitlabin integroitua CI: tä rakentamiseen, testaamiseen ja julkaisemiseen kaikissa projekteissamme (JS ja Java). Uudessa JS monorepossa on kaksi vaihetta:

  1. Rakenna
  2. Julkaise

rakentamisvaihe on äärimmäisen yksinkertainen ja se suorittaa seuraavat kaksi komentosarjaa:

lerna bootstrap
lerna run build

Tämä vaihe toimii jokaisessa yksittäisessä toimikunnassa, jonka tarkoituksena on lähinnä validoida paketin järki. Julkaisuvaihe sen sijaan kulkee seuraavasti:

git checkout master
lerna bootstrap
git reset --hard
lerna publish from-package --yes

tajusimme, että jouduimme tekemään git checkout master ja git reset --hard, koska GitLab kloonaa (tai hakee kokoonpanosta riippuen) repon, ja sitten tarkistaa, mikä sitoumus on tarkoitus rakentaa. Tämä asettaa työhakemiston ”detached HEAD” – tilaan, eli ref HEAD ei osoita mihinkään. Lerna käyttää HEAD selvittääkseen paketin nykyisen version ja irtopäätilassa olevat virheet.

myös lerna publish from-package vastakohtana lerna publish, koska yksinkertaisen lerna publish olisi Lerna valittanut, että nykyinen versio on jo julkaistu, koska metatiedot olivat päivitetty, kun Kehittäjä juoksi lerna version paikallisesti. from-package argumentti käskee Lernaa julkaisemaan kaikki versiot, joita ei tällä hetkellä ole npm: ssä tietylle paketille. Tämä auttaa myös, Jos julkaisu epäonnistui jostain syystä ja olet uudelleen putki.

julkaisuvaihe on määritetty toimimaan vain tageilla, jotka vastaavat seuraavaa regex-krediittiä:

^v(0|\d*)\.(0|\d*)\.(0|\d*)(-(0|\d*|\d**)(\.(0|\d*|\d**))*)?(\++(\.+)*)?$

Tämä on hieman hienostunutta, ja useimmille joukkueille ja useimmille tarkoituksille yksinkertaisesti ^v*$ pitäisi toimia. 🙂

huomaa, että vaikka emme ole vielä tehneet sitä, koska olemme pieni tiimi, voi myös merkitä yllä olevaa regexiä seuraavat tägit suojatuiksi Gitlabissa rajoittamaan sitä, kuka voi julkaista paketteja npm: ään.

voit tarkistaa monorepomme osoitteesta https://github.com/mitterio/js-sdk (tämä on peilattu sisäisestä GitLab repostamme).

Pikamuistiinpanot

ajettaessa yleisiä skriptejä (kuten kirjoituskonekirjoitusten julkaisemista varten) on varsin hyödyllistä tietää skriptiä ajavan paketin tiedot. Tämä koskee skriptejä npm: n elinkaaressa sekä skriptejä, joita voidaan suorittaa käyttämällä lerna run tai lerna exec. Kun kyseessä on annettu paketti npm, npm antaa koko package.json ympäristömuuttujia käyttävän skriptin käyttöön. Niinpä tietylle paketille, jolla on seuraava package.json:

{
"name": "@mitter-io/core",
"version": "0.6.28",
"repository": {
"type": "git"
}
}

seuraavat muuttujat ovat käytettävissä ajettaessa mitä tahansa elinkaarikomppaniaa:

npm_package_name=@mitter-io/core
npm_package_version=0.6.28
npm_package_repository_type=git

Quirks/Issues

pari asiaa, joita vielä työstämme uuden kokoonpanon kanssa (osa niistä on ongelmia, kun taas osaa emme luultavasti vain tiedä paremmin):

  • ei ole varma, onko se mahdollista, mutta haluaisimme saada yhteiset elinkaarikäsikirjoitukset kaikkiin paketteihimme. Näiden julistaminen juuressa package.json ei toimi.
  • on äärimmäisen vaikeaa testata Lerna-setupia kokonaan ilman, että NPM: lle oikeasti julkaistaan jotain. Ei ole varma, onko --dry-run jossain.
  • Lernalla on tapana pitää yhteinen config-lohko devDependencies siten, että kaikki devDependencies ovat samaa versiota jokaiselle alapaketille. Tämä on melko viileä ominaisuus,mutta veisi meiltä jonkin aikaa karsia kaikki yhteiset.
  • sama voisi päteä myös muihin riippuvuuksiin, joten vaikka emme halua yhteistä dependencies config-lohkoa, olisi mukavaa, että eri projekteissa olisi mahdollisuus ilmaista muuttujia. Esimerkiksi Java/Kotlin monorepossamme käytetään gradle.properties sisältämään muuttujia, kuten springBootVersionspringCoreVersion jne., joita sitten käytetään yksittäisten gradle skriptejä.

ajatuksemme monoreposista
viime aikoina on käyty aika kiivasta väittelyä monoreposin kanssa ja siitä, näemmekö taas valtavan määrän hyppivän kelkkaan, mikä muistuttaa aika paljon aikaa, jolloin mikropalvelut olivat kaikki raivona.

tässä seuraamallamme rakenteella on useita monorepoja, eikä tämä ole
ensimmäinen kertamme monorepoksen hallinnassa. Koko alustamme ja taustajärjestelmämme on monorepo, joka sisältää yksityisiä, käyttöönotettavia koodeja ja useita julkisia paketteja, jotka julkaistaan bintraylle. Meillä on myös pääsivusto käynnissä Jousitaustan kanssa, jossa frontend on nipussa webpackin kanssa, joka tukee kuumaa uudelleenlatausta (webpack watch) jne. Emme koskaan päättäneet mennä Yhden mono-repo koko organisaation, koska työkalut yksinkertaisesti ei ollut siellä.

suurin osa Java-koodistamme yhdessä repossa toimii hyvin, koska gradle tarjoaa kaikki Java monorepon ja lerna ja NPM: n elinkaaren, joka tarjoaa työkalun JS SDK: n monorepolle. Niin, yksinkertaisesti sanottuna, monorepos ovat suuria, kun tunnistat kattavuus muutoksia, jotka menevät repo. Java-taustajärjestelmässämme näimme useita MRs: ää eri projekteissa yhden ominaisuuden vuoksi, mikä houkutteli meitä siirtymään monorepoon vain tässä nimenomaisessa projektissa, jossa kaikki muut koodimme ovat edelleen erillisissä repoissa. Ja kun näimme samanlaisen kuvion syntyvän myös JS SDKs: llemme, muutimme Lernaan.

huomaa, että olemme pieni noin 9 insinöörin tiimi, joten se mikä toimii meille, ei välttämättä toimi erikokoisissa tiimeissä. Haluamme lähinnä huomauttaa, että minkään ratkaisun ei tarvitse olla kaksijakoinen, jolloin joko teemme sen kuten on määrätty tai emme tee sitä lainkaan.

jotkut monorepolle näkemämme motiivit pätivät varmasti meihin ja monet eivät. Esimerkiksi, emme yksinkertaisesti voi säästää aikaa rakentaa työkaluja, jos meidän koko codebase siirrettiin yhden repo – riippumatta hyödystä saatamme tai ehkä ei koeta. Keskustelussa ei siis todellakaan ole kyse ”yksittäisestä reposta” — sinänsä kyse ei ole mistään muusta kuin uudesta hakemistorakenteesta. Niiden resepti on lievittää tiettyjä asioita ja kuten jokaisen ”Hopealuoti”, on olemassa varoituksia.

keskustelu käsittelee ohjelmistoalan yhteisiä kysymyksiä ja sitä, mitä ratkaisuja on yleisesti tehty; ”yhteinen” on avainsana. Alue, jossa poiketaan ”yhteisestä” sovelluksesta, on se, jossa saa innovoida, tehdä muutoksia ja rakentaa vähän.