Go proqramlaşdırma dili — faydalı xarici paketlər (libraries)

Rashid Alyarov
PASHA Bank
Published in
7 min readApr 19, 2020

Təqribən iki ilə yaxın zaman çərçivəsində PAŞA bankda işləməyin nəticəsi olaraq son texnologiyalar və onların ən yaxşı tətbiqini (best practice) həyata keçirmək məni Go dilini öyrənməyə vadar etdi. Bugün hal hazırda tətbiq etdiyimiz mikroservis arxitekturasında istifadə etdiyimiz bu dilin bəzi kitabxanalarından danışacağıq. Sözü gedən paketlər Go dilində kod yazmanı daha da bəsitləşdirir və eyni zamanda dilin kompleks tərəflərindən maksimal dərəcədə istifadəsinə icazə verir. Beləliklə, danışacağımız paketlər növbətilərdir:

1. Go-arg / Strukturlu arqument parseri

Github səhifə

Go-arg komanda sətri ilə idarə olunan və ya istənilən proqram tətbiqində istifadə oluna biləcək faydalı bir paketdir.

Burada komanda sətri arqumentlərini struktur bəlirləməklə elan edib başlayırıq:

var args struct {
GoPath string `arg:"env:GOPATH"`
Dataset string `help:"dataset to use" arg:"required"`
Foo bool `default:"false"`
}

Strukturun içində qeyd olunan parametrlər, komanda sətrində qeyd elədiyimiz boolean və ya mühit dəyişənləri ola bilər. Struktur hazır olduqda onu paketin içindəki arg.MustParse(&args) funksiyanı cağıraraq, göstərilən dəyişənləri kodumuzda istifadə etmək mümkündür.

package main

import (
"fmt"

"github.com/alexflint/go-arg"
)

func main() {
var args struct {
GoPath string `arg:"env:GOPATH"`
Dataset string `help:"dataset to use" arg:"required"`
Foo bool `default:"false"`
}
arg.MustParse(&args)
fmt.Println("go path: ", args.GoPath)
fmt.Println("Foo: ", args.Foo)
fmt.Println("Dataset: ", args.Dataset)
}

Geriyə qalan

go run main.go --dataset=dtst --foo=true

komandasını işlətməklə nəticəni əldə etməkdir.

go path:  /Users/ralyarov/go
Foo: true
Dataset: dtst

2. Logrus / Strukturlu logger (standart logger kitabxanası ilə uyğunlaşan)

Github səhifə

Bildiyimiz kimi ən yaxşı yazılmış proqram təminatında belə səhvlər çıxa bilər. Bunun qarşısını almaq və tez həllini tapmaq üçün loglardan istifadə etmək mütləqdir. Go dilində bu mövzunun uzərində işləmək üçün standart log paketi var, lakin bu paket yalnız məhdudlaşdırılmış məsələ toplusunun öhtəsindən gəlir. Bunun üçün populyar və istifadəsi rahat paketlərdən biri logrus paketidir.

Logrus-dan aşağıda göstərildiyi kimi istifadə etmək olar.

package main

import (
log "github.com/sirupsen/logrus"
)

func main() {
// İstədiyimiz log səviyyəsini təyin edə bilərik: Debug, Trace, Error və s.
// Qeyd olunan səviyyə və daha vacib səvviyədə olan loglar əks olunacaq
log.SetLevel(log.DebugLevel)

log.Debug("Standart format")
log.Trace("Trace səvviyəsində olan log ekranda əks olunmur")
log.Info("Info debug-dan daha vacib səviyyə olduğunnan ekranda əks olunur")

// Format edərək bizə lazımı formatda məlumatı əks etdirə bilərik
log.SetFormatter(&log.JSONFormatter{})

log.Debug("Json formatında olan log")

// Yeni parametr əlavə etmək istədikdə WithFields function-u istifadə olunur
myLogger := log.WithFields(log.Fields{
"USER_IP": "0.0.0.0",
"USER_ID": 123,
})
myLogger.Debug("Userin əlavə məlumatları")
}

Nəticə olaraq:

