Вступление: Мифы и реальность Go-программиста
В IT-сообществе бытует два полярных мнения о работе Go-разработчика. Одни говорят, что это рай для перфекциониста: строгая типизация, отсутствие наследования классов и молниеносная компиляция. Другие — что это бесконечные if err != nil и борьба с дженериками (хотя их уже добавили).
Где правда? Как на самом деле проходит рабочий день Go разработчика в средней компании, которая пишет высоконагруженные микросервисы? Сегодня я открою дверь в свою реальность. Никакой воды — только код, тулы и реальные кейсы.
Я работаю в продуктовой компании, где Golang — основной язык бэкенда. Наш стек: gRPC, PostgreSQL, Kafka, Kubernetes. Типичная история для современной разработки на Go. Поехали.
08:30 — Утро: План и код-ревью
День начинается не с кофе, а с просмотра уведомлений в GitLab и Slack. Пока мозг свежий, лучшее время для проверки Merge Requests коллег.
Утренний рутинг: Что я смотрю в первую очередь?
- Ошибки в CI/CD — если сборка упала ночью, это первая задача.
- Комментарии к моему коду — в Go принято делать детальные ревью.
- Графики мониторинга — не просел ли latency после вчерашнего деплоя.
Кстати, о код-ревью. В Go-сообществе есть негласное правило: код должен быть простым. Если вы пишете сложный велосипед, вас завернут на ревью. Пример типичного комментария от сеньора:
// Плохо: избыточная абстракция
type Processor interface {
Process(ctx context.Context, data []byte) ([]byte, error)
}
// Хорошо: просто функция
func Process(ctx context.Context, data []byte) ([]byte, error) {
// логика
}
В 09:00 — ежедневный стендап. Мы не тратим время на долгие рассказы. Формат: что сделал вчера, что буду делать сегодня, есть ли блокеры. Никакого «как прошёл ваш вечер».
09:30 — Погружение в код: Пишем микросервис на Go
Сегодня в планах — доработка сервиса уведомлений. Это классический микросервис на Go, который принимает события из Kafka и шлёт email/push. Задача: добавить поддержку шаблонов.
Первым делом открываю структуру проекта. В Go принято минималистичное дерево папок:
notifier/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handler/
│ ├── service/
│ └── repository/
├── pkg/
│ └── models/
└── go.mod
Я начинаю с модели данных. В Go для работы с БД часто используют Go ORM (например, GORM) или чистые SQL-запросы через sqlx. Я предпочитаю sqlx — меньше магии, больше контроля.
type Template struct {
ID int64 `db:"id"`
Name string `db:"name"`
Content string `db:"content"`
CreatedAt time.Time `db:"created_at"`
}
func (r *Repository) GetTemplateByName(ctx context.Context, name string) (*Template, error) {
var t Template
err := r.db.GetContext(ctx, &t, "SELECT * FROM templates WHERE name = $1", name)
if err != nil {
return nil, fmt.Errorf("get template: %w", err)
}
return &t, nil
}
Обратите внимание на обработку ошибок. В Golang нет try-catch. Каждый вызов функции проверяется на ошибку. Это не баг, а фича — код становится предсказуемым. За 5 лет работы я ни разу не поймал неожиданный null pointer в продакшене.
11:00 — Интеграция с gRPC
Следующий этап — добавить gRPC-эндпоинт для админки. В современной разработке на Go gRPC стал стандартом для внутренних сервисов. Он быстрее REST и генерирует клиентов автоматически.
Сначала описываю proto-файл:
syntax = "proto3";
package notifier;
service TemplateService {
rpc CreateTemplate (CreateTemplateRequest) returns (TemplateResponse);
}
message CreateTemplateRequest {
string name = 1;
string content = 2;
}
message TemplateResponse {
int64 id = 1;
string name = 2;
}
Затем генерирую код через protoc и пишу реализацию. Типичный обработчик в Go выглядит так:
func (s *TemplateServer) CreateTemplate(ctx context.Context, req *pb.CreateTemplateRequest) (*pb.TemplateResponse, error) {
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "name is required")
}
template, err := s.service.Create(ctx, req.GetName(), req.GetContent())
if err != nil {
return nil, status.Errorf(codes.Internal, "create template: %v", err)
}
return &pb.TemplateResponse{
Id: template.ID,
Name: template.Name,
}, nil
}
Важный момент: в Go принято использовать пустые интерфейсы (interface{}) только в крайнем случае. Для gRPC всё строго типизировано — это спасает от багов на этапе компиляции.
12:30 — Обед и рефлексия
Перерыв — святое. Но даже за едой я иногда ловлю себя на мысли: «А не забыл ли я закрыть контекст в горутине?». Кстати, о горутинах. Это главный козырь Go разработчика. Лёгкие потоки, которые позволяют обрабатывать тысячи запросов на одном ядре.
Пример из реального кода — пул воркеров для Kafka:
func (c *Consumer) Start(ctx context.Context, workers int) {
for i := 0; i < workers; i++ {
go func(id int) {
for {
select {
case <-ctx.Done():
log.Printf("worker %d stopped", id)
return
default:
msg, err := c.readMessage(ctx)
if err != nil {
continue
}
c.processMessage(msg)
}
}
}(i)
}
}
Этот код —典型ный пример паттерна worker pool. Он стабильно работает в продакшене уже год. Ни одной утечки памяти.
14:00 — Послеобеденный слот: Деплой и мониторинг
Вторая половина дня часто посвящена инфраструктуре. Go продакшен — это не только код, но и метрики, логи, алерты.
Мы используем Prometheus для сбора метрик. Добавить их в Go-сервис — дело 10 минут:
var (
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests.",
},
[]string{"method", "path", "status"},
)