Въведение в Go (Golang)

Go е език за програмиране, създаден в Google през 2009 година от Robert Griesemer, Rob Pike и Ken Thompson. Той комбинира простотата на Python с производителността на C, което го прави идеален за backend разработка и API сървъри.

Go Programming

Защо Go за Backend?

Сравнение с други езици

┌─────────────────┬──────────┬──────────┬──────────┬──────────┬───────────┐
│   Критерий      │    Go    │  Node.js │  Python  │   Java   │   Rust    │
├─────────────────┼──────────┼──────────┼──────────┼──────────┼───────────┤
│ Скорост         │   ★★★★★  │   ★★★    │   ★★     │   ★★★★   │   ★★★★★   │
│ Лесно учене     │   ★★★★   │   ★★★★★  │   ★★★★★  │   ★★★    │   ★★      │
│ Concurrency     │   ★★★★★  │   ★★★    │   ★★     │   ★★★★   │   ★★★★★   │
│ Memory safety   │   ★★★★   │   ★★★    │   ★★★★   │   ★★★★   │   ★★★★★   │
│ Compilation     │   ★★★★★  │   N/A    │   N/A    │   ★★★    │   ★★      │
│ Ecosystem       │   ★★★★   │   ★★★★★  │   ★★★★★  │   ★★★★★  │   ★★★     │
│ DevOps friendly │   ★★★★★  │   ★★★    │   ★★★    │   ★★     │   ★★★★    │
└─────────────────┴──────────┴──────────┴──────────┴──────────┴───────────┘

Ключови предимства на Go

1. Бърза компилация
Go се компилира изключително бързо - проект с хиляди файлове се компилира за секунди.

2. Single binary deployment
Компилираният Go binary съдържа всичко необходимо - няма външни зависимости.

3. Вградена concurrency
Goroutines и channels правят конкурентното програмиране лесно и безопасно.

4. Garbage collection
Автоматично управление на паметта без overhead на JVM.

5. Стандартна библиотека
net/http, encoding/json, database/sql - всичко нужно за API е вградено.

Архитектура на Go API сървър

┌──────────────────────────────────────────────────────────────────────────────┐
│                        GO API SERVER ARCHITECTURE                             │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   ┌─────────────┐                                                            │
│   │   main.go   │  Entry point                                               │
│   └──────┬──────┘                                                            │
│          │                                                                   │
│          ▼                                                                   │
│   ┌─────────────┐     ┌─────────────┐     ┌─────────────┐                   │
│   │   Router    │────►│  Middleware │────►│  Handlers   │                   │
│   │  (mux)      │     │  (CORS,Auth)│     │             │                   │
│   └─────────────┘     └─────────────┘     └──────┬──────┘                   │
│                                                   │                          │
│                                                   ▼                          │
│                       ┌─────────────┐     ┌─────────────┐                   │
│                       │   Models    │◄────│  Services   │                   │
│                       │  (structs)  │     │  (business) │                   │
│                       └─────────────┘     └──────┬──────┘                   │
│                                                   │                          │
│                                                   ▼                          │
│                                           ┌─────────────┐                    │
│                                           │  Database   │                    │
│                                           │  (SQLite)   │                    │
│                                           └─────────────┘                    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

Основни концепции в Go

Структури (Structs)

// Дефиниция на модел
type Article struct {
    ID         int       `json:"id"`
    Title      string    `json:"title"`
    Content    string    `json:"content"`
    Author     string    `json:"author"`
    Published  bool      `json:"published"`
    RenderMode string    `json:"render_mode"`
    CreatedAt  time.Time `json:"created_at"`
    UpdatedAt  time.Time `json:"updated_at"`
}

// Методи върху struct
func (a *Article) IsValid() bool {
    return a.Title != "" && a.Content != "" && a.Author != ""
}

func (a *Article) Summary() string {
    if len(a.Content) > 200 {
        return a.Content[:200] + "..."
    }
    return a.Content
}

Interfaces

// Interface за repository pattern
type ArticleRepository interface {
    GetByID(id int) (*Article, error)
    GetAll() ([]Article, error)
    Create(article *Article) error
    Update(article *Article) error
    Delete(id int) error
}

// SQLite имплементация
type SQLiteArticleRepo struct {
    db *sql.DB
}

func (r *SQLiteArticleRepo) GetByID(id int) (*Article, error) {
    var a Article
    err := r.db.QueryRow(`
        SELECT id, title, content, author, published, 
               render_mode, created_at, updated_at 
        FROM articles WHERE id = ?
    `, id).Scan(
        &a.ID, &a.Title, &a.Content, &a.Author, 
        &a.Published, &a.RenderMode, &a.CreatedAt, &a.UpdatedAt,
    )
    return &a, err
}