DEBU[0000] Standart format                              
INFO[0000] Info debug-dan daha vacib səviyyə olduğunnan ekranda əks olunur
{"level":"debug","msg":"Json formatında olan log","time":"2020-04-17T17:30:21+04:00"}
{"USER_ID":123,"USER_IP":"0.0.0.0","level":"debug","msg":"Userin əlavə məlumatları","time":"2020-04-17T17:30:21+04:00"}

3. Testify / Go kodunu test etmək üçün paket toplusu

Github səhifə

İstər mikroservis, istərsə də monolit arxitekturasında unit testlərin olmağı vacib amillərdən biridir. Go dilində test yazılışını sadələşdirən bir çox paketlər var. Bunlardan ən məhşurlarının gomock və testify olduğunu demək olar. Hər iki paketdən istifadə etdikdən sonra testify daha rahat olduğu qərarına gəldim. Beləki, gomock istifadə etmək üçün mock obyektlər generasiya etmək və bunun üçün əlavə komandalar yadda saxlamaq tələb olunur. Testify isə bu işi çox sadələşdirir.

Yazılmış testləri

go test main_test.go

komandası vasitəsi ilə işlədə bilərik. Komandada main_test.go faylı belə görsənir:

package main

import (
"testing"

assert "github.com/stretchr/testify/assert"
)

func TestSomething(t *testing.T) {
assert := assert.New(t)

assert.Equal(3, 3, "3 və 3 bərabər olmalıdırlar")

assert.Equal(1, 2, "1 və 2 bərabər olmalıdırlar")

assert.NotEqual(3, 3, "3 və 3 bərabər olmamalıdırlar")

assert.NotEqual(1, 2, "1 və 2 bərabər olmamalıdırlar")

type person struct{}
p := person{}
assert.NotNil(p, "Nil olmamalıdır")
assert.NotNil(nil, "Nil olmamalıdır")
}

Nəticə

--- FAIL: TestSomething (0.00s)
main_test.go:14:
Error Trace: main_test.go:14
Error: Not equal:
expected: 1
actual : 2
Test: TestSomething
Messages: 1 və 2 bərabər olmalıdırlar
main_test.go:16:
Error Trace: main_test.go:16
Error: Should not be: 3
Test: TestSomething
Messages: 3 və 3 bərabər olmamalıdırlar
main_test.go:23:
Error Trace: main_test.go:23
Error: Expected value not to be nil.
Test: TestSomething
Messages: Nil olmamalıdır
FAIL
FAIL command-line-arguments 2.144s
FAIL

4. Govalidator / Verilənlərin validasiyası üçün sadə qaydalar toplusu

Github səhifə

Govalidator köməyi ilə verilənlər şərti yoxlamaların (if-else-statements) əvəzinə, paketin verdiyi funksionallıqla yazılan qaydalara əsasən yoxlanışdan keçir. Xırda bir web proqram yazmaqla validasiyanı nümayiş etmək mümkündür.

package main

import (
"encoding/json"
"fmt"
"net/http"

"github.com/thedevsaddam/govalidator"
)

func handler(w http.ResponseWriter, r *http.Request) {
// Yoxlama olunmalı field-lər və onlara uyğun qaydalar
rules := govalidator.MapData{
"username": []string{"required", "between:3,8"},
"email": []string{"required", "min:4", "max:20", "email"},
"phone": []string{"digits:12"},
"dob": []string{"date"},
}

// Verilən parametrlərə və qaydalara uyğun validation keçmədiyi təqdirdə qaytarılacaq message
messages := govalidator.MapData{
"username": []string{"required:İstifadəçi adını daxil etmək mütləqdir!",
"between:Daxil edilən istifadəçi adı 3-8 simvol arası olmalıdır"},
"phone": []string{"digits:Mobil nömrə yalnız rəqəmlərdən ibarət olmalıdır və uzunluğu 12 simvol olmalıdır"},
}
opts := govalidator.Options{
Request: r, // request obyetkti
Rules: rules, // validation qaydaları
Messages: messages, // qaytarilacaq message (Vacib parametr deyil)
RequiredDefault: true, // bütün parametrlər qaydalara uyğun olmalıdır
}
v := govalidator.New(opts)
e := v.Validate()
err := map[string]interface{}{"validationError": e}
w.Header().Set("Content-type", "application/json")
json.NewEncoder(w).Encode(err)
}

