Go (Golang) - wydajne API i serwisy backendowe
Go (Golang) wydajne
backendGo (Golang) - wydajne API i serwisy backendowe
Go, znany również jako Golang, to język programowania stworzony przez Google w 2009 roku. Zaprojektowany przez Roberta Griesemera, Roba Pike'a i Kena Thompsona, Go powstał jako odpowiedź na frustracje związane z istniejącymi językami używanymi w infrastrukturze Google. Celem było stworzenie języka, który łączy prostotę Pythona z wydajnością C++, jednocześnie oferując natywne wsparcie dla współbieżności i szybką kompilację.
Dziś Go jest jednym z najpopularniejszych języków do budowy mikroserwisów, API, narzędzi CLI i systemów rozproszonych. Używają go firmy takie jak Google, Uber, Dropbox, Twitch, Docker i Kubernetes - a właściwie sam Kubernetes jest napisany w Go.
Dlaczego Go zyskał popularność?#
Go wyróżnia się kilkoma kluczowymi cechami:
- Prostota składni - minimalna ilość słów kluczowych, brak dziedziczenia klas
- Szybka kompilacja - duże projekty kompilują się w sekundach
- Statyczne typowanie - błędy wykrywane na etapie kompilacji
- Wbudowana współbieżność - goroutines i kanały jako elementy języka
- Pojedynczy binark - brak zależności runtime, łatwy deployment
- Garbage collector - automatyczne zarządzanie pamięcią z niskimi opóźnieniami
- Bogata biblioteka standardowa - serwer HTTP, JSON, kryptografia, testowanie
Goroutines i kanały - model współbieżności#
Goroutines to lekkie wątki zarządzane przez runtime Go. Są znacznie tańsze niż wątki systemowe - można uruchomić setki tysięcy goroutines na zwykłym serwerze. Każda goroutine zaczyna z zaledwie 2 KB stosu, który dynamicznie rośnie w miarę potrzeb.
Podstawowe goroutines#
package main
import (
"fmt"
"sync"
"time"
)
func fetchData(source string, wg *sync.WaitGroup) {
defer wg.Done()
// Symulacja pobierania danych
time.Sleep(100 * time.Millisecond)
fmt.Printf("Pobrano dane z: %s\n", source)
}
func main() {
var wg sync.WaitGroup
sources := []string{
"https://api.users.example.com",
"https://api.orders.example.com",
"https://api.products.example.com",
"https://api.payments.example.com",
}
start := time.Now()
for _, source := range sources {
wg.Add(1)
go fetchData(source, &wg)
}
wg.Wait()
fmt.Printf("Wszystkie zapytania zakończone w: %v\n", time.Since(start))
// ~100ms zamiast ~400ms sekwencyjnie
}
Kanały (Channels)#
Kanały umożliwiają bezpieczną komunikację między goroutines. Zamiast współdzielenia pamięci, goroutines komunikują się przez przesyłanie wiadomości - zgodnie z mottem Go: „Nie komunikuj się przez współdzielenie pamięci; współdziel pamięć przez komunikację".
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
type Result struct {
Source string
Data interface{}
Error error
}
func fetchAPI(url string, ch chan<- Result) {
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(url)
if err != nil {
ch <- Result{Source: url, Error: err}
return
}
defer resp.Body.Close()
var data interface{}
json.NewDecoder(resp.Body).Decode(&data)
ch <- Result{Source: url, Data: data}
}
func aggregateData(urls []string) []Result {
ch := make(chan Result, len(urls))
for _, url := range urls {
go fetchAPI(url, ch)
}
results := make([]Result, 0, len(urls))
for i := 0; i < len(urls); i++ {
results = append(results, <-ch)
}
return results
}
func main() {
urls := []string{
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/users/1",
"https://jsonplaceholder.typicode.com/comments/1",
}
results := aggregateData(urls)
for _, r := range results {
if r.Error != nil {
fmt.Printf("Błąd z %s: %v\n", r.Source, r.Error)
} else {
fmt.Printf("Sukces z %s\n", r.Source)
}
}
}
Select - multipleksowanie kanałów#
Instrukcja select pozwala oczekiwać na wiele operacji kanałowych jednocześnie:
func fetchWithTimeout(url string, timeout time.Duration) (*http.Response, error) {
ch := make(chan *http.Response, 1)
errCh := make(chan error, 1)
go func() {
resp, err := http.Get(url)
if err != nil {
errCh <- err
return
}
ch <- resp
}()
select {
case resp := <-ch:
return resp, nil
case err := <-errCh:
return nil, err
case <-time.After(timeout):
return nil, fmt.Errorf("timeout po %v", timeout)
}
}
Serwer HTTP z biblioteki standardowej#
Go ma wbudowany, produkcyjny serwer HTTP. W przeciwieństwie do wielu języków, nie potrzebujesz zewnętrznego frameworka, aby uruchomić wydajny serwer webowy:
package main
import (
"encoding/json"
"log"
"net/http"
"strings"
"sync"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type UserStore struct {
mu sync.RWMutex
users map[int]User
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]User),
nextID: 1,
}
}
func (s *UserStore) Create(user User) User {
s.mu.Lock()
defer s.mu.Unlock()
user.ID = s.nextID
s.nextID++
s.users[user.ID] = user
return user
}
func (s *UserStore) GetAll() []User {
s.mu.RLock()
defer s.mu.RUnlock()
users := make([]User, 0, len(s.users))
for _, u := range s.users {
users = append(users, u)
}
return users
}
func main() {
store := NewUserStore()
mux := http.NewServeMux()
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
json.NewEncoder(w).Encode(store.GetAll())
case http.MethodPost:
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, `{"error":"nieprawidłowe dane"}`, http.StatusBadRequest)
return
}
created := store.Create(user)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(created)
default:
http.Error(w, `{"error":"metoda niedozwolona"}`, http.StatusMethodNotAllowed)
}
})
// Middleware logujący
handler := loggingMiddleware(mux)
log.Println("Serwer uruchomiony na :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s", r.Method, r.URL.Path, r.RemoteAddr)
next.ServeHTTP(w, r)
})
}
Popularne frameworki: Gin, Echo, Fiber#
Choć biblioteka standardowa jest wystarczająca, frameworki dodają wygodne funkcje jak routing z parametrami, walidację, middleware i generowanie dokumentacji.
Gin - najpopularniejszy framework#
Gin jest najczęściej używanym frameworkiem webowym Go. Oferuje doskonałą wydajność i intuicyjne API:
package main
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name" binding:"required,min=2"`
Description string `json:"description"`
Price float64 `json:"price" binding:"required,gt=0"`
Category string `json:"category" binding:"required"`
}
var products = []Product{
{ID: 1, Name: "Laptop", Description: "Laptop programistyczny", Price: 4999.99, Category: "elektronika"},
{ID: 2, Name: "Klawiatura", Description: "Mechaniczna", Price: 399.99, Category: "akcesoria"},
}
func main() {
r := gin.Default()
// Middleware CORS
r.Use(func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
c.Next()
})
api := r.Group("/api/v1")
{
api.GET("/products", getProducts)
api.GET("/products/:id", getProduct)
api.POST("/products", createProduct)
api.PUT("/products/:id", updateProduct)
api.DELETE("/products/:id", deleteProduct)
}
r.Run(":8080")
}
func getProducts(c *gin.Context) {
category := c.Query("category")
if category != "" {
filtered := make([]Product, 0)
for _, p := range products {
if p.Category == category {
filtered = append(filtered, p)
}
}
c.JSON(http.StatusOK, gin.H{"data": filtered, "count": len(filtered)})
return
}
c.JSON(http.StatusOK, gin.H{"data": products, "count": len(products)})
}
func getProduct(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "nieprawidłowe ID"})
return
}
for _, p := range products {
if p.ID == id {
c.JSON(http.StatusOK, gin.H{"data": p})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "produkt nie znaleziony"})
}
func createProduct(c *gin.Context) {
var product Product
if err := c.ShouldBindJSON(&product); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
product.ID = len(products) + 1
products = append(products, product)
c.JSON(http.StatusCreated, gin.H{"data": product})
}
func updateProduct(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var input Product
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
for i, p := range products {
if p.ID == id {
input.ID = id
products[i] = input
c.JSON(http.StatusOK, gin.H{"data": input})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "produkt nie znaleziony"})
}
func deleteProduct(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
for i, p := range products {
if p.ID == id {
products = append(products[:i], products[i+1:]...)
c.JSON(http.StatusOK, gin.H{"message": "usunięto"})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "produkt nie znaleziony"})
}
Echo - minimalistyczny i wydajny#
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
e.GET("/api/health", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"status": "healthy",
"version": "1.0.0",
})
})
e.Logger.Fatal(e.Start(":8080"))
}
Fiber - zainspirowany Express.js#
Fiber bazuje na fasthttp i jest przyjazny dla programistów przechodzących z Node.js:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/limiter"
"time"
)
func main() {
app := fiber.New(fiber.Config{
Prefork: true,
CaseSensitive: true,
ServerHeader: "MDS-API",
})
app.Use(cors.New())
app.Use(limiter.New(limiter.Config{
Max: 100,
Expiration: 1 * time.Minute,
}))
app.Get("/api/status", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"status": "ok",
"uptime": time.Since(time.Now()).String(),
})
})
app.Listen(":3000")
}
Modelowanie danych za pomocą struktur#
Go nie ma klas w tradycyjnym rozumieniu. Zamiast tego wykorzystuje struktury (structs) z metodami i interfejsy do polimorfizmu:
package models
import "time"
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Email string `json:"email" gorm:"uniqueIndex;not null" validate:"required,email"`
Name string `json:"name" gorm:"not null" validate:"required,min=2,max=100"`
Password string `json:"-" gorm:"not null"` // "-" ukrywa pole w JSON
Role string `json:"role" gorm:"default:user"`
Active bool `json:"active" gorm:"default:true"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Orders []Order `json:"orders,omitempty" gorm:"foreignKey:UserID"`
}
type Order struct {
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"index;not null"`
Total float64 `json:"total" gorm:"not null"`
Status string `json:"status" gorm:"default:pending"`
Items []Item `json:"items" gorm:"foreignKey:OrderID"`
CreatedAt time.Time `json:"created_at"`
}
type Item struct {
ID uint `json:"id" gorm:"primaryKey"`
OrderID uint `json:"order_id"`
ProductID uint `json:"product_id"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
}
// Interfejs definiujący kontrakt repozytorium
type UserRepository interface {
FindByID(id uint) (*User, error)
FindByEmail(email string) (*User, error)
Create(user *User) error
Update(user *User) error
Delete(id uint) error
List(page, limit int) ([]User, int64, error)
}
Obsługa błędów w Go#
Go przyjmuje jawne podejście do obsługi błędów. Zamiast wyjątków, funkcje zwracają wartość błędu, którą trzeba sprawdzić:
package service
import (
"errors"
"fmt"
)
// Definiowanie własnych typów błędów
var (
ErrNotFound = errors.New("zasób nie znaleziony")
ErrAlreadyExists = errors.New("zasób już istnieje")
ErrUnauthorized = errors.New("brak autoryzacji")
ErrValidation = errors.New("błąd walidacji")
)
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Detail string `json:"detail,omitempty"`
Err error `json:"-"`
}
func (e *AppError) Error() string {
return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Detail)
}
func (e *AppError) Unwrap() error {
return e.Err
}
func NewNotFoundError(resource string, id interface{}) *AppError {
return &AppError{
Code: 404,
Message: "nie znaleziono",
Detail: fmt.Sprintf("%s o ID %v nie istnieje", resource, id),
Err: ErrNotFound,
}
}
func NewValidationError(detail string) *AppError {
return &AppError{
Code: 400,
Message: "błąd walidacji",
Detail: detail,
Err: ErrValidation,
}
}
// Użycie w serwisie
type UserService struct {
repo UserRepository
}
func (s *UserService) GetUser(id uint) (*User, error) {
user, err := s.repo.FindByID(id)
if err != nil {
return nil, fmt.Errorf("pobieranie użytkownika %d: %w", id, err)
}
if user == nil {
return nil, NewNotFoundError("użytkownik", id)
}
return user, nil
}
func (s *UserService) CreateUser(user *User) error {
// Sprawdzenie czy email już istnieje
existing, err := s.repo.FindByEmail(user.Email)
if err != nil {
return fmt.Errorf("sprawdzanie emaila: %w", err)
}
if existing != nil {
return &AppError{
Code: 409,
Message: "konflikt",
Detail: "użytkownik z tym emailem już istnieje",
Err: ErrAlreadyExists,
}
}
if err := s.repo.Create(user); err != nil {
return fmt.Errorf("tworzenie użytkownika: %w", err)
}
return nil
}
Dostęp do bazy danych - GORM i sqlx#
GORM - ORM dla Go#
GORM to najpopularniejszy ORM w ekosystemie Go, oferujący auto-migracje, relacje i query builder:
package database
import (
"fmt"
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func Connect(dsn string) (*gorm.DB, error) {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return nil, fmt.Errorf("połączenie z bazą danych: %w", err)
}
// Connection pooling
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
// Auto-migracja
err = db.AutoMigrate(&User{}, &Order{}, &Item{})
if err != nil {
return nil, fmt.Errorf("migracja: %w", err)
}
log.Println("Połączono z bazą danych")
return db, nil
}
// Implementacja repozytorium z GORM
type GormUserRepository struct {
db *gorm.DB
}
func NewGormUserRepository(db *gorm.DB) *GormUserRepository {
return &GormUserRepository{db: db}
}
func (r *GormUserRepository) FindByID(id uint) (*User, error) {
var user User
result := r.db.Preload("Orders").First(&user, id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &user, nil
}
func (r *GormUserRepository) List(page, limit int) ([]User, int64, error) {
var users []User
var total int64
r.db.Model(&User{}).Count(&total)
offset := (page - 1) * limit
result := r.db.Offset(offset).Limit(limit).
Order("created_at DESC").
Find(&users)
return users, total, result.Error
}
func (r *GormUserRepository) Create(user *User) error {
return r.db.Create(user).Error
}
sqlx - rozszerzenie database/sql#
Dla lepszej kontroli nad zapytaniami SQL, sqlx jest lekką alternatywą:
package database
import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type SqlxUserRepository struct {
db *sqlx.DB
}
func NewSqlxConnection(dsn string) (*sqlx.DB, error) {
return sqlx.Connect("postgres", dsn)
}
func (r *SqlxUserRepository) FindByID(id uint) (*User, error) {
var user User
err := r.db.Get(&user,
`SELECT id, email, name, role, active, created_at
FROM users WHERE id = $1`, id)
if err != nil {
return nil, err
}
return &user, nil
}
func (r *SqlxUserRepository) Search(query string, limit int) ([]User, error) {
var users []User
err := r.db.Select(&users,
`SELECT id, email, name, role, created_at
FROM users
WHERE name ILIKE $1 OR email ILIKE $1
ORDER BY created_at DESC
LIMIT $2`,
"%"+query+"%", limit)
return users, err
}
gRPC i Protocol Buffers#
Go jest jednym z najlepszych języków do budowy serwisów gRPC. Protocol Buffers zapewniają wydajną serializację, a gRPC - szybką komunikację między serwisami:
// user.proto
syntax = "proto3";
package user;
option go_package = "./pb";
message User {
int64 id = 1;
string name = 2;
string email = 3;
string role = 4;
}
message GetUserRequest {
int64 id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
string role = 3;
}
message UserListResponse {
repeated User users = 1;
int64 total = 2;
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
}
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (UserListResponse);
}
Implementacja serwera gRPC w Go:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "myapp/pb"
)
type userServer struct {
pb.UnimplementedUserServiceServer
users map[int64]*pb.User
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user, ok := s.users[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "użytkownik %d nie znaleziony", req.Id)
}
return user, nil
}
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
id := int64(len(s.users) + 1)
user := &pb.User{
Id: id,
Name: req.Name,
Email: req.Email,
Role: req.Role,
}
s.users[id] = user
return user, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("nie udało się nasłuchiwać: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &userServer{
users: make(map[int64]*pb.User),
})
log.Println("Serwer gRPC uruchomiony na :50051")
log.Fatal(s.Serve(lis))
}
Testowanie w Go#
Go ma wbudowany framework testowy - nie potrzebujesz zewnętrznych bibliotek. Testy tworzy się w plikach _test.go:
package service_test
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.Default()
r.GET("/api/products", getProducts)
r.POST("/api/products", createProduct)
return r
}
func TestGetProducts(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/products", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("oczekiwano status 200, otrzymano %d", w.Code)
}
if !strings.Contains(w.Body.String(), "data") {
t.Error("odpowiedź nie zawiera klucza 'data'")
}
}
func TestCreateProduct(t *testing.T) {
router := setupRouter()
body := `{"name":"Test Product","price":29.99,"category":"test"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/products", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Errorf("oczekiwano status 201, otrzymano %d", w.Code)
}
}
// Testy tabelaryczne - idiomatyczny wzorzec Go
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
isValid bool
}{
{"prawidłowy email", "user@example.com", true},
{"bez domeny", "user@", false},
{"bez @", "userexample.com", false},
{"pusty string", "", false},
{"z subdomeną", "user@sub.example.com", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ValidateEmail(tt.email)
if result != tt.isValid {
t.Errorf("ValidateEmail(%q) = %v, oczekiwano %v",
tt.email, result, tt.isValid)
}
})
}
}
// Benchmark
func BenchmarkGetProducts(b *testing.B) {
router := setupRouter()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/products", nil)
router.ServeHTTP(w, req)
}
}
Uruchamianie testów:
# Wszystkie testy
go test ./...
# Z verbose output
go test -v ./...
# Z pokryciem kodu
go test -cover ./...
# Benchmarki
go test -bench=. ./...
# Race detector
go test -race ./...
Cross-kompilacja i deployment#
Jedną z największych zalet Go jest możliwość kompilacji na dowolną platformę z dowolnej platformy:
# Kompilacja na Linux z Windowsa/Maca
GOOS=linux GOARCH=amd64 go build -o server-linux ./cmd/server
# Kompilacja na Windows
GOOS=windows GOARCH=amd64 go build -o server.exe ./cmd/server
# Kompilacja na ARM (Raspberry Pi, AWS Graviton)
GOOS=linux GOARCH=arm64 go build -o server-arm64 ./cmd/server
# Optymalizacja rozmiaru binarki
go build -ldflags="-s -w" -o server ./cmd/server
Docker i Kubernetes#
Go idealnie współgra z konteneryzacją. Dzięki statycznej kompilacji, obrazy Docker mogą być minimalne:
# Multi-stage build
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Cachowanie zależności
COPY go.mod go.sum ./
RUN go mod download
# Kopiowanie kodu i budowanie
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server
# Minimalny obraz produkcyjny
FROM scratch
COPY --from=builder /server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]
Ten obraz waży zaledwie 5-15 MB - porównaj to z typowym obrazem Node.js (200+ MB) czy Javy (300+ MB).
Manifest Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-api
spec:
replicas: 3
selector:
matchLabels:
app: go-api
template:
metadata:
labels:
app: go-api
spec:
containers:
- name: go-api
image: registry.example.com/go-api:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: go-api-service
spec:
selector:
app: go-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Wydajność - porównanie z innymi językami#
Go oferuje wyjątkową wydajność w stosunku do swojej prostoty. Poniżej typowe porównanie dla API REST zwracającego dane z bazy:
| Metryka | Go (Gin) | Node.js (Express) | Python (FastAPI) | Java (Spring Boot) | |---|---|---|---|---| | Zapytania/s | ~45 000 | ~15 000 | ~8 000 | ~35 000 | | Opóźnienie p99 | 2 ms | 12 ms | 25 ms | 5 ms | | Zużycie RAM | 20 MB | 80 MB | 60 MB | 200 MB | | Czas startu | 10 ms | 500 ms | 2 s | 8 s | | Rozmiar Docker | 10 MB | 200 MB | 150 MB | 300 MB |
Go osiąga wydajność zbliżoną do Javy przy znacznie mniejszym zużyciu zasobów i szybszym czasie startu, co jest krytyczne w środowiskach kontenerowych i serverless.
Kiedy wybrać Go?#
Go jest idealnym wyborem, gdy:
- Budujesz mikroserwisy - szybki start, mały footprint, łatwy deployment
- Potrzebujesz wysokiej współbieżności - goroutines obsługują tysiące połączeń
- Tworzysz narzędzia CLI - pojedynczy binarny plik bez zależności
- Pracujesz z chmurą - Docker, Kubernetes, AWS Lambda, Cloud Functions
- Zależy ci na wydajności - niskoopóźnieniowe API i przetwarzanie danych
- Chcesz prostoty - minimalna składnia, szybkie wdrożenie nowych programistów
Go nie jest najlepszym wyborem, gdy potrzebujesz rozbudowanego ekosystemu ORM i generatorów (jak Rails czy Django), programowania funkcyjnego na zaawansowanym poziomie, lub GUI desktopowego.
Podsumowanie#
Go to język, który udowodnił swoją wartość w produkcji. Od mikroserwisów po narzędzia infrastrukturalne, od API po systemy rozproszone - Go oferuje unikalną kombinację wydajności, prostoty i niezawodności.
Kluczowe zalety Go to:
- Goroutines zapewniające lekką współbieżność
- Prosty model błędów wymuszający jawną obsługę
- Szybka kompilacja i natychmiastowy start aplikacji
- Minimalne obrazy Docker idealne dla Kubernetes
- Wbudowane narzędzia do testowania, profilowania i formatowania kodu
- Bogaty ekosystem z frameworkami jak Gin, Echo i Fiber
Potrzebujesz wydajnego API w Go?#
W MDS Software Solutions Group specjalizujemy się w budowie wysokowydajnych systemów backendowych:
- Projektowanie i implementacja API w Go
- Architektura mikroserwisów z gRPC
- Optymalizacja wydajności istniejących systemów
- Konteneryzacja i deployment na Kubernetes
- Szkolenia i konsulting technologiczny
Skontaktuj się z nami, aby omówić Twój projekt i dowiedzieć się, jak Go może przyspieszyć Twoje usługi!
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.