Lucas F. Costa Menselijke gedachten over exacte wetenschappen

jan 16, 2022
admin

Hoi, iedereen! Zoals jullie misschien hebben gemerkt ben ik erg geinteresseerd in Go en sinds ik verliefd ben geworden op deze taal wil ik er vaker over schrijven.

Als je Go nog niet kent denk ik echt dat je het moet leren. Go is geweldig!

Go is een relatief jonge taal en als het aankomt op vendoring en dependency management is het nog niet echt volwassen. De Go gemeenschap heeft echter een aantal praktijken aangenomen en gereedschappen gemaakt om aan deze vraag te voldoen. Dit is waar we het vandaag over gaan hebben.

Uitleg over het Go Ecosysteem

Go heeft als doel om eenvoud te gebruiken om complexe taken te volbrengen en daarmee de taal leuker en productiever te maken. Vanaf het begin van de taal heeft het Go team ervoor gekozen om string literals te gebruiken om de import syntax willekeurig te maken om te beschrijven wat er geïmporteerd werd.

Dit is wat er in hun eigen docs staat:

Een expliciet doel voor Go vanaf het begin was om Go code te kunnen bouwen met alleen de informatie die in de broncode zelf staat, zonder een makefile te hoeven schrijven of een van de vele moderne vervangers voor makefiles. Als Go een configuratiebestand nodig zou hebben om uit te leggen hoe je je programma moet bouwen, dan zou Go gefaald hebben.

Om dit doel te bereiken, geeft het Go team de voorkeur aan conventies boven configuraties. Dit zijn de conventies die zijn aangenomen als het gaat om pakketbeheer:

  1. Het importpad is altijd op een bekende manier afgeleid van de URL van de broncode. Daarom ziet uw import eruit als: github.com/author/pkgname. Door dit te doen krijgen we ook een automatisch beheerde namespace, omdat deze online diensten al unieke paden voor elk pakket beheren.
  2. De plaats waar we pakketten opslaan in ons bestandssysteem is op een bekende manier afgeleid van het import pad. Als u zoekt naar waar go de pakketten opslaat die u downloadt, zult u het kunnen relateren aan de URL waarvandaan het werd gedownload.
  3. Elke directory in een broncodeboom komt overeen met een enkel pakket. Dit maakt het gemakkelijker voor u om broncode voor bepaalde pakketten te vinden en helpt u uw code op een gestandaardiseerde manier te organiseren. Door de mappenstructuur aan de pakketstructuur te koppelen, hoeven we ons niet over beide tegelijk zorgen te maken, omdat bestandssysteemgereedschappen pakketbeheergereedschappen worden.
  4. Het pakket wordt gebouwd met alleen informatie uit de broncode. Dit betekent geen makefiles en geen configuration en bevrijdt u van specifieke (en waarschijnlijk ingewikkelde) toolchains.

Nu we dat gezegd hebben, is het gemakkelijk te begrijpen waarom het go get commando werkt zoals het doet.

Gebruik van Go Tools’ Commando’s

Voordat we in deze commando’s duiken, laten we eerst begrijpen wat de “$GOPATH” is.

De $GOPATH is een omgevingsvariabele die wijst naar een directory die kan worden beschouwd als een werkruimtedirectory. Het zal uw broncodes, gecompileerde pakketten en runnable binaries bevatten.

go get

Het go get commando is eenvoudig en werkt bijna als een git clone. Het argument dat je moet doorgeven aan go get is een eenvoudige URL van een archief. In dit voorbeeld, zullen we het commando gebruiken: go get https://github.com/golang/oauth2.

Wanneer dit commando wordt uitgevoerd, zal go eenvoudigweg het pakket van de door u opgegeven URL ophalen en het in uw $GOPATH directory plaatsen. Als u naar uw $GOPATH directory navigeert, zult u nu zien dat u een folder heeft in src/github.com/golang/oauth2 die de bronbestanden van het package bevat en een gecompileerd package in de pkg directory (samen met zijn dependencies).

Wanneer u go get uitvoert, moet u in gedachten houden dat alle gedownloade pakketten in een directory worden geplaatst die overeenkomt met de URL die u gebruikte om het te downloaden.

Het heeft ook een hoop andere vlaggen beschikbaar, zoals -u dat een pakket bijwerkt of -insecure dat u toestaat om pakketten te downloaden met onveilige schema’s zoals HTTP. U kunt meer lezen over “geavanceerd” gebruik van het go get commando op deze link.

Ook, volgens go help gopath, werkt het go get commando ook submodules bij van de pakketten die u binnenhaalt.

go install

