API Gateway на Go: ваш личный дирижёр микросервисного оркестра

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

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

Начать курс

Вступление: почему вашему Go-проекту нужен дирижёр

Представьте себе симфонический оркестр. У вас есть скрипки (сервис пользователей), духовые (сервис заказов), ударные (сервис платежей). Каждый инструмент прекрасен сам по себе, но без дирижёра они будут играть кто в лес, кто по дрова. В мире микросервисов на Go API Gateway — это тот самый дирижёр.

Когда вы пишете на Go, вы привыкли к производительности, простоте и элегантности. Но как только количество сервисов переваливает за 3–4, начинается хаос: клиенты не знают, куда стучаться, аутентификация дублируется в каждом сервисе, а мониторинг превращается в головную боль. API Gateway для Go сервисов решает эти проблемы разом.

В этой статье мы не просто поговорим о теории. Мы возьмём Go, стандартную библиотеку и пару популярных пакетов, чтобы собрать свой собственный шлюз. Готовы дирижировать? Поехали!

Что такое API Gateway и зачем он нужен в Go?

API Gateway — это единая точка входа для всех клиентских запросов к вашим бэкенд-сервисам. Представьте, что у вас есть:

  • user-service:8081 — регистрация и профили
  • order-service:8082 — корзина и заказы
  • payment-service:8083 — транзакции

Без шлюза клиент должен знать адреса всех трёх сервисов. С API Gateway на Go он стучится только в api.example.com, а шлюз сам решает, куда направить запрос.

Основные функции шлюза

  • Маршрутизация — перенаправление запросов к нужному сервису
  • Аутентификация и авторизация — проверка JWT токенов в одном месте
  • Rate Limiting — защита от DDoS и перегрузок
  • Агрегация данных — сбор ответов от нескольких сервисов в один
  • Балансировка нагрузки — распределение запросов между инстансами

Go идеально подходит для этой роли: он лёгкий, быстрый и позволяет обрабатывать тысячи запросов в секунду на одном ядре.

Пишем простой API Gateway на Go: от идеи до кода

Давайте построим минимальный, но рабочий Go API Gateway. Мы будем использовать только стандартную библиотеку net/http и пакет httputil.ReverseProxy для проксирования.

Шаг 1: Базовая маршрутизация

Начнём с простого: направляем запросы на разные сервисы в зависимости от пути.

package main

import (
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
)

func main() {
    // Сервис пользователей
    userServiceURL, _ := url.Parse("http://localhost:8081")
    userProxy := httputil.NewSingleHostReverseProxy(userServiceURL)

    // Сервис заказов
    orderServiceURL, _ := url.Parse("http://localhost:8082")
    orderProxy := httputil.NewSingleHostReverseProxy(orderServiceURL)

    http.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Перенаправляем запрос к user-service: %s", r.URL.Path)
        userProxy.ServeHTTP(w, r)
    })

    http.HandleFunc("/api/orders/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Перенаправляем запрос к order-service: %s", r.URL.Path)
        orderProxy.ServeHTTP(w, r)
    })

    log.Println("API Gateway запущен на :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Этот код уже делает 80% работы: любой запрос к /api/users/ уходит на user-service, а к /api/orders/ — на order-service. Но где же аутентификация?

Шаг 2: Добавляем middleware для JWT

Настоящая магия API Gateway на Go происходит в middleware. Давайте добавим проверку JWT-токена перед проксированием.

package main

import (
    "context"
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"
)

// Структура для хранения данных пользователя в контексте
type contextKey string
const userIDKey contextKey = "userID"

// Middleware для проверки JWT
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" || !strings.HasPrefix(token, "Bearer ") {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        // Упрощённая проверка: в реальности используйте библиотеку jwt-go
        tokenValue := strings.TrimPrefix(token, "Bearer ")
        if tokenValue != "valid-token" {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }

        // Передаём ID пользователя в контекст
        ctx := context.WithValue(r.Context(), userIDKey, "user-123")
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func main() {
    userServiceURL, _ := url.Parse("http://localhost:8081")
    userProxy := httputil.NewSingleHostReverseProxy(userServiceURL)

    // Прокси с логированием
    userHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := r.Context().Value(userIDKey)
        log.Printf("Пользователь %s запрашивает %s", userID, r.URL.Path)
        userProxy.ServeHTTP(w, r)
    })

    http.Handle("/api/users/", authMiddleware(userHandler))

    log.Println("Защищённый API Gateway на :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Теперь все запросы к пользовательскому сервису проходят через аутентификацию. Go делает это элегантно и без лишних зависимостей.

Продвинутые паттерны: агрегация и rate limiting

Простое проксирование — это хорошо, но шлюз API для Go сервисов может гораздо больше. Давайте рассмотрим два мощных паттерна.

Агрегация данных: собираем пазл из сервисов

Представьте, что клиент хочет получить профиль пользователя с его последними заказами. Вместо двух запросов делаем один — шлюз сам соберёт данные.

func aggregateHandler(w http.ResponseWriter, r *http.Request) {
    // Параллельно запрашиваем данные из двух сервисов
    userChan := make(chan []byte)
    orderChan := make(chan []byte)

    go func() {
        // Запрос к user-service
        resp, _ := http.Get("http://localhost:8081/profile")
        defer resp.Body.Close()
        body, _ := io.ReadAll(resp.Body)
        userChan <- body
    }()

    go func() {
        resp, _ := http.Get("http://localhost:8082/orders/last")
        defer resp.Body.Close()
        body, _ := io.ReadAll(resp.Body)
        orderChan <- body
    }()

    userData := <-userChan
    orderData := <-orderChan

    // Объединяем в один JSON
    combined := fmt.Sprintf(`{"user": %                

Блоги

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