Goroutines и Channels

// Goroutines - лек thread
func processArticles(articles []Article) {
    results := make(chan ProcessResult, len(articles))
    
    // Стартираме goroutine за всяка статия
    for _, article := range articles {
        go func(a Article) {
            result := processArticle(a)
            results <- result
        }(article)
    }
    
    // Събираме резултатите
    for i := 0; i < len(articles); i++ {
        result := <-results
        handleResult(result)
    }
}

// Worker pool pattern
func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
    var wg sync.WaitGroup
    
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- process(job)
            }
        }()
    }
    
    wg.Wait()
    close(results)
}

HTTP сървър в Go

Основен сървър

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

func main() {
    // Регистриране на handlers
    http.HandleFunc("/api/health", corsMiddleware(healthHandler))
    http.HandleFunc("/api/articles", corsMiddleware(articlesHandler))
    http.HandleFunc("/api/article", corsMiddleware(articleHandler))
    
    // Стартиране на сървъра
    port := ":8082"
    log.Printf("Server starting on port %s...", port)
    log.Fatal(http.ListenAndServe(port, nil))
}

// CORS Middleware
func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", 
            "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", 
            "Content-Type, Authorization")
        
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next(w, r)
    }
}

// Helper за JSON отговори
func jsonResponse(w http.ResponseWriter, data interface{}, status int) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

Handler примери

// GET /api/articles - връща всички публикувани статии
func articlesHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        jsonResponse(w, map[string]string{
            "error": "Method not allowed",
        }, http.StatusMethodNotAllowed)
        return
    }
    
    rows, err := db.Query(`
        SELECT id, title, content, author, published, 
               render_mode, created_at, updated_at 
        FROM articles 
        WHERE published = true 
        ORDER BY created_at DESC
    `)
    if err != nil {
        jsonResponse(w, map[string]string{
            "error": err.Error(),
        }, http.StatusInternalServerError)
        return
    }
    defer rows.Close()
    
    var articles []Article
    for rows.Next() {
        var a Article
        rows.Scan(
            &a.ID, &a.Title, &a.Content, &a.Author,
            &a.Published, &a.RenderMode, &a.CreatedAt, &a.UpdatedAt,
        )
        articles = append(articles, a)
    }
    
    if articles == nil {
        articles = []Article{} // Връщаме празен array, не null
    }
    
    jsonResponse(w, articles, http.StatusOK)
}

// POST /api/inquiry - създава ново запитване
func createInquiryHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        jsonResponse(w, map[string]string{
            "error": "Method not allowed",
        }, http.StatusMethodNotAllowed)
        return
    }
    
    var inquiry Inquiry
    if err := json.NewDecoder(r.Body).Decode(&inquiry); err != nil {
        jsonResponse(w, map[string]string{
            "error": "Invalid JSON: " + err.Error(),
        }, http.StatusBadRequest)
        return
    }
    
    // Валидация
    if inquiry.Name == "" || inquiry.Email == "" || inquiry.Message == "" {
        jsonResponse(w, map[string]string{
            "error": "Name, email, and message are required",
        }, http.StatusBadRequest)
        return
    }
    
    // Записване в базата
    result, err := db.Exec(`
        INSERT INTO inquiries 
        (name, email, message, source_page, source_type, article_id) 
        VALUES (?, ?, ?, ?, ?, ?)
    `, inquiry.Name, inquiry.Email, inquiry.Message,
       inquiry.SourcePage, inquiry.SourceType, inquiry.ArticleID)
    
    if err != nil {
        jsonResponse(w, map[string]string{
            "error": err.Error(),
        }, http.StatusInternalServerError)
        return
    }
    
    id, _ := result.LastInsertId()
    inquiry.ID = int(id)
    inquiry.CreatedAt = time.Now()
    
    jsonResponse(w, map[string]interface{}{
        "success": true,
        "inquiry": inquiry,
    }, http.StatusCreated)
}

База данни с database/sql

SQLite интеграция

import (
    "database/sql"
    _ "github.com/mattn/go-sqlite3"
)

var db *sql.DB