Wanneer go install in de bron-directory van een pakket wordt uitgevoerd, zal het de laatste versie van dat pakket en al zijn afhankelijkheden in de pkg directory compileren.

go build

Go build is verantwoordelijk voor het compileren van de pakketten en hun afhankelijkheden, maar het installeert niet de resultaten!

Vendoring

Zoals u misschien heeft gemerkt aan de manier waarop Go zijn afhankelijkheden opslaat, heeft deze benadering van afhankelijkhedenbeheer een paar problemen.

Op de eerste plaats zijn we niet in staat om te bepalen welke versie van een pakket we nodig hebben, tenzij het in een compleet andere repository wordt gehost, anders haalt go get altijd de laatste versie van een pakket op. Dit betekent dat als iemand een brekende verandering aan zijn pakket doet en het niet in een andere repo plaatst, u en uw team in de problemen komen omdat u uiteindelijk verschillende versies van hetzelfde pakket kunt ophalen en dit zal dan leiden tot “werkt op mijn machine” problemen.

Een ander groot probleem is dat, als gevolg van het feit dat go get pakketten installeert in de root van uw src directory, u niet in staat zult zijn om verschillende versies van uw afhankelijkheden te hebben voor elk van uw projecten. Dit betekent dat u geen projecten kunt hebben die afhankelijk zijn van verschillende versies van hetzelfde pakket, u zult of de ene of de andere versie tegelijk moeten hebben.

Om deze problemen te verzachten, heeft het Go team sinds Go 1.5 een vendoring functie geïntroduceerd. Met deze functie kunt u de code van uw afhankelijkheid voor een pakket in zijn eigen directory zetten, zodat het altijd dezelfde versies krijgt voor alle builds.

Stel dat u een project hebt met de naam awesome-project dat afhankelijk is van popular-package. Om te garanderen dat iedereen in je team dezelfde versie van popular-package zal gebruiken, kun je de broncode ervan in $GOPATH/src/awesome-project/vendor/popular-package zetten. Dit zal werken omdat Go dan zal proberen het pad van uw afhankelijkheden op te lossen vanuit de vendor directory van zijn eigen map (als het tenminste één .go bestand heeft) in plaats van $GOPATH/src. Dit zal uw builds ook deterministisch (reproduceerbaar) maken, aangezien ze altijd dezelfde versie van popular-package.

Het is ook belangrijk om op te merken dat het go get commando de gedownloade pakketten niet automatisch in de vendor map zal zetten. Dit is een taak voor vendoring tools.

Wanneer u vendoring gebruikt, kunt u dezelfde import paden gebruiken als wanneer u dat niet zou doen, omdat Go altijd zal proberen om afhankelijkheden te vinden in de dichtstbijzijnde vendor directory. Het is niet nodig om vendor/ voor te schrijven aan een van uw import paden.

