Раздел 24 · Микросервисы (Go)

Неделя 1: контракты, OpenAPI, Protobuf, три сервиса

middle~45 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Неделя 1: контракты, OpenAPI, Protobuf, три сервиса

Стартовая неделя курса. На выходе у нас, три работающих сервиса, говорящие через gRPC, и один из них принимающий HTTP-запросы по OpenAPI-контракту. Контракты пишутся первыми, реализация догоняет.

Цели недели

  1. Поднять монорепо на Go Workspaces.
  2. Описать OpenAPI-контракт для OrderService (HTTP).
  3. Описать Protobuf-контракты для InventoryService и PaymentService (gRPC).
  4. Сгенерировать клиентский и серверный код через ogen (HTTP) и protoc-gen-go + protoc-gen-go-grpc (gRPC).
  5. Реализовать три сервиса с минимальной бизнес-логикой (in-memory).
  6. В OrderService подключить gRPC-клиенты к Inventory и Payment.
  7. Прогнать автоматический test-api scenario.

Структура репозитория к концу недели

.
├── go.work
├── go.work.sum
├── Taskfile.yml
├── buf.work.yaml
├── shared/
│   ├── api/order/v1/             # OpenAPI yaml
│   │   ├── order.openapi.yaml
│   │   ├── components/            # переиспользуемые схемы
│   │   ├── paths/                 # маршруты
│   │   └── params/                # path-параметры
│   ├── proto/
│   │   ├── inventory/v1/inventory.proto
│   │   └── payment/v1/payment.proto
│   ├── pkg/                       # ВСЁ генерируется, в .gitignore
│   │   ├── openapi/order/v1/      # ogen-output
│   │   └── proto/inventory|payment/v1/
│   └── go.mod
├── order/      # main.go, использует shared/pkg/openapi
├── inventory/  # main.go, регистрирует gRPC server из shared/pkg/proto/inventory
└── payment/    # main.go, регистрирует gRPC server из shared/pkg/proto/payment

Go Workspaces, зачем

Go Workspaces, это go.work файл, который объединяет несколько модулей в один dev-experience. Без него приходится либо складывать всё в один модуль (теряем независимость), либо городить replace директивы (хрупко). С go.work:

go 1.24

use (
    ./order
    ./inventory
    ./payment
    ./shared
)
  • каждый сервис, отдельный модуль со своим go.mod,
  • go build ./order/cmd находит shared без replace,
  • IDE видит весь репозиторий как один проект,
  • go.work.sum коммитим, как go.sum.

В CI и продакшене для сборки каждого сервиса отдельно используем GOWORK=off, чтобы строиться на чистых go.mod. Это требование “один сервис, один артефакт”.

OpenAPI для HTTP, зачем не “просто handlers”

Можно написать http.HandleFunc("/orders", handleOrders) руками. Но в командной разработке это источник дрейфа: фронт ждёт created_at, бэк отдаёт createdAt, ошибки описаны в README, типы заглушены interface{}. Контракт пишется в коде, а в коде он необозрим.

OpenAPI это machine-readable описание HTTP API:

  • эндпоинты, методы, схемы запросов/ответов, ошибки,
  • единый источник правды для фронта и бэка,
  • генерация клиентов (TS для фронта, Go для бэка) и серверных stub-ов,
  • mock-серверы для разработки фронта.

Мы используем ogen, потому что он:

  • быстрый кодген,
  • идиоматичный вывод (без reflection в рантайме),
  • хорошо работает с Go 1.21+,
  • активно развивается.

Структура OpenAPI yaml

Не пихаем всё в один файл. Раскладываем:

shared/api/order/v1/
├── order.openapi.yaml          # корень: info, paths, refs
├── paths/                      # один файл на маршрут
│   ├── orders.yaml             # POST /orders
│   ├── order_by_uuid.yaml      # GET /orders/{order_uuid}
│   ├── order_pay.yaml          # POST /orders/{order_uuid}/pay
│   └── order_cancel.yaml       # POST /orders/{order_uuid}/cancel
├── components/
│   ├── enums/                  # OrderStatus, PaymentMethod
│   ├── errors/                 # 11 типовых ошибок 4xx/5xx
│   ├── create_order_request.yaml
│   ├── create_order_response.yaml
│   ├── order_dto.yaml
│   ├── pay_order_request.yaml
│   ├── pay_order_response.yaml
│   └── get_order_response.yaml
└── params/
    └── order_uuid.yaml         # path-параметр

Корневой order.openapi.yaml тонкий, только $ref-ы на части. Это даёт нормальный diff в PR при изменении одного эндпоинта.

Сгенерированный код

task gen-openapi запускает ogen и кладёт результат в shared/pkg/openapi/order/v1/. Файлы:

  • oas_server_gen.go, интерфейс который реализует наш сервис,
  • oas_handlers_gen.go, http handler-ы,
  • oas_router_gen.go, маршрутизатор,
  • oas_schemas_gen.go, типы,
  • oas_client_gen.go, http-клиент.

В order/internal/api/... пишем только реализацию интерфейса. Никаких ручных JSON-парсеров и валидаций.

премиум-урок

Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.

Оплата пока не подключена: премиум и курсы открываются промокодом.