Содержание урока · 15 разделов
- Что такое go kit: тулкит, а не фреймворк
- Три слоя: transport, endpoint, service
- Endpoint как универсальная абстракция
- Middleware в go-kit: обёртки вместо звеньев
- Путь запроса: что происходит на каждом слое
- Что уже есть в коробке
- Микросервисы go: почему слои важны при росте
- func main: сборочный цех сервиса
- Куда класть ошибку: бизнес-сбой против инфраструктурного
- Когда go-kit уместен, а когда хватит gin
- Сигналы, что ваш выбор — go kit
- Как устроен курс go-kit
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Что такое go kit: тулкит, а не фреймворк#
Начну с истории, потому что она объясняет всю дальнейшую конструкцию. В 2014–2015 годах инженер Питер Бургон (Peter Bourgon) заметил парадокс: Go уже стал «языком сервера», но крупные «предприятия нового типа» — Facebook, Twitter, Netflix, SoundCloud — для микросервисов брали JVM-стеки. Не потому, что Go плох, а потому, что у Java-мира были готовые библиотеки и практики для распределённых систем, а у Go — только примитивы и идиомы. Итак, что такое go kit по определению: набор пакетов, переносящий те практики в идиоматичный Go. В README он описывается как «programming toolkit for building microservices (or elegant monoliths)» — тулкит для микросервисов и «элегантных монолитов». Проект волонтёрский, без коммерческого бэкинга, живёт с 2014 года, актуальный релиз — v0.13.0, а официальный FAQ прямо отвечает: go kit используется в продакшене в организациях больших и малых. Ключевые слова здесь — «набор пакетов»: вы берёте endpoint, логгер и circuit breaker по отдельности и собираете сами, а не устанавливаете каркас, который забирает себе main.
Разница практическая, а не терминологическая. Фреймворк зовёт ваш код: он владеет точкой входа, роутером и жизненным циклом, вы лишь заполняете хуки. Тулкит — библиотеки, которые зовёте вы: main остаётся вашим, go-kit даёт детали, которые вы собираете в конструкцию сами. Поэтому в списке related projects README Gin и Gorilla соседствуют с go kit на равных: веб-фреймворк может работать рядом, на транспортном слое, а может не использоваться вовсе.
Три слоя: transport, endpoint, service#
Официальная FAQ начинается с честного предупреждения тем, кто пришёл из MVC: go kit — не MVC-фреймворк. Сервис на go kit раскладывается на три слоя, и это главная картинка всего курса. Транспортный слой (transport) привязан к конкретным протоколам — HTTP, gRPC, Thrift, net/rpc: он декодирует запрос в структуру и кодирует ответ обратно. Слой endpoint — средняя полка: каждый метод сервиса абстрагируется до универсального endpoint.Endpoint, и именно здесь живёт «safety and antifragile logic» — проверки безопасности и антихрупкость. Слой service — бизнес-логика, и FAQ требует от него чистоты по канонам Clean Architecture и Hexagonal Architecture: сервис не знает ни про HTTP-заголовки, ни про gRPC-коды ошибок. Запрос входит на первом слое, спускается до третьего, ответ поднимается обратно — луковица, знакомая вам по middleware в gin, только слоёв ровно три, и у каждого своя ответственность.
Endpoint: фундаментальный строительный блок
GOpackage endpoint
import "context"
// Endpoint — фундаментальный строительный блок серверов и клиентов go-kit.
// Универсальная абстракция одного RPC-метода: запрос вошёл — ответ вышел.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
// Middleware — цепочная обёртка над endpoint'ом: принимает endpoint,
// возвращает endpoint с дополнительным поведением.
type Middleware func(Endpoint) Endpoint
// Failer реализуют response-структуры с бизнес-ошибкой внутри:
// транспорт поймёт, что ответ неуспешный, и закодирует его иначе.
type Failer interface {
Failed() error
}Это не упрощение, а реальное содержимое пакета endpoint (файл endpoint.go в репозитории). Вся архитектура держится на одной сигнатуре: любой метод любого сервиса превращается в функцию «контекст и запрос → ответ и ошибка». Рядом живут Nop — пустышка для тестов, и Chain — помощник композиции: Chain(outer, others...) считает первый аргумент самым внешним. Уровень абстракции предельно низкий, и в этом смысл: endpoint можно привязать к HTTP, к gRPC, обернуть в логи и метрики, не трогая саму функцию.
Endpoint как универсальная абстракция#
Первое, что смущает новичка, — interface{} на входе и выходе. Это не лень, а шов между слоями: транспорт знает конкретные типы (декодер превратил JSON в UppercaseRequest), endpoint о них не думает — он просто передаёт значение вниз, а внутри сервис приводит его к своему типу. Такой разрыв защищает логику от транспортных понятий: сменили HTTP на gRPC — endpoint не заметил. Второе следствие: один endpoint обслуживает много транспортов. FAQ приводит ровно этот сценарий — легаси HTTP API и новый gRPC-сервис в одном приложении шлют запросы в один и тот же endpoint. Третье следствие: endpoint одинаково работает и на сервере, и на клиенте — комментарий в исходниках называет его «fundamental building block of servers and clients». Исходящий вызов к чужому сервису — тоже endpoint, а значит, к нему одинаково прицепляются ретраи, балансировка и circuit breaker. Интерфейсы в Go — фундамент всего этого стиля; контракты поведения, неявная реализация и маленькие интерфейсы — тема урока 10-interfejsy продвинутого курса, полезно держать его под рукой, когда пойдёте писать свои интерфейсы сервиса. А детальный разбор сигнатуры ждёт в уроке 02-endpoint — он идёт следующим.
Middleware в go-kit: обёртки вместо звеньев#
Слово middleware в go-kit прижилось, но работает иначе, чем в gin. Мы уже разбирали встроенные middleware gin: там это func(*gin.Context) в цепочке вызовов, c.Next() и луковица, исполняющиеся на каждый запрос. В go-kit middleware — это тип func(Endpoint) Endpoint, и обёртывание происходит один раз, при сборке приложения: ep = loggingMiddleware(ep). На запросах никакой сборки нет — цепочка уже готова, статична и целиком видна в func main. FAQ формулирует философию так: strict separation of concerns через middleware (или decorator) pattern — обёртки добавляют логирование, rate limiting, балансировку и трейсинг, не касаясь кода внутри. Луковица сохраняется: внешний middleware видит запрос первым и ответ последним, поэтому замер латентности выглядит так же, как в gin, — старт таймера до вызова внутреннего endpoint'а, снятие показаний после. Разница в масштабе: у gin звенья висят на маршрутизаторе и знают про HTTP; у go-kit обёртка транспорт-агностична — одна и та же loggingMiddleware работает и для HTTP-, и для gRPC-версии сервиса. Есть и второй вид обёрток — service middleware: декоратор, реализующий интерфейс сервиса и оборачивающий конкретную реализацию; к ним вернёмся в уроке про service-слой.
Привязка endpoint'а к HTTP-транспорту
GO// Транспортный слой: единственное место, где видно *http.Request.
r.Methods("POST").Path("/profiles/").Handler(
httptransport.NewServer(
e.PostProfileEndpoint, // наш endpoint
decodePostProfileRequest, // декодер: *http.Request -> request-структура
encodeResponse, // энкодер: response -> http.ResponseWriter
options..., // опции: ошибки, таймауты, before/after
),
)Сигнатура kithttp.NewServer из transport/http/server.go: endpoint, функция декодирования, функция кодирования и вариативные ServerOption. Декодер и энкодер — единственный транспортно-специфичный код: только они знают про URL, заголовки и тело запроса. Самый частый вопрос после примера — «а где роутер?»; он остаётся обычным (в оригинале из FAQ — gorilla-стиль), go-kit не отбирает роутинг, лишь даёт http.Handler-обёртку над endpoint'ом. Для gRPC та же привязка выглядит как аналогичный конструктор из пакета transport/grpc с парой декодер/энкодер под proto — разбору это будет посвящён отдельный урок второй половины курса.
Путь запроса: что происходит на каждом слое#
Соберём картину целиком. Клиент шлёт POST /profiles/. Роутер транспортного слоя выбирает handler — обёртку httptransport.NewServer. Декодер превращает тело запроса в структуру PostProfileRequest; здесь транспорт может отвергнуть мусор — сломанный JSON, промах по формату. Дальше запрос попадает в цепочку endpoint-обёрток снаружи внутрь: метрики засекли старт, лимитер списал токен, логгер записал «запрос принят». Внутри цепочки вызывается сам endpoint — тонкая функция, которая приводит request к аргументам сервиса и зовёт метод. Бизнес-логика работает в третьем слое: валидирует профиль, пишет в базу, возвращает результат — и не подозревает, что её дёрнул HTTP. Ответ поднимается обратно: обёртки дописывают в лог латентность, метрика инкрементирует счётчик с меткой кода ответа, энкодер сериализует структуру и выставляет статус. Если где-то паника, FAQ советует не ловить её внутри слоёв: паника — программистская ошибка, пусть процесс падает громко, а observability-стек поднимает тревогу; при крайней необходимости recover вешают отдельной обёрткой снаружи транспорта.
Что уже есть в коробке#
| Домен | Пакет | Что внутри |
|---|---|---|
| Ядро | endpoint | Endpoint, Middleware, Chain, Failer, Nop |
| Транспорты | transport/http, /grpc, /thrift, /netrpc | NewServer и пара декодер/энкодер; в репозитории также AMQP, NATS, AWS Lambda |
| Логи | log (актуальная версия — модуль github.com/go-kit/log) | Минимальный интерфейс Logger, структурированные keyvals, обёртки With и level |
| Метрики | metrics | Counter, Gauge, Histogram; бэкенды Prometheus, statsd, Graphite, InfluxDB, expvar, DogStatsD |
| Трейсинг | tracing | OpenTracing, OpenCensus, Zipkin — спаны через endpoint-обёртки |
| Устойчивость | circuitbreaker, ratelimit | Обёртки Gobreaker и Hystrix, token bucket для лимитов |
| Распределённость | sd, sd/lb | Consul, etcd, ZooKeeper, Eureka, DNS SRV; round robin, random, retry |
| Аутентификация | auth | JWT, basic, casbin — middleware для endpoint'ов |
Микросервисы go: почему слои важны при росте#
Пара «микросервисы + Go» давно стала дефолтом бэкенда, поэтому запрос «микросервисы go» — не SEO-пустышка: экосистема реально ставит распределённые системы в центр языка. Но у микросервисов есть неприятное свойство: проблемы умножаются на количество сервисов. Логи у пяти сервисов в пяти форматах — это не логи, а расследование. Метрики без единых меток не складываются в дашборд. Исходящий вызов без таймаута и breaker'а — будущий каскадный сбой. Здесь и срабатывает главная идея go kit: раз все вызовы — и входящие, и исходящие — выглядят как endpoint, сквозные требования встраиваются один раз и работают везде. Транспортная развязка добавляет второе ускорение: gRPC для межсервисного общения и HTTP для публики садятся на одни и те же endpoints без копирования логики. Единая структура слоёв экономит самое дорогое — время людей: инженер, разобравший один go kit-сервис, разберёт и остальные, потому что декодеры на своих местах, обёртки видны в main, а бизнес-логика не пахнет HTTP. README формулирует цели прямо: работать в гетерогенной SOA, где большинство соседей — не go kit-сервисы; RPC как основной паттерн обмена; подключаемые сериализация и транспорт; никаких предписаний об инфраструктуре.
func main: сборочный цех сервиса#
Раз обёртки компонуются на старте, место сборки — функция main. FAQ отводит этому целый раздел с интригующим заголовком «Почему func main всегда такая большая». Ответ: в go kit весь граф компонентов собирается явно и декларативно в main — логгер, сервис, обёртки, endpoints, транспорты. Никаких глобальных синглтонов и магических DI-контейнеров: зависимости передаются конструкторами, жизненный цикл виден сверху донизу. Это осознанный выбор, и он приносит два дивиденда. Первый — тестируемость: компоненты не лезут в глобальное состояние, значит, тест собирает свой маленький граф из заглушек. Второй — честность зависимостей: если компоненту нужен логгер, он получает его параметром, и техдолг «тайных связей» не заводится. Дорожная карта сборки всегда одна и та же, и ниже — как она выглядит для stringsvc, классического учебного сервиса go kit.
Сборка stringsvc в func main
GOfunc main() {
logger := kitlog.NewJSONLogger(os.Stdout) // структурированный лог в stdout
var svc StringService // интерфейс сервиса (домен)
svc = stringService{} // конкретная реализация
svc = LoggingMiddleware(logger)(svc) // service-обёртка: тот же интерфейс
upper := makeUppercaseEndpoint(svc) // метод сервиса -> endpoint
mux := http.NewServeMux()
// Транспортный слой: endpoint -> HTTP-обработчик.
mux.Handle("/uppercase", httptransport.NewServer(
upper,
decodeUppercaseRequest, // *http.Request -> UppercaseRequest
encodeResponse, // UppercaseResponse -> JSON
))
logger.Log("msg", "HTTP-сервер запущен", "addr", ":8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}Читаем сверху вниз: логгер, интерфейс, реализация, обёртка, endpoint, транспорт, запуск. Каждая строка — слой; добавить метрики — значит дописать одну строку-обёртку, а не править хендлеры. kitlog здесь — актуальный модуль github.com/go-kit/log: старые пути пакета log внутри go-kit оставлены как алиасы для совместимости, README советует предпочитать новый. Обратите внимание: svc объявлен как интерфейс — LoggingMiddleware принимает и возвращает StringService, поэтому цепочка декораторов не ограничена по длине.
Куда класть ошибку: бизнес-сбой против инфраструктурного#
В распределённой системе ошибки бывают двух сортов, и go kit заставляет их различать. «Профиль не найден», «недостаточно средств», «email занят» — это бизнес-ответы: у сервиса спрашивали, сервис честно ответил «нет». Таймаут сети, недоступная база, сломанный балансировщик — инфраструктурные сбои, о которых должны знать обёртки: именно по ним срабатывает circuit breaker и запускаются ретраи. Механика такая. Бизнес-ошибку кладут в поле Err внутри response-структуры, и структура реализует интерфейс Failer (метод Failed() error) — транспорт видит неуспех и кодирует ответ с нужным статусом. Инфраструктурную ошибку возвращают вторым значением endpoint'а — по договорённости её читают сторожевые обёртки вроде breaker'а. FAQ объясняет, зачем это разделение: было бы странно, если бы «пользователь не найден» на клиенте тригал circuit breaker — это не сбой, а норма жизни. Звучит как мелочь, пока не случится первый каскадный отказ; после него это становится религией.
Код урока требует установленного Go и пакета github.com/go-kit/kit (go get, актуальный релиз v0.13.0). В песочнице курса сторонние модули не исполняются ни одним движком, поэтому интерактивы урока построены на чтении и понимании кода, а у блоков без кнопки «Запустить» — осознанное решение, а не лень. Скопируйте stringsvc к себе и прогоните curl'ом: лучший способ прочувствовать слои.
Когда go-kit уместен, а когда хватит gin#
Живое мнение, как обещал. Для типового проекта — CRUD API, монолит, команда из трёх человек, HTTP и только HTTP — go kit избыточен: gin, fiber или echo дадут тот же результат за вечер и без церемоний с декодерами; я бы не тянул слои в сервис с одним транспортом и без требований платформы. go-kit начинает окупаться при других вводных. Первая: сервисов несколько, между ними RPC, платформа требует единые логи, метрики и трейсинг, а вызовы наружу обязаны быть одинаково защищены таймаутами и breaker'ами. Вторая — долгоживущий код: шаблонная надбавка на каждый метод (декодер, энкодер, обёртка) превращается в дисциплину, которая держит архитектуру при смене людей. Третья, неожиданная, — «элегантные монолиты»: даже внутри одного бинарника модули, разделённые слоями, легче разъезжаются по сервисам потом, если до этого дойдёт. Цена тоже честная: go-kit многословен, func main растёт, порог входа выше, чем у gin. Условие моего «берите go kit» такое: вы точно знаете, что принимаете микросервисный паттерн — это формулировка из FAQ, — а не надеетесь, что он «как-нибудь пригодится».
Сигналы, что ваш выбор — go kit#
- Сервисов уже несколько, и к каждому ведут и внутренние RPC, и внешний HTTP.
- Платформа диктует наблюдаемость: единый формат логов, метрики Prometheus, трейсинг между сервисами.
- Исходящие вызовы обязаны быть защищены одинаково: таймауты, ретраи, circuit breaker, rate limiting.
- Команда большая и текучая: единая структура слоёв ускоряет онбординг сильнее, чем «читай код и разбирайся».
- Проект надолго, и вы готовы платить шаблонным кодом за предсказуемость изменений через год.
Чем go kit является по отношению к вашему коду?
В каком слое живёт бизнес-логика и что ему запрещено?
Сервис вернул «профиль не найден». Куда положить ошибку, чтобы circuit breaker вызывающего клиента не срабатывал зря?
Ручная обёртка: кто увидит запрос первым?
Прочитайте код и выберите, что он выведет:
var ep endpoint.Endpoint = uppercaseEndpoint
ep = metricsMiddleware(ep) // обёрнут первым
ep = loggingMiddleware(ep) // обёрнут вторым
// Далее где-то: resp, err := ep(ctx, req)endpoint.Chain: кто снаружи?
Прочитайте код и выберите, что он выведет:
mw := endpoint.Chain(
logging, // первый аргумент
metrics, // второй
tracing, // третий
)
ep := mw(uppercaseEndpoint)Восстановите ядро go-kit
Соберите тип endpoint'а и его привязку к HTTP. Пропуски — по порядку.
Соберите сервис по слоям
Расставьте строки сборки stringsvc в правильном порядке: от интерфейса сервиса до ListenAndServe.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Термины урока
Как устроен курс go-kit#
Курс — 25 уроков, и построен он по слоям, от ядра к периферии. Следующий урок — 02-endpoint: детальный разбор endpoint.Endpoint, обёрток и композиции. Дальше service-слой с интерфейсами (03), HTTP-транспорт с декодерами и энкодерами (04), кодеки и обработка ошибок (05). Блок наблюдаемости: kitlog (06), метрики с Prometheus (07), трейсинг Jaeger (08). Устойчивость: rate limiting (09), circuit breaker на sony/gobreaker (10), балансировка и ретраи (11), discovery с Consul (12). Затем gRPC-транспорт (13), глубокий разбор endpoint- и service-обёрток (14–15), тестирование (16), структура проекта (17), конфигурация (18), graceful shutdown (19), healthcheck (20). Финал — асинхронность, события, безопасность и деплой (21–24) и итоговый проект: микросервис заказов с endpoint'ами, метриками, breaker'ом и gRPC (25). Полная программа — на странице курса. Правило курса: сквозной пример развивается от урока к уроку, поэтому не бросайте домашки — stringsvc, который вы собрали сегодня на бумаге, к финалу превратится в сервис заказов.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- go kit — тулкит (набор пакетов), а не фреймворк: main остаётся вашим, компоненты собираются явно.
- Три слоя: transport (протокол) → endpoint (универсальная абстракция RPC и антихрупкость) → service (бизнес-логика).
- endpoint.Endpoint = func(ctx, request interface{}) (response interface{}, err error); Middleware = func(Endpoint) Endpoint.
- Логи, метрики, трейсинг, лимиты и breaker'ы — middleware-обёртки, компонуемые на старте в func main.
- Бизнес-ошибки кладут в response-структуру (Failer), инфраструктурные — в error endpoint'а.
- gin/fiber/echo быстрее для HTTP-монолита; go kit — для микросервисов, мульти-транспорта и платформенной наблюдаемости.
- Проект Питера Бургона с 2014 года, волонтёрский, актуальный релиз v0.13.0.
Домашнее задание
Соберите каркас своего сервиса на бумаге или в редакторе: интерфейс из двух-трёх методов, конкретная реализация, make-функции endpoint'ов и func main с полной сборкой — логгер, обёртка логирования, endpoints, httptransport.NewServer. Затем откройте свой прошлый gin-проект и выпишите, какие из его хендлеров стали бы endpoint'ами, а какие — обёртками. Запускать код не обязательно: цель урока — увидеть слои до того, как мы набьём руку на endpoint'ах.
Личные заметкиПусто
Вопросы и ответы по уроку
go kit или gin — что выбрать новичку?
Для первого HTTP API — gin (или fiber/echo): быстрый старт, один транспорт, минимум церемоний. go kit берите, когда проект осознанно микросервисный: несколько сервисов, RPC между ними, требования к логам, метрикам и трейсингу.
go kit ещё актуален?
Да. Проект волонтёрский и стабильный, живёт с 2014 года, актуальный релиз v0.13.0, используется в продакшене. Третий пересмотр ядра остановился на endpoint как универсальной абстракции — и это осознанная точка, а не заброшенность.
Можно ли использовать go kit вместе с gin?
Да. Роутер остаётся обычным: kithttp.NewServer возвращает http.Handler, который прицепляется к любому роутеру, включая gin через обёртку его хендлера. Главное правило одно — не тянуть транспортные типы в service-слой.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
gRPC-Go: высокопроизводительный RPC
Курс gRPC-Go: proto3, кодогенерация, unary и streaming вызовы, interceptors, TLS, дедлайны, load balancing. 25 уроков и итоговый проект.
- Писать .proto-схемы и генерировать код
- Реализовывать unary и streaming сервисы
- Управлять метаданными, дедлайнами и отменами
- Строить interceptors для логов и аутентификации