Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 1 из 25 ~15 мин +50 XP

Философия go-kit и микросервисы

Уроки gin, fiber и echo уже позади, и API на один роутер вы соберёте за вечер. Но как только сервисов становится несколько, у каждой команды всплывает один и тот же вопрос: что такое go kit и зачем он нужен, если фреймворки уже решают всё? Ответ прост: go kit — не фреймворк, а тулкит для микросервисов от Питера Бургона, где логика не зависит от транспорта, а логи, метрики и трейсинг надеваются обёртками. Разберём три слоя, принципы и честно — когда go-kit уместен.

Содержание урока · 15 разделов

Что такое 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.

Три слоя: 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: фундаментальный строительный блок

GO
package 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 вешают отдельной обёрткой снаружи транспорта.

Что уже есть в коробке#

ДоменПакетЧто внутри
ЯдроendpointEndpoint, Middleware, Chain, Failer, Nop
Транспортыtransport/http, /grpc, /thrift, /netrpcNewServer и пара декодер/энкодер; в репозитории также AMQP, NATS, AWS Lambda
Логиlog (актуальная версия — модуль github.com/go-kit/log)Минимальный интерфейс Logger, структурированные keyvals, обёртки With и level
МетрикиmetricsCounter, Gauge, Histogram; бэкенды Prometheus, statsd, Graphite, InfluxDB, expvar, DogStatsD
ТрейсингtracingOpenTracing, OpenCensus, Zipkin — спаны через endpoint-обёртки
Устойчивостьcircuitbreaker, ratelimitОбёртки Gobreaker и Hystrix, token bucket для лимитов
Распределённостьsd, sd/lbConsul, etcd, ZooKeeper, Eureka, DNS SRV; round robin, random, retry
АутентификацияauthJWT, 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

GO
func 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-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. Пропуски — по порядку.

// Универсальная абстракция одного RPC-метода. type func(ctx context., request interface{}) (response interface{}, err error) // Привязка endpoint'а к HTTP-транспорту. handler := httptransport.(upper, decodeRequest, encodeResponse)

Соберите сервис по слоям

Расставьте строки сборки stringsvc в правильном порядке: от интерфейса сервиса до ListenAndServe.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Термины урока

Термин

go kit

нажмите, чтобы перевернуть
Определение

Набор пакетов (не фреймворк) для микросервисов: robust, reliable, maintainable. Автор — Питер Бургон, проект с 2014 года, релиз v0.13.0.

1 / 6

Как устроен курс 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 · Философия go-kit и микросервисы)

Вопросы и ответы по уроку

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-слой.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

gRPC25 уроков~14 чпродвинутый25 уроков уже доступны

gRPC-Go: высокопроизводительный RPC

Курс gRPC-Go: proto3, кодогенерация, unary и streaming вызовы, interceptors, TLS, дедлайны, load balancing. 25 уроков и итоговый проект.

  • Писать .proto-схемы и генерировать код
  • Реализовывать unary и streaming сервисы
  • Управлять метаданными, дедлайнами и отменами
  • Строить interceptors для логов и аутентификации