Alla som jobbar inom områden där man kommer i kontakt med systemutveckling och tekniken bakom denna kommer om de inte redan hört talas om det höra om mikrotjänster eller micro services som vi kallar det på utrikiska. Principen bakom mikrotjänster är att varje tjänst är väldigt bra på en och endast en sak. Detta ger oss som jobbar med att konstruera IT-system några nya utmaningar och möjligheter.
Meningen med mikrotjänster är att de är små
Det finns en trend som visar på att de verktyg som förut bara blev större och ännu större för att kunna göra allt nu bytt riktning och blir mindre och mindre i stället. Många av de tekniker vi tidigare använde för att bygga lösningar har i sina senare versioner blivit mindre och mer anpassade för att fungera i sammanhang med teknik där kraven på infrastruktur minskat i stället för ökat.
När många system uppdateras passar man gärna på att bryta ut delar av de stora systemen i mindre tjänster och då kan man också välja att utmana vilka utvecklingsmiljöer man använder.
Varför finns go när det finns så många andra språk?
Ska man utveckla en tjänst på backendsidan så vill man gärna ha kod som är så enkel som möjligt att läsa och som kör så snabbt som möjligt. Det ska finnas ett bra standardbibliotek och paket man kan koppla på vid behov som aktivt utvecklas och följer teknikutvecklingen. Det ska också finnas något bra ramverk att bygga sina lösningar ovanpå.
Det som för mig är intressant med Go är dessa fördelar:
- Man skapar binärfiler som är enkla att distribuera och som inte är beroende av en runtimemiljö som kan krocka i versioner.
- Språket är kompilerat, vilket gör att man i grunden har en bra prestanda. All bra prestanda förutsätter dock välskrivna program.
- Starkt typat, vilket gör att man inte stavar fel på fältnamn i någon underliggande datastruktur, vilket i och för sig hör till att det är kompilerat.
- Syntaxen är liten och lite rå, vilket gör att den är lätt att lära sig och man har lätt att förstå hur koden fungerar när man läser kod någon annan skrivit eller kod man kanske själv skrivit för flera år sedan.
- Hanteringen är byggd för att jobba med flera trådar med samtidig körning och infrastrukturen så som hur man kopplar upp mot databaser är gjord så att man inte riskerar göra fel vid hantering av samtidighet som annars är lätt att gå vilse i.
- Tar lite minne och väldigt lite CPU, kräver mindre hårdvara.
Det som får mig att vara tveksam är:
- Inte lika googlingsbart som många andra språk.
- Lite knepigt att navigera bland paket, det finns paket som ingår i grundbiblioteket och så finns det sådana som ligger utanför som verkar göra samma sak.
- Det finns redan så många språk och tekniker vi redan använder, har vi verkligen plats för en till även om den är snabbare än det mesta annat?
Ett ramverk för att slippa uppfinna allt
För att något ska fungera i en driftmiljö så ställs det ytterligare krav, men dessa hamnar snarare på ramverket man använder är hur själva språket är skapat. En viktig del när det gäller all typ av utveckling i miljöerna är att man inte bara väljer en grundfunktion och lägger på alla paket man tycker passar in, men det finns flitiga människor som sätter samman dessa paket som passar bra ihop till ett ramverk så att man får med sig mycket färdigt.
Idag är det nästan givet när man arbetar i miljöer med Javascript för frontend att man inte bara använder "react" som ett bibliotek, man går oftast direkt på att använda "nextjs" som ett paket. Samma sak gäller go, om än kanske inte lika tydligt.
Har tittat på några olika och fastnade för ett som heter gin. Det är ett ramverk som fokuserar på hastighet och enkelhet så att koden man skriver blir enkel och läsbar samtidigt som den blir snabb och stabil för att kompensera för utmaningarna i standardbiblioteket i go.
Språkets struktur
När man konstruerade go så har man inspirerats av C och Python och det märks. Man finner många delar som går att känna igen när man jobbar i de språken och för mig som arbetat med C i väldigt många år så var tröskeln till go väldigt låg.
Deklarationer av variabler sker med nyckelordet efteråt. Om man ska deklareraa flera variabler av samma typ, då kan man skriva dem på en lista och avsluta med kommatecken. För att fylla på värden i en struktur är syntaxen för att skapa en instans av en variabel också väldigt effektiv
// Deklarera en variabel som en sträng
firstName string
// Deklarera två heltal
firstNumber, secondNumber int
// Ta tillbaka två variabler från ett funktionsanrop med sina respektive typer
product, errorState := FindProductByPartNo(partNo)
// Deklarera en funktion som returnerar två objekt
func FindProductByPartNo(partNo string) Product, string {
// Gör en massa saker
}
// Skapa en produkt med förifyllda värden
product Product{
PartNo: "FREIGHT",
Name: "Fraktartikeln",
}
Det jag gillar är att man tagit bort en hel del som annars finns och ersätter det med enklare skrivningar i stället, till exempel finns inte något while, man får i stället använda for.
// Skriv ut Hello world 1, Hello world 2 osv tom 10
// Variabeln i definieras med ett := då 0 implicit är ett heltal
for i:=1; i<=10; i++ {
fmt.Println("Hello world %d", i)
}
Används lika gärna bara med en villkorssats, man kan utelämna initiering och uppräkning och då behövs ingen while.
for rows.Next() {
var product Product
err = rows.Scan(&product.PartNo, &product.Name)
// Gör något med resultatet
}
För den som jobbat med C är pekare något som sitter i ryggmärgen och som man i de flesta språk valt att abstrahera bort. I go finns dessa pekare och det finns de klassiska * för att nå värdet och & för att hitta till adressen. Det ser man i exemplet ovan när man definierar en variabel för en produkt och sedan ger adressen till PartNo och Name ovan som då sätter värden på fälten.
Man behöver inte jobba med klasser för att jobba med datastrukturer, det är helt enkelt bara en struktur med fältnamn. Med det inbyggda stödet för tolkning med json kan man enkelt ange vad fältet ska heta vid serialisering och deserialisering, likväl som att man kan välja att utelämna ett fält om det inte har något värde.
type Product struct {
PartNo string `json:"part_no"`
Name string `json:"name"`
}
Alla delar i en kod man vill dela med sig av med omvärlden börjar med stor bokstav och alla privata börjar med liten bokstav och så är det med det. Det märks också hur smidigt det är att jobba med typerna som är implicita, lite som vi i C# jobbar med var.
func DoMeaningOfLifeCalculation() {
meaningOfLife := calculateSum(36, 6)
return meaningOfLife
}
func calculateSum(a, b int) int {
return a + b
}
Man kan redan när man skapar ett objekt bestämma vad som ska hända när man kliver ur en funktion med nyckelordet defer, vilket gör att om man öppnar upp en koppling kan man på raden efter skriva en sats som stänger den och att detta sedan exekveras när man klivit ur funktionen oavsett när man kliver ur funktionen.
file := OpenFileOnDisk(path)
defer CloseFileOnDisk(file)
line := readFirstLine(file)
En liten klurig sak är att när man arbetar med arrayer och listor så är dessa fasta i sin längd och man kan inte bara fylla på i slutet av dem som man gör i andra språk. Det finns fina funktioner i ramverket och med paket för att komma runt den begränsningen, men det är som jag förstått en av nycklarna till att språket kan vara effektiv i användning av minne och prestanda.
Man kan dela upp sin kod i flera filer utan att det blir komplicerat, även om det till en början är lite förvirrande med hur en funktion kan finnas i vilken fil som helst i samma katalog. Med pakethanteringen så är det verkligen enkelt att kunna återanvända kod i sina egna moduler.
Hur ser då en hello world mikrotjänst ut?
När jag skapar en mikrotjänst vill jag smidigt kunna använda en funktion som ser till att den går stabilt, har loggning, möjligheter att lägga på authentisering och auktorisering som styrs av någon av de andra tjänsterna i lösningen. Kommunikation mellan tjänster ska vara möjligt med i mitt fall Rabbit MQ, men i denna enklaste form av Hello World ser den ut så här med go + gin.
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type World struct {
Hello string
}
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, World{
Hello: "World",
})
})
r.Run()
}
Vi startar upp den med:
Vi testar att den fungerar:
Vad blir nästa steg?
Själv kommer jag fortsätta jobba med go för fler tjänster och framför allt kommer den att ersätta en del av det jag gör som Javascript idag. Det är väldigt enkelt att få ut en mikrotjänst i våra driftmiljöer.