func main() {
http.HandleFunc("/", handler)
fmt.Println("Listening on port: 9000")
http.ListenAndServe(":9000", nil)
}

İlkin olaraq 9000 portunda proqramı qaldırırıq. Sonra isə aşağıdaki komanda ilə sorğu göndəririk.

curl GET “http://localhost:9000?username=&phone=994501111111&dob="

Nəticə:

{
"validationError": {
"dob": [
"The dob field must be a valid date format. e.g: yyyy-mm-dd, yyyy/mm/dd etc"
],
"agree": [
"The agree field is required"
],
"email": [
"The email field is required",
"The email field must be minimum 4 char",
"The email field must be a valid email address"
],
"username": [
"İstifadəçi adını daxil etmək mütləqdir!",
"Daxil edilən istifadəçi adı 3-8 simvol arası olmalıdır"
]
}
}

5. Sql-migrate / SQL sxemalarının miqrasiyası paketi

Github səhifə

Günümüzdə ən xırda layihəni belə verilənlər bazası olmadan təsəvvür etmək olmur. Layihədə istifadə olunan cədvəllər haqqında məlumatı kod daxili saxlamaq işimizi çox rahatlaşdırmaqdan savayı səliqəliliyin və ardıcıllığın qorunmasını təmin edir. Sql-migrate paketidə məhz bu məqsədlərə çatmağda bizə kömək edir. Aşağıda qeyd olunan kimi verilənlər bazasına qoşuluruq və daha sonra yaratdığımız sql skriptlər olan faylı icra edirik. Bu misalda istifadə olunan baza postgresql-dir, ancaq sql-migrate paketi bir çox bazaları dəstəkləyir.

package main

import (
"database/sql"
"log"

_ "github.com/lib/pq"
migrate "github.com/rubenv/sql-migrate"
)

func migrateDb() {
// Postgres bazaya qoşulmaq üçün gərəkli məlumatlar
connStr := "host=localhost port=5432 dbname=test user=postgres sslmode=disable"

db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal("OpenConnection error: ", err)
}
defer db.Close()

// Xarici sql file-da qeyd olunmuş skriptlərin miqrasiyası
migrations := &migrate.FileMigrationSource{
Dir: "./",
}

// Kod ilə skriptlərin miqrasiyası
//migrations := &migrate.MemoryMigrationSource{
// Migrations: []*migrate.Migration{
// &migrate.Migration{
// Id: "123",
// Up: []string{"CREATE TABLE person (id int)"},
// Down: []string{"DROP TABLE person"},
// },
// },
//}

_, err = migrate.Exec(db, "postgres", migrations, migrate.Up)
if err != nil {
log.Fatal("Exception.migrateDb.Apply: ", err)
}

}

func main() {
migrateDb()
}

create_person_table.sql faylı:

-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
create table person
(
id bigint. primary key,
name varchar(16) not null,
phone varchar(32) not null,
email varchar(32) not null,
constraint uk_id
unique (id)
);

Nəticədə iki ədəd cədvəl yaradılır: person və gorp_migration. Skriptdə qeyd etdiyimiz "person" cədvəli:

select * from person;id | name | phone | email----+------+-------+-------(0 rows)

Miqrasiya olunma haqqında məlumatların əks olunduğu “gorp_migration” cədvəli:

select * from gorp_migrations;id            |          applied_at-------------------------+-------------------------------create_person_table.sql | 2020-04-18 17:10:03.329912+00

6. GORM / Go üçün ORM kitabxanası

Github səhifə

ORM(Object-Relational Mapping) cədvəllərdə məlumatların idarə olunması üçün hazırda olan ən müasir yanaşmadır. GORM paketi ilə Go proqramlaşdırma dilində bu məntiqi çox sadə üsülla reallaşdırmaq mümkündür. Bu misalda daha öncə yaratdığımız Person cədvəlinnən istifadə edəcəyik.