func initDB() {
    var err error
    dbPath := "/var/www/project/data/app.db"
    
    db, err = sql.Open("sqlite3", dbPath)
    if err != nil {
        log.Fatal(err)
    }
    
    // Настройки за по-добра производителност
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    // Създаване на таблици
    _, err = db.Exec(`
        CREATE TABLE IF NOT EXISTS articles (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            author TEXT NOT NULL,
            published BOOLEAN DEFAULT FALSE,
            render_mode TEXT DEFAULT 'SSR',
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        );
        
        CREATE INDEX IF NOT EXISTS idx_articles_published 
        ON articles(published);
        
        CREATE INDEX IF NOT EXISTS idx_articles_render_mode 
        ON articles(render_mode);
    `)
    if err != nil {
        log.Fatal(err)
    }
}

Prepared Statements

var (
    stmtGetArticle    *sql.Stmt
    stmtCreateArticle *sql.Stmt
)

func initStatements() {
    var err error
    
    stmtGetArticle, err = db.Prepare(`
        SELECT id, title, content, author, published, 
               render_mode, created_at, updated_at 
        FROM articles WHERE id = ?
    `)
    if err != nil {
        log.Fatal(err)
    }
    
    stmtCreateArticle, err = db.Prepare(`
        INSERT INTO articles (title, content, author, published, render_mode)
        VALUES (?, ?, ?, ?, ?)
    `)
    if err != nil {
        log.Fatal(err)
    }
}

// Използване
func getArticle(id int) (*Article, error) {
    var a Article
    err := stmtGetArticle.QueryRow(id).Scan(
        &a.ID, &a.Title, &a.Content, &a.Author,
        &a.Published, &a.RenderMode, &a.CreatedAt, &a.UpdatedAt,
    )
    return &a, err
}

Error Handling в Go

// Custom error types
type NotFoundError struct {
    Resource string
    ID       int
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s with ID %d not found", e.Resource, e.ID)
}

// Използване
func getArticle(id int) (*Article, error) {
    var a Article
    err := db.QueryRow("SELECT ... WHERE id = ?", id).Scan(...)
    
    if err == sql.ErrNoRows {
        return nil, &NotFoundError{Resource: "Article", ID: id}
    }
    if err != nil {
        return nil, fmt.Errorf("database error: %w", err)
    }
    return &a, nil
}

// В handler
func articleHandler(w http.ResponseWriter, r *http.Request) {
    article, err := getArticle(id)
    
    if err != nil {
        var notFound *NotFoundError
        if errors.As(err, &notFound) {
            jsonResponse(w, map[string]string{
                "error": err.Error(),
            }, http.StatusNotFound)
            return
        }
        
        // Log internal errors, return generic message
        log.Printf("Error: %v", err)
        jsonResponse(w, map[string]string{
            "error": "Internal server error",
        }, http.StatusInternalServerError)
        return
    }
    
    jsonResponse(w, article, http.StatusOK)
}

Testing в Go

// article_test.go
package main

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/api/health", nil)
    rec := httptest.NewRecorder()
    
    healthHandler(rec, req)
    
    if rec.Code != http.StatusOK {
        t.Errorf("Expected status 200, got %d", rec.Code)
    }
    
    var response map[string]string
    json.Unmarshal(rec.Body.Bytes(), &response)
    
    if response["status"] != "ok" {
        t.Errorf("Expected status 'ok', got '%s'", response["status"])
    }
}

func TestArticleValidation(t *testing.T) {
    tests := []struct {
        name    string
        article Article
        valid   bool
    }{
        {
            name:    "Valid article",
            article: Article{Title: "Test", Content: "Content", Author: "Author"},
            valid:   true,
        },
        {
            name:    "Missing title",
            article: Article{Content: "Content", Author: "Author"},
            valid:   false,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := tt.article.IsValid(); got != tt.valid {
                t.Errorf("IsValid() = %v, want %v", got, tt.valid)
            }
        })
    }
}

Deployment

Компилация

# Стандартна компилация
go build -o server main.go

# С оптимизации за production
CGO_ENABLED=1 go build -ldflags="-s -w" -o server main.go

# Cross-compilation
GOOS=linux GOARCH=amd64 go build -o server-linux main.go

Systemd Service

# /etc/systemd/system/go-api.service
[Unit]
Description=Go API Server
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/project/backend
ExecStart=/var/www/project/backend/server
Restart=always
RestartSec=5
Environment=PORT=8082

[Install]
WantedBy=multi-user.target

Заключение

Go е отличен избор за backend разработка благодарение на:

  • Простота и бърза компилация
  • Отлична производителност
  • Вградена concurrency
  • Single binary deployment
  • Богата стандартна библиотека

За нашия проект Go перфектно комплементира Astro frontend-а, осигурявайки бърз и надежден API сървър.


Брой думи: 3,287