Om volledig te kunnen begrijpen hoe vendoring werkt, moeten we het algoritme begrijpen dat Go gebruikt om import paden op te lossen, en dat is het volgende:

  1. Kijk voor de import in de lokale vendor directory (indien aanwezig)
  2. Als we dit pakket niet in de lokale vendor directory kunnen vinden, gaan we naar de bovenliggende map en proberen het daar in de vendor directory te vinden (als die er is)
  3. Als we dit pakket niet in de lokale vendor directory kunnen vinden, gaan we naar de bovenliggende map en proberen het daar in de vendor directory te vinden (als
  4. We herhalen stap 2 totdat we $GOPATH/src
  5. We zoeken naar het geïmporteerde pakket in $GOROOT
  6. Als we dit pakket niet kunnen vinden in $GOROOT zoeken we het in onze $GOPATH/src map

Basically, betekent dit dat elk pakket zijn eigen afhankelijkheden kan hebben, opgelost in zijn eigen vendor directory. Als u bijvoorbeeld afhankelijk bent van package x en package y, en package x is ook afhankelijk van package y maar heeft er een andere versie van nodig, dan kunt u uw code nog steeds zonder problemen uitvoeren, omdat x naar y zal zoeken in zijn eigen vendor map, terwijl uw package naar y zal zoeken in de vendor map van uw project.

Nu, een praktisch voorbeeld. Laten we zeggen dat je deze mapstructuur hebt:

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go vendor/ github.com/user/package-one/ one.go

Als we github.com/user/package-one importeren vanuit main.go zouden importeren, zou het oplossen naar de versie van dit pakket in de vendor map in dezelfde map:

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go vendor/ github.com/user/package-one/ one.go

Nu als we hetzelfde pakket importeren in client.go zal het ook dit pakket oplossen naar de vendor map in zijn eigen map:

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here server/ server.go vendor/ github.com/user/package-one/ one.go

Hetzelfde gebeurt als we dit pakket importeren in het server.go bestand:

$GOPATH src/ github.com/user/package-one/ one.go myproject main.go vendor/ github.com/user/package-one/ one.go client/ client.go vendor/ github.com/user/package-one/ server/ server.go <-- Importing package-one from here vendor/ github.com/user/package-one/ <-- resolves to here one.go

Uitleg over afhankelijkheidsbeheer

Nu we al deze dingen hebben geleerd over hoe Go omgaat met import en vendoring, wordt het tijd dat we het eindelijk gaan hebben over afhankelijkheidsbeheer.

De tool die ik momenteel gebruik om afhankelijkheden in mijn eigen projecten te beheren heet godep. Het schijnt erg populair te zijn en het werkt prima voor mij, dus ik raad je ten zeerste aan om dat ook te gebruiken.

Het is gebouwd rond de manier waarop vendoring werkt. Alles wat je moet doen om het te gaan gebruiken is het commando godep save gebruiken wanneer je je afhankelijkheden wilt opslaan in je vendor map.

Wanneer je godep save uitvoert, zal godep een lijst van je huidige afhankelijkheden opslaan in Godeps/Godeps.json en vervolgens hun broncode kopiëren naar de vendor map. Het is ook belangrijk op te merken dat u deze afhankelijkheden op uw machine geïnstalleerd moet hebben om godep in staat te stellen ze te kopiëren.

Nu kunt u de vendor map en zijn inhoud vastleggen om ervoor te zorgen dat iedereen dezelfde versies van dezelfde pakketten zal hebben wanneer u uw pakket uitvoert.

Een ander interessant commando is godep restore, dat de versies die in uw Godeps/Godeps.json bestand staan, zal installeren in uw $GOPATH.

Om een dependency te updaten, hoeft u deze alleen maar te updaten met go get -u (zoals we eerder hebben besproken) en dan godep save uit te voeren, zodat godep het Godeps/Godeps.json bestand zal updaten en de benodigde bestanden naar de vendor directory zal kopiëren.

Een paar gedachten over de manier waarop Go omgaat met afhankelijkheden

Aan het eind van deze blog post, wil ik ook graag mijn eigen mening geven over de manier waarop Go omgaat met afhankelijkheden.

Ik denk dat Go’s keuze om externe repositories te gebruiken om de namespaces van pakketten af te handelen geweldig was omdat het de hele package resolutie veel eenvoudiger maakt door de bestandssysteem concepten te verbinden met de namespace concepten. Dit zorgt er ook voor dat het hele ecosysteem onafhankelijk werkt, omdat we nu een gedecentraliseerde manier hebben om pakketten op te halen.

Het gedecentraliseerde pakketbeheer heeft echter een prijs, namelijk dat we niet in staat zijn om alle knooppunten te controleren die deel uitmaken van dit “netwerk”. Als iemand besluit om zijn pakket uit github te halen, bijvoorbeeld, dan kunnen honderden, duizenden of zelfs miljoenen builds plotseling beginnen te falen. Naamsveranderingen kunnen ook hetzelfde effect hebben.

Gezien Go’s hoofddoelen is dit volkomen logisch en een volkomen eerlijke afweging. Go draait om conventie in plaats van configuratie en er is geen eenvoudiger manier om met afhankelijkheden om te gaan dan de manier waarop het nu doet.

Natuurlijk kunnen er een paar verbeteringen worden aangebracht, zoals het gebruik van git tags om specifieke versies van pakketten op te halen en gebruikers toe te staan om te specificeren welke versies hun pakketten zullen gebruiken. Het zou ook cool zijn om in staat te zijn om deze afhankelijkheden op te halen in plaats van ze uit te pluizen op broncontrole. Dit zou ons in staat stellen om vuile diffs te vermijden die alleen wijzigingen aan code in de vendor directory bevatten en het hele repository schoner en lichter te maken.

Get in touch!

Als je twijfels of gedachten hebt of het niet eens bent met iets wat ik heb geschreven, deel het dan met me in de reacties hieronder of bereik me op @thewizardlucas op twitter. Ik zou graag horen wat je te zeggen hebt en eventuele correcties doen als ik fouten heb gemaakt.

Tot slot, maar niet in het minst, zorg ervoor dat je een kijkje neemt op deze geweldige talk van Wisdom Omuya op GopherCon 2016 waarin hij uitlegt hoe Go Vendoring werkt en ook wijst op een aantal details over de innerlijke werking ervan.

Dank voor het lezen van dit!

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.