Введение в микросервисы на Go
Микросервисная архитектура стала стандартом де-факто для построения современных распределенных систем. Язык Go (Golang) с его легковесными горутинами, высокой производительностью и простотой развертывания идеально подходит для реализации микросервисов. В этой статье мы разберем архитектуру микросервисов на Go от концепции до практической реализации с примерами кода.
Go предлагает встроенную поддержку конкурентности, быстрый запуск и малый размер бинарных файлов — все это критически важно для микросервисов. Мы рассмотрим, как спроектировать сервис, реализовать REST API, добавить middleware и подготовить приложение к контейнеризации.
Архитектура микросервиса на Go
1. Структура проекта
Правильная организация кода — основа поддерживаемого микросервиса. Рекомендуемая структура:
myservice/
├── cmd/
│ └── server/
│ └── main.go # Точка входа
├── internal/
│ ├── handler/ # HTTP-обработчики
│ ├── service/ # Бизнес-логика
│ ├── repository/ # Работа с данными
│ └── middleware/ # Промежуточные обработчики
├── pkg/
│ └── models/ # Общие модели данных
├── config/
│ └── config.go # Конфигурация
├── go.mod
└── Dockerfile
Такая структура соответствует принципам Clean Architecture: handler → service → repository. Это позволяет легко тестировать каждый слой независимо.
2. Конфигурация и запуск
Начнем с простого HTTP-сервера с использованием стандартной библиотеки net/http:
// cmd/server/main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"status": "ok"}`)
})
log.Printf("Server starting on :%s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
Это минимальный микросервис. Для production используйте роутер с поддержкой middleware, например gorilla/mux или chi.
Практическая реализация микросервиса
3. REST API с gorilla/mux
Установим gorilla/mux и реализуем CRUD для сущности "Пользователь":
// go.mod
module github.com/youruser/userservice
go 1.21
require github.com/gorilla/mux v1.8.1
// internal/handler/user.go
package handler
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
)
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
var users = []User{
{ID: "1", Name: "Alice", Age: 30},
}
func GetUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func CreateUser(w http.ResponseWriter, r *http.Request) {
var newUser User
if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
users = append(users, newUser)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}
// cmd/server/main.go (продолжение)
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
"yourproject/internal/handler"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/users", handler.GetUsers).Methods("GET")
r.HandleFunc("/users", handler.CreateUser).Methods("POST")
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
4. Middleware: логирование и аутентификация
Middleware — неотъемлемая часть микросервисов. Реализуем логирование запросов:
// internal/middleware/logging.go
package middleware
import (
"log"
"net/http"
"time"
)
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("Completed in %v", time.Since(start))
})
}
Подключаем middleware к роутеру:
// cmd/server/main.go
func main() {
r := mux.NewRouter()
r.Use(middleware.Logging) // Глобальное middleware
r.HandleFunc("/users", handler.GetUsers).Methods("GET")
// ...
}
5. Graceful shutdown
Корректное завершение работы — важная практика. Добавим graceful shutdown:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
r := mux.NewRouter()
// ... настройка роутов
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
// Запуск в горутине
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Ожидание сигнала
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
Docker и развертывание
Микросервисы на Go идеально подходят для контейнеризации. Пример Dockerfile с multi-stage сборкой:
# Dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/server .
EXPOSE