package main

import (
"fmt"

"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)

// Person cədvəli üçün struktur
type Person struct {
Id int
Name string
Phone string
Email string
}


// Cədvəlin adını vermədiyimiz təqdirdə strukturun cəm forması qeyd olunur (People)
// Bu səbəbdən cədvəlin adını belə formada override etmək lazımdır
func (Person) TableName() string {
return "person"
}

func main() {
db, err := gorm.Open("postgres", "host=localhost port=5432 dbname=test user=postgres sslmode=disable")
if err != nil {
panic("failed to connect database")
}
defer db.Close()

// Cədvəldə yeni yazı yaratmaq üçün
db.Create(&Person{Id: 1, Name: "Rashid", Phone: "994501111111", Email: "rasidaliyarov@gmail.com"})
db.Create(&Person{Id: 2, Name: "Mushviq", Phone: "994500000000", Email: "mushvig.niftaliyev@gmail.com"})

// Cədvəldən oxuma
var person Person
var person2 Person
db.First(&person, 1) // person id 1 olan yazını əldə etmək
fmt.Println(person)

db.First(&person2, "name = ?", "Mushviq") // person name Mushviq olan yazını əldə etmək
fmt.Println(person2)

// Əldə olunan person yeniləmək
db.Model(&person).Update("Phone", "994502222222")
fmt.Println(person)

// person yazısını silmək
db.Delete(&person)
}

Nəticə olaraq

{1 Rashid 994501111111 rasidaliyarov@gmail.com}
{2 Mushviq 994500000000 mushvig.niftaliyev@gmail.com}
{1 Rashid 994502222222 rasidaliyarov@gmail.com}

və cədvəldə olan məlumat

select * from person;id |  name   |    phone     |            email----+---------+--------------+------------------------------2 | Mushviq | 994500000000 | mushvig.niftaliyev@gmail.com(1 row)

7. GoCron / GoLang “Job”larının planlayıcısı (Job Scheduling) üçün paket

Github səhifə

Eyni tapşırıqları bəlli bir vaxt aralığı ərzində təkrarlamaq üçün işlər (jobs) yazıla bilər. Bu işləri yazmaq üçün gocron planlayıcısından (scheduler) istifadə edilir. Aşağıdakı misala nəzər yetirə bilərsiniz.

package mainimport (
"fmt"
"time"
"github.com/jasonlvhit/gocron"
)
func task() {
fmt.Println("I am running task.")
}
func taskWithParams(a int, b string) {
fmt.Println(a, b)
}
func main() {
// Parametrləri olmayan funksiyalar
gocron.Every(1).Second().Do(task)
gocron.Every(2).Minutes().Do(task)
gocron.Every(1).Day().Do(task)
// Parametrli funksiyalar
gocron.Every(1).Second().Do(taskWithParams, 1, "hello")
// İstənilın həftənin günü təkrarlanmaqla
gocron.Every(1).Monday().Do(task)
// Spesifik vaxtda təkrarlanma ilə - 'hour:min:sec' - saniyə vacib deyil
gocron.Every(1).Day().At("10:30").Do(task)
gocron.Every(1).Monday().At("18:30").Do(task)
gocron.Every(1).Tuesday().At("18:30:59").Do(task)
// Saatlıq tapşırığın dərhal başlaması
gocron.Every(1).Hour().From(gocron.NextTick()).Do(task)
// Spesifik tarixdə/vaxtda başlanılması istənilən tapşırıq
t := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.Local)
gocron.Every(1).Hour().From(&t).Do(task)
// Tapşırığı silmək istədikdə
//gocron.Remove(task)
// Bütün tapşırıqları təmizləmək istədikdə
//gocron.Clear()
// Bütün tapşırıqları başlatmaq istədikdə
<- gocron.Start()
}

Sadalanan paketlər mürəkkəb kod məntiqini sadə dildə yazmağa, eləcə də oxunaqlı formaya salmağa kömək edir. Ümid edirəm ki, bu paketlər sizin kod yazma rutininizi daha da rahatlaşdıracaq.

--

--