Перейти к основному содержимому

Пример микросервиса на Go

Разработчику Архитектору

Пример микросервиса на Go

Важно

Предварительно рекомендую познакомиться с Docker.

Микросервис на языке Go демонстрирует принципы контейнеризации и быстрого развертывания приложений. Использование стандартных инструментов позволяет создать надежную среду разработки и эксплуатации. Следующие шаги могут включать добавление базы данных, настройку балансировки нагрузки или интеграцию с системой мониторинга.


Установка инструментов разработки

Процесс создания и запуска микросервиса начинается с подготовки окружения. Для работы потребуется установить компилятор языка Go, инструмент для управления сборками и программу для контейнеризации Docker.

Установка среды разработки происходит в несколько этапов. Сначала установите Go с официального сайта, добавьте go в PATH и проверьте:

go version

Разбор:

  • go version проверяет, что бинарник Go доступен в PATH и возвращает версию toolchain.
  • Команда полезна как первый smoke-check окружения: если она работает, можно переходить к go mod и сборке.
  • По выводу видно платформу (windows/amd64, linux/amd64), что помогает диагностировать проблемы кросс-сборки.

Затем установите Docker Desktop (или Engine на Linux), запустите демон и проверьте:

docker --version

Разбор:

  • docker --version проверяет, что CLI Docker установлен и вызывается из текущего терминала.
  • Эта проверка не гарантирует, что демон запущен, но подтверждает наличие клиентской части.
  • Если команда недоступна, дальше шаги со сборкой образа (docker build) не сработают.

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


Создание структуры проекта

Файловая структура проекта должна соответствовать стандартам организации кода в экосистеме Go. Корневая директория содержит исходные файлы, конфигурационные скрипты и документацию. Внутри корня создается файл main.go, который служит точкой входа для приложения.

Первым действием создают каталог проекта и инициализируют модуль Go (go.mod):

mkdir my-microservice
cd my-microservice
go mod init my-microservice

Разбор:

  • mkdir my-microservice создаёт отдельную директорию проекта, чтобы не смешивать файлы с другими задачами.
  • cd my-microservice переводит терминал в корень будущего модуля.
  • go mod init my-microservice создаёт go.mod и фиксирует модульный путь проекта.
  • После инициализации все зависимости и версии будут управляться через модульный механизм Go.

Структура файлов выглядит следующим образом:

  • my-microservice/ — корневая папка проекта;
  • main.go — основной файл с логикой сервера;
  • Dockerfile — инструкция для сборки образа контейнера;
  • go.mod — описание зависимостей модуля;
  • go.sum — контрольные суммы зависимостей (генерируется автоматически).

Такая организация позволяет легко масштабировать проект, добавлять новые пакеты и поддерживать порядок в коде. Использование стандартной структуры упрощает работу других разработчиков и автоматизированных систем сборки.


Написание кода сервера

Расширенный разбор маршрутов, форм и шаблонов — в Веб на стандартной библиотеке Go.

Файл main.go содержит реализацию простого HTTP-сервера, который обрабатывает входящие запросы и возвращает ответ клиенту. Код написан с использованием стандартной библиотеки net/http, которая входит в состав базового дистрибутива Go.

Приложение регистрирует обработчик для корневой URL-адреса /. При поступлении запроса сервер выполняет функцию обработки, которая формирует текстовый ответ. Функция http.HandleFunc связывает путь с функцией-обработчиком. Вызов функции http.ListenAndServe запускает сервер на указанном порту.

Код ITЗагрузка примера кода…

Разбор:

  • package main и func main() формируют исполняемую программу, а не библиотеку.
  • helloHandler(w http.ResponseWriter, r *http.Request) — обработчик HTTP-запроса, где w используется для ответа клиенту.
  • http.HandleFunc("/", helloHandler) регистрирует маршрут / и привязывает его к функции-обработчику.
  • http.ListenAndServe(":8080", nil) запускает сервер и блокирует поток, пока процесс не завершится или не возникнет ошибка.
  • Проверка if err != nil обязательна, потому что запуск может провалиться, например при занятом порте.

В приведенном примере используется функция fmt.Fprintf для записи данных в объект ResponseWriter. Этот объект представляет собой поток вывода ответа HTTP. Строка "Привет! Это микросервис на Go..." отправляется браузеру пользователя.

Функция main инициирует цикл обработки событий. Метод ListenAndServe блокирует выполнение программы до момента остановки сервера. Параметр :8080 указывает порт, на котором будет слушаться трафик. Если порт занят или недоступен, система вернет ошибку, которую код выводит в консоль через fmt.Printf.

Проверка сборки локально:

go build
./main # Windows: main.exe

Разбор:

  • go build компилирует проект в бинарник текущей платформы, не запуская его.
  • Запуск ./main (или main.exe) проверяет, что приложение работает как самостоятельный исполняемый файл.
  • Такой цикл удобен для production: сборка и запуск разделены, а артефакт можно упаковать в контейнер.

Конфигурация Dockerfile

Файл Dockerfile определяет инструкцию для создания образа контейнера. Он содержит последовательность шагов по подготовке окружения, копированию кода и настройке запуска. Использование многоступенчатой сборки уменьшает размер итогового образа, исключая лишние инструменты разработки из финальной версии.

Первый этап сборки использует официальный образ Go как основу для компиляции. Эта стадия называется builder. Она загружает образ с установленным компилятором и инструментами. Затем в образ копируются файлы проекта. Команда RUN go build -o /app/main . собирает исполняемый файл и помещает его в директорию /app.

Второй этап использует легкий образ Alpine Linux. Этот образ содержит только необходимые системные библиотеки, что делает его минимальным по размеру. Копирование исполняемого файла из предыдущего этапа в текущий образ происходит через команду COPY --from=builder /app/main /app/main.

Финальная инструкция CMD указывает, какую команду выполнять при запуске контейнера. В данном случае это запуск собранного бинарного файла /app/main. Образ также объявляет порт 8080 как открытый для внешних подключений.

Код ITЗагрузка примера кода…

Разбор:

  • FROM golang:1.21-alpine AS builder задаёт build-stage с компилятором Go.
  • COPY go.mod ./ и RUN go mod download позволяют кэшировать слой зависимостей и ускорять повторные сборки.
  • RUN CGO_ENABLED=0 GOOS=linux go build ... собирает статический Linux-бинарник для финального контейнера.
  • Второй FROM alpine:latest формирует runtime-stage с минимальным набором файлов.
  • COPY --from=builder /app/main . переносит только бинарник, исключая исходники и инструменты сборки.
  • EXPOSE 8080 документирует сетевой порт сервиса, а CMD ["./main"] задаёт команду запуска контейнера.

Использование переменной окружения CGO_ENABLED=0 отключает связь с C-библиотеками, что позволяет создать полностью статический бинарный файл. Такой файл не требует наличия дополнительных библиотек в целевом образе и работает быстрее. Образ Alpine обеспечивает безопасность и экономию места на диске.

Актуальный шаблон multi-stage с построчным разбором и проверкой curlGo-сервис в галерее Dockerfile; теория инструкций — Dockerfile.


Сборка и запуск контейнера

Сборка образа по Dockerfile (точка — текущая директория с файлом):

docker build -t my-go-service .
docker images

Разбор:

  • docker build -t my-go-service . собирает образ из текущего каталога и присваивает ему тег my-go-service.
  • Точка в конце команды указывает build context: именно эти файлы доступны Docker во время сборки.
  • docker images показывает, что образ успешно создан и готов к запуску.

Запуск с пробросом порта и удалением контейнера после остановки (--rm):

docker run -p 8080:8080 --rm my-go-service

Разбор:

  • docker run создаёт и запускает контейнер из образа my-go-service.
  • -p 8080:8080 связывает порт хоста с портом контейнера, чтобы сервис был доступен снаружи.
  • --rm автоматически удаляет контейнер после остановки и не оставляет "мусор" в списке контейнеров.

При запуске система выведет сообщение "Сервер запущен на порту 8080". Это подтверждает, что приложение успешно стартовало внутри изолированной среды. Контейнер остается активным и ожидает входящих запросов.

Если требуется остановить контейнер вручную, достаточно нажать комбинацию клавиш Ctrl+C в терминале. Система завершит процесс и удалит контейнер благодаря флагу --rm.


Проверка работы сервиса

Для проверки работоспособности микросервиса необходимо открыть веб-браузер и перейти по адресу http://localhost:8080. Браузер отправит HTTP-запрос на указанный порт. Сервер внутри контейнера обработает запрос и вернет текстовое сообщение.

На экране браузера отобразится следующий текст:

Привет! Это микросервис на Go, работающий внутри контейнера.

Разбор:

  • Это ожидаемое тело HTTP-ответа от обработчика /, возвращаемое функцией helloHandler.
  • Появление текста подтверждает полный путь запроса: браузер -> Docker port mapping -> Go-сервер -> ответ клиенту.
  • Если текст не появляется, проверяют контейнерные логи, проброс порта и статус процесса сервера.

Отсутствие ошибок загрузки страницы означает, что сеть между хост-машиной и контейнером настроена корректно. Порт 8080 открыт и доступен для внешнего подключения.

Альтернативная проверка без браузера — curl в терминале:

curl http://localhost:8080

Разбор:

  • curl выполняет тот же HTTP-запрос, что и браузер, но в терминале и без GUI.
  • Команда удобна для автоматических проверок и скриптов в CI/CD.
  • Совпадение ответа с браузером подтверждает, что API работает независимо от клиента.

Результат выполнения команды совпадает с тем, что отображается в браузере. Такая проверка подтверждает полную функциональность микросервиса и корректность настройки Docker.


Как развить этот пример до "боевого" сервиса

Текущий пример полезен как старт, а следующий шаг обычно связан с переходом от "один файл и один handler" к более устойчивой структуре. Ниже — практичный маршрут, который можно внедрять по частям.


Минимальная структура директорий

my-microservice/
cmd/
api/
main.go
internal/
httpapi/
handlers.go
middleware.go
service/
notes.go
storage/
memory.go
configs/
config.example.env
Dockerfile
go.mod

Разбор:

  • cmd/api/main.go хранит точку входа приложения и изолирует запуск от доменной логики.
  • internal/httpapi, internal/service, internal/storage разделяют транспорт, бизнес-правила и слой хранения.
  • Такая структура уменьшает связанность, упрощает unit-тесты и замену реализации (например, in-memory на PostgreSQL).
  • Папка configs держит примеры конфигурации и упрощает запуск в разных окружениях.

Такая декомпозиция отделяет транспортный слой (httpapi) от бизнес-логики (service) и реализации хранения (storage). Это ускоряет тестирование и упрощает замену хранения в памяти на базу данных.


Конфигурация через переменные окружения

В учебном коде порт задан строкой ":8080". Для реальных окружений удобнее читать настройки из переменных:

port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
addr := ":" + port

Разбор:

  • os.Getenv("PORT") читает значение из переменной окружения и делает настройку внешней для кода.
  • Условие if port == "" задаёт безопасный дефолт для локального запуска.
  • addr := ":" + port формирует строку адреса для ListenAndServe.
  • Подход позволяет использовать один и тот же бинарник в dev, staging и production.

Это позволяет запускать один и тот же бинарник в разных средах без пересборки. В контейнерах и оркестраторах такой подход считается стандартом.


Health-check и readiness

Один endpoint "сервер жив" часто дополняют проверкой готовности зависимостей:

  • GET /healthz — процесс работает;
  • GET /readyz — сервис готов принимать трафик (например, БД отвечает).

Разделение этих проверок снижает ложные алерты во время запуска и деплоя.


Graceful shutdown

Для корректной остановки полезно заменить ListenAndServe на http.Server и добавить обработку сигналов ОС. Тогда активные запросы успевают завершиться:

Код ITЗагрузка примера кода…

Разбор:

  • http.Server даёт больше контроля над жизненным циклом, чем простой вызов ListenAndServe.
  • Сервер запускается в отдельной goroutine, чтобы главный поток мог слушать сигналы завершения.
  • signal.NotifyContext создаёт контекст, который закрывается при SIGINT или SIGTERM.
  • context.WithTimeout(..., 5*time.Second) ограничивает время graceful shutdown.
  • srv.Shutdown(shutdownCtx) завершает приём новых соединений и даёт активным запросам время корректно закончиться.

Что добавить сразу после первого релиза

  1. Логирование запросов с временем обработки.
  2. Метрики (/metrics) и базовые алерты.
  3. Тесты на handlers через httptest.
  4. CI-шаг go test ./... и сборка Docker-образа.
  5. Линтеры (go vet, staticcheck) в pipeline.

Дополнительные сниппеты с разбором

Middleware для request-id

func requestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := strconv.FormatInt(time.Now().UnixNano(), 10)
w.Header().Set("X-Request-ID", id)
next.ServeHTTP(w, r)
})
}

Разбор:

  • Middleware оборачивает основной handler и добавляет к каждому ответу X-Request-ID.
  • Идентификатор удобно использовать для трассировки в логах и при разборе инцидентов.
  • next.ServeHTTP передаёт управление следующему обработчику в цепочке.
  • Сниппет легко расширить: передавать id в контекст и включать в structured logging.

JSON-ответ с корректным статусом

func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}

Разбор:

  • Установка Content-Type сообщает клиенту, что тело ответа в формате JSON.
  • WriteHeader(status) задаёт HTTP-код до записи тела.
  • json.NewEncoder(w).Encode(v) сериализует структуру напрямую в поток ответа.
  • Вынесение в helper сокращает дублирование и стандартизирует ответы API.

Внутренние связи и следующий шаг


Ключевые тезисы

  • Минимальный микросервис на Go строится на net/http, модуле go.mod и одном бинарнике.
  • Docker многоступенчатой сборкой уменьшает размер образа и упрощает деплой.
  • Следующий шаг после демо-сервиса — конфигурация, graceful shutdown, тесты и наблюдаемость.

Мини-практикум

  1. Вынесите порт в PORT и добавьте значение по умолчанию 8080.
  2. Добавьте endpoint GET /healthz и GET /readyz.
  3. Напишите один тест через httptest на 200 OK для корневого маршрута.

Типичные ошибки

  • Код смешивает transport, бизнес-логику и работу с данными в main.go.
  • Образ контейнера собирается без multi-stage и становится тяжелым.
  • Сервер останавливается резким kill без Shutdown(ctx).

Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.