Неделя 1: контракты, OpenAPI, Protobuf, три сервиса
Стартовая неделя курса. На выходе у нас, три работающих сервиса, говорящие через gRPC, и один из них принимающий HTTP-запросы по OpenAPI-контракту. Контракты пишутся первыми, реализация догоняет.
Цели недели
- Поднять монорепо на Go Workspaces.
- Описать OpenAPI-контракт для OrderService (HTTP).
- Описать Protobuf-контракты для InventoryService и PaymentService (gRPC).
- Сгенерировать клиентский и серверный код через
ogen(HTTP) иprotoc-gen-go+protoc-gen-go-grpc(gRPC). - Реализовать три сервиса с минимальной бизнес-логикой (in-memory).
- В OrderService подключить gRPC-клиенты к Inventory и Payment.
- Прогнать автоматический 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-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.