Микросервисы на Go: архитектура и практика для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Микросервисы на Go: от идеи до продакшена

Go (Golang) завоевал репутацию идеального языка для построения микросервисной архитектуры. Благодаря встроенной поддержке конкурентности, быстрой компиляции, минимальному потреблению памяти и простому синтаксису, Go позволяет создавать легковесные и высокопроизводительные сервисы. В этой статье мы разберем ключевые принципы построения микросервисов на Go, рассмотрим практические примеры и лучшие практики, которые помогут вам избежать типичных ошибок.

Почему Go — идеальный выбор для микросервисов?

Перед тем как погрузиться в архитектуру, давайте разберем, почему Go стал стандартом де-факто для микросервисов:

  • Быстрый старт: Go компилируется в единый бинарный файл, что упрощает деплой и масштабирование.
  • Встроенная конкурентность: горутины и каналы позволяют эффективно обрабатывать тысячи запросов без сложных потоков.
  • Минимальное потребление памяти: каждый микросервис на Go потребляет в разы меньше ресурсов, чем аналоги на Java или Python.
  • Простота поддержки: статическая типизация и строгий компилятор снижают количество ошибок на этапе выполнения.

Архитектура микросервиса на Go: базовый шаблон

Рассмотрим типичную структуру микросервиса на Go. Для примера создадим простой сервис управления пользователями.

Структура проекта

user-service/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handler/
│   │   └── user.go
│   ├── service/
│   │   └── user.go
│   ├── repository/
│   │   └── postgres.go
│   └── model/
│       └── user.go
├── pkg/
│   └── middleware/
│       └── logging.go
├── config/
│   └── config.go
├── go.mod
└── Dockerfile

Этот шаблон следует принципам Clean Architecture: зависимости направлены внутрь, а бизнес-логика изолирована от инфраструктуры.

Пример основного файла (cmd/server/main.go)

package main

import (
    "log"
    "net/http"
    "os"
    "user-service/internal/handler"
    "user-service/internal/repository"
    "user-service/internal/service"
)

func main() {
    // Инициализация зависимостей
    db, err := repository.NewPostgresDB("postgres://user:pass@localhost:5432/users")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    userRepo := repository.NewUserRepository(db)
    userSvc := service.NewUserService(userRepo)
    userHandler := handler.NewUserHandler(userSvc)

    // Настройка маршрутов
    mux := http.NewServeMux()
    mux.HandleFunc("GET /users/{id}", userHandler.GetByID)
    mux.HandleFunc("POST /users", userHandler.Create)
    mux.HandleFunc("PUT /users/{id}", userHandler.Update)

    // Запуск сервера
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    log.Printf("Server starting on :%s", port)
    log.Fatal(http.ListenAndServe(":"+port, mux))
}

Реализация слоёв микросервиса

Разделим код на три основных слоя: handler (транспорт), service (бизнес-логика) и repository (доступ к данным).

Слой handler: обработка HTTP-запросов

// internal/handler/user.go
package handler

import (
    "encoding/json"
    "net/http"
    "user-service/internal/model"
    "user-service/internal/service"
)

type UserHandler struct {
    svc *service.UserService
}

func NewUserHandler(svc *service.UserService) *UserHandler {
    return &UserHandler{svc: svc}
}

func (h *UserHandler) GetByID(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    user, err := h.svc.GetByID(r.Context(), id)
    if err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
        return
    }
    json.NewEncoder(w).Encode(user)
}

func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
    var user model.User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "invalid request", http.StatusBadRequest)
        return
    }
    created, err := h.svc.Create(r.Context(), &user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(created)
}

Слой service: бизнес-логика

// internal/service/user.go
package service

import (
    "context"
    "user-service/internal/model"
    "user-service/internal/repository"
)

type UserService struct {
    repo *repository.UserRepository
}

func NewUserService(repo *repository.UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) GetByID(ctx context.Context, id string) (*model.User, error) {
    user, err := s.repo.FindByID(ctx, id)
    if err != nil {
        return nil, err
    }
    // Здесь можно добавить дополнительную бизнес-логику
    // Например, проверку прав доступа или кэширование
    return user, nil
}

func (s *UserService) Create(ctx context.Context, user *model.User) (*model.User, error) {
    // Валидация данных
    if user.Name == "" || user.Email == "" {
        return nil, ErrInvalidInput
    }
    return s.repo.Save(ctx, user)
}

Слой repository: работа с базой данных

// internal/repository/postgres.go
package repository

import (
    "context"
    "database/sql"
    "user-service/internal/model"
)

type UserRepository struct {
    db *sql.DB
}

func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}

func (r *UserRepository) FindByID(ctx context.Context, id string) (*model.User, error) {
    row := r.db.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = $1", id)
    user := &model.User{}
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        return nil, err
    }
    return user, nil
}

func (r *UserRepository) Save(ctx context.Context, user *model.User) (*model.User, error) {
    err := r.db.QueryRowContext(ctx,
        "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
        user.Name, user.Email).Scan(&user.ID)
    if err != nil {
        return nil, err
    }
    return user, nil
}

Комму

Блоги

Рекомендаций по книгам