Въведение в Go (Golang)
Go е език за програмиране, създаден в Google през 2009 година от Robert Griesemer, Rob Pike и Ken Thompson. Той комбинира простотата на Python с производителността на C, което го прави идеален за backend разработка и API сървъри.
Защо 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, ¬Found) {
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