TGViewer
Channel Public Channel
Библиотека Go-разработчика | Golang

Библиотека Go-разработчика | Golang

@goproglib

Все самое полезное для Go-разработчика в одном канале.

Учиться у нас: clc.to/qaSdww

По рекламе: @tproger_sales_bot

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4a8c24689c2151c752af0

Заявка: 8137752389
Subscribers
24.1K
Photos
2.8K
Videos
54
Links
5.4K

Showing posts older than #7304 · Back to latest

Older Posts 20 shown
Post #7302 2.99K
🤖 Используешь AI для написания кода? В Яндексе покажут, как применять AI для реальных задач разработки.

23 июня в 19:00 совместно с Яндексом проведём открытый урок «AI-инструменты в разработке: как писать код быстрее с помощью ассистентов».

Спикер — Ольга Лукьянова, руководитель команды поиска и навигации по коду в SourceCraft. Более 18 лет развивала инструменты для разработчиков в JetBrains и руководила разработкой IDE в Huawei.

Что получишь на уроке:

— поймёшь, как использовать AI-ассистентов и облачных агентов в работе;
— научишься быстрее разбираться в новых проектах и кодовой базе;
— узнаешь, какие задачи стоит отдавать AI и как получать качественный результат;
— увидишь полный workflow работы с AI: от постановки задачи до код-ревью.

На уроке — живой разбор реального проекта с кодом. Ольга покажет промпты из рабочих сценариев и ответит на ваши вопросы в Q&A.

⚠️ Количество мест ограничено

🗓️ Когда: 23 июня, 19:00 (МСК)

👉 Занять место на открытом уроке
  • 🥱 2
  • 👍 1
Post #7301 2.79K
🎉 Первый релиз кандидат Go 1.27

Вышел релиз-кандидат Go 1.27, финал ждут в августе 2026. Команда просит прогнать на нём свои тесты и нагрузку, пока есть время поймать регрессии. Что в нём интересного.

➡️ Дженерик-методы

Главная новость, которую ждали годами. Теперь метод может объявлять собственные параметры типа, а не только функция уровня пакета.

Раньше дженерик-функцию, привязанную к типу, приходилось выносить в область видимости всего пакета. Теперь так:
type Box[T any] struct{ v T }

func (b Box[T]) Map[U any](f func(T) U) Box[U] {
return Box[U]{f(b.v)}
}


Методы интерфейсов параметры типа объявлять не могут, и дженерик-методом интерфейс не реализуешь.

➡️ JSON переписали

Появились пакеты encoding/json/v2 и encoding/json/jsontext. Старый encoding/json теперь работает поверх v2. Поведение сохранили, но разбор JSON стал заметно быстрее, а кодирование осталось примерно на том же уровне. v2 строже по умолчанию, отвергает битый UTF-8 и дублирующиеся ключи в объекте.

Если что-то сломалось, есть аварийный тормоз GOEXPERIMENT=nojsonv2.

➡️ UUID в стандартной библиотеке

Новый пакет uuid генерирует и парсит UUID. Можно выкинуть одну внешнюю зависимость из проекта.

➡️ Что ещё

Мелкие аллокации до 80 байт стали дешевле примерно на 30 процентов за счёт специализированных по размеру функций выделения памяти.

Профиль утечек горутин goroutineleak доехал из эксперимента в стабильную версию и ловит горутины, навсегда заблокированные на канале или мьютексе.

Добавили экспериментальный пакет simd для портируемых векторных операций, включается через GOEXPERIMENT=simd.

Завезли постквантовые подписи crypto/mldsa по FIPS 204 и поддержку их в crypto/tls и crypto/x509. В strings и bytes появилась CutLast, в net/url методы Clone.

Каналы из пакета time теперь всегда небуферизованные, настройку asynctimerchan убрали навсегда. И минимум для macOS поднялся до Ventura 13.

Поставить и попробовать.
go install golang.org/dl/go1.27rc1@latest
go1.27rc1 download


➡️ Источник

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoLive
  • 👍 14
  • 🔥 5
  • ❤ 3
Post #7300 3.37K
😁 Интерфейс ради мока проверяет ваш мок, а не код

Сообщество Go любит интерфейсы, и они правда мощные. Но в какой-то момент «пиши тестируемый код» превратилось в «оборачивай в интерфейс вообще всё»:
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
Create(ctx context.Context, u *User) error
Update(ctx context.Context, u *User) error
Delete(ctx context.Context, id string) error
}
// и мок на 120 строк, который расходится с реальной
// реализацией в тот же миг, когда кто-то добавил метод


Мок становится грузом в поддержке. Он не говорит, корректен ли ваш SQL запрос. Он не ловит разницу в поведении pgx между pgx.ErrNoRows и реальной ошибкой скана. И он даёт ложную уверенность, тесты зелёные, а боевые запросы кривые.

➡️ Что делать вместо

Тестировать код базы против настоящей базы. testcontainers-go поднимает реальный Postgres прямо в CI. Код, сгенерированный sqlc, типизирован и корректен по построению.

Интерфейсы остаются там, где они нужны по делу, то есть на границах сервисов, где вы реально подменяете реализацию:
func TestCreateMember(t *testing.T) {
ctx := context.Background()
db := testutil.NewPostgresContainer(t) // реальный postgres, реальная схема

q := db_gen.New(db)
member, err := q.CreateMember(ctx, db_gen.CreateMemberParams{
FullName: "Witty",
Phone: "+254700000000",
})

require.NoError(t, err)
require.Equal(t, "Witty", member.FullName)
}


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

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • ❤ 5
  • 👍 4
  • 😁 3
Post #7299 3.45K
🔍 Почему у горутин нет ID

Разработчики, пришедшие из Java или Python удивляются: горутина запущена, но получить её идентификатор нельзя. Нет метода, нет структуры, нет ничего, что можно было бы сохранить и использовать потом. В Go это сделано намеренно.

❓ Как это устроено

Горутина — анонимный воркер. Она не имеет имени, не возвращает дескриптор при запуске, не регистрируется нигде в рантайме с точки зрения программиста.

Это не техническое ограничение. Рантайм Go знает о каждой горутине всё что нужно — стек, статус, планировщик. Но эта информация намеренно скрыта от прикладного кода.

Причина в том, что именованные горутины провоцируют плохой дизайн. Когда у потока есть ID, программисты начинают привязывать к нему состояние — «этот поток обрабатывает этот запрос, значит туда и кладём контекст». Именно так работает thread-local storage, и именно это создаёт проблемы в конкурентных системах.

Пример из реальной жизни: если бы net/http хранил состояние запроса в горутине по ID, сервер не мог бы распараллелить обработку одного запроса между несколькими горутинами. Вся архитектура стала бы жёстче.

➡️ Подводные камни

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

Похожая история возникает с goroutine-local storage — его периодически пытаются реализовать через runtime.Stack и парсинг вывода. Это работает, но Go-команда считает такой подход антипаттерном: он хрупкий, медленный и ломается при изменении формата стека.

➡️ Пример кода

Правильный способ передать состояние между горутинами — канал или context.Context:
func worker(ctx context.Context, jobs <-chan int, results chan<- int) {
for {
select {
case j, ok := <-jobs:
if !ok {
return
}
results <- process(ctx, j)
case <-ctx.Done():
return
}
}
}


Горутина не знает своего ID. Но она знает, что делать, через аргументы и каналы. Этого достаточно.

Отсутствие goroutine ID — осознанное решение, которое держит архитектуру чистой. Вместо того чтобы привязывать состояние к конкретной горутине, Go предлагает передавать его явно — через каналы и контекст. Это делает код проще для тестирования и масштабирования.

У нашей рассылки есть ID. Вот он — https://clc.to/mKZg2A

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoDeep
  • 👍 10
  • 😁 2
Post #7298 3.55K
🚀 Java догнала Go, а на больших запросах обогнала

В 2020 Марк Нельсон проверял, может ли Java-микросервис держать скорость как у Go. Тогда для маленького сервиса вышла ничья. В июне 2026 замер повторился на свежих рантаймах, и расклад поменялся.

Оба сервиса гоняли по очереди на одной машине, без конкуренции за CPU. Go на стандартном net/http, без фреймворка. Java на Helidon SE с виртуальными потоками, в двух вариантах. Обычный JVM от Oracle JDK 26 и тот же JDK с AOT-кэшем Leyden.

Сервис делал немного работы на запрос. Регистр, разворот строки, CRC32 и JSON в ответ.

❓ Что показали цифры

На одном воркере и 7 байтах все шли рядом. Go около 3 200 запросов в секунду, JVM около 2 722, Leyden около 3 561. Классическая картинка из старых споров, где Go стартует резво.

А потом подняли конкуренцию до 192 воркеров, и кривая разъехалась. На 7 байтах Go выдал около 59 тысяч запросов в секунду, JVM около 74 тысяч, Leyden около 99 тысяч.

На 2 КБ пик Go около 17 тысяч против 40 с лишним тысяч у обоих вариантов Java. На 8 КБ Go около 6,8 тысячи, Java около 15 тысяч. Отказов не было нигде. В финальной матрице Go не выиграл ни одной ячейки.

❓Какие выводы

Старый тезис «Java тяжёлая, для мелких сервисов берите Go» этот прогон не подтверждает. Go по-прежнему хорош, компактный код и поставка одним бинарником никуда не делись. Но виртуальные потоки и зрелый JVM делают блокирующий код Java дешёвым, а Helidon SE держит сервис маленьким, так что сравнение не «Go против монстра-фреймворка».

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

➡️ Код и сырые результаты

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoLive
  • ❤ 9
  • 🤔 5
  • 🥱 5
  • 👍 1
Post #7297 3.4K
🤨 Не прокидывайте context в чистые функции. Он стоит дороже, чем кажется

Проброс контекста штука верная и важная. Но совет «всегда прокидывай context» превращается в карго культ, когда ctx добавляют в сигнатуру каждой функции, включая чистые функции без всякого ввода вывода, просто потому что так сказал линтер или комментарий в PR:
// Этой функции context не нужен
func calculateInstallments(principal float64, rate float64, months int) []Installment {
// чистая математика, нет I/O, нет горутин
}


У контекста есть цена. Это аргумент, который приходится тянуть повсюду, он зашумляет сигнатуры и провоцирует пихать зависимости через context.WithValue, чего делать не стоит.

➡️ Где он нужен

Прокидывайте контекст туда, где он реально используется. Это вызовы базы, HTTP клиенты, gRPC стабы, всё, что блокируется на I/O или нуждается в отмене. Не прокидывайте его в чистые функции, доменную логику и всё, что возвращается за микросекунды независимо от отмены.

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

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • 👍 12
  • ❤ 2
Post #7296 3.35K
⏰ Уже сегодня в 19:00 (МСК) стартует открытый урок!

Тема:

«Мультиагентные системы: почему большинство архитектур переусложнены»


🔥 За 90 минут разберёмся, когда действительно стоит строить мультиагентную систему, а когда она только добавляет сложность, расходы и новые точки отказа.

Поговорим о критериях выбора архитектуры, типичных ошибках и ограничениях современных ИИ-агентов, которые важно учитывать ещё до внедрения в продукт.

🎙️ Спикер — Дмитрий Юдин, руководитель AI/ML-направления в Сloud․ru.

🎁 Для всех участников подготовили промокод на скидку 10 000 ₽ на курс «Разработка ИИ-агентов».

👉 Успей присоединиться к уроку
  • ❤ 3
Post #7294 3.31K
☕️ Топ-вакансий для Go-разработчиков за неделю

Go-разработчик — от 100 до 350 тысяч рублей в зависимости от нагрузки

Джун+ в Москву

Go разработчик — 320 тысяч рублей и гибрид в Москве

➡️ Еще больше топовых вакансий — в нашем канале Go jobs

🐸Библиотека Go-разработчика

#GoWork
  • ❤ 3
Post #7293 3.27K
🖥 Монолитные тесты Go API: cлой логики, слой HTTP и трейдоффы

Мы вынесли моки в mock.go и наметили разбиение тестов на слои. Теперь покажем сами тесты двух слоёв и честно разберём, чем платим за такую структуру.

🛠 Бизнес логика (`*_service_test.go`)

Этот файл проверяет только доменную логику. Слой репозитория замокан через mock.go, поэтому тесты гоняются целиком в памяти и работают очень быстро.

Здесь проверяют правила валидации, обработку кривых данных, доменные ошибки и переходы состояний:
func TestAuthServiceLogin(t *testing.T) {
t.Run("login credentials", func(t *testing.T) {
testAuthRepo.GetUsernameOrEmailFunc = func(ctx context.Context, username string) (*models.Users, error) {
hash, _ := bcrypt.GenerateFromPassword([]byte("!Abc1234"), bcrypt.DefaultCost)
return &models.Users{ID: 1, Username: "test_account", Password: string(hash)}, nil
}
req := auth.LoginRequest{Username: "rdev", Password: "!Abc1234"}
_, err := testAuthSvc.Login(context.Background(), req)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
})
}


🛠 HTTP слой (`*_handler_test.go`)

Этот файл проверяет, как API общается с внешним миром. Он поднимает запросы через net/http/httptest и использует моки сервиса, чтобы не дёргать реальную логику. Под проверкой статус коды, сериализация JSON, параметры запроса, заголовки, работа мидлварей вроде rate limiter.

Табличный тест гоняет несколько кейсов входа сразу:
func TestAuthHandlerLogin(t *testing.T) {
tests := []struct {
name string
requestBody any
expectedStatus int
}{
{"complete credentials", loginRequest{"rdev", "!Abc1234"}, http.StatusOK},
{"no password", loginRequest{"rdev", ""}, http.StatusBadRequest},
{"password too short", loginRequest{"rdev", "1234"}, http.StatusBadRequest},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, w := helpers.SetupJSONTestContext(t, http.MethodPost, "/login", tt.requestBody)
testAuthHandler.Login(ctx)
if w.Code != tt.expectedStatus {
t.Errorf("Login() status = %d, want %d", w.Code, tt.expectedStatus)
}
})
}
}


❔ Чем платим

Плюсы:

• баги в роутинге ищутся в тесте хендлера
• баги в расчётах ищутся в тесте сервиса
• изоляция моков снимает циклические импорты
• границы слоёв не дают случайно проверить механику HTTP внутри теста логики

Минусы:

• чуть больше бойлерплейта
• несколько файлов требуют начальной настройки
• при изменении интерфейса сервиса mock.go придётся править руками, если не подключить генератор вроде mockery.

Разделение тестов по ответственности это проверенная для Go стратегия. Изолируете HTTP логику от бизнес логики, выносите моки в один файл и получаете набор тестов, который не разрастается, не ломается циклическими импортами и легко читается.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • ❤ 4
  • 👍 1
Post #7292 2.76K
⚡️ Быстрый бэкап с шифрованием и дедупликацией из коробки

restic — это открытая программа бэкапа на Go, которая работает на Linux, macOS, Windows, а также FreeBSD и OpenBSD. На GitHub у проекта около 34 тысяч звёзд.

Что делает


Снимает инкрементальные снапшоты ваших файлов, шифрует их, дедуплицирует и складывает в хранилище, которому не обязательно доверять. Каждый новый снапшот занимает место только под реальный прирост данных, а дубликаты убираются ещё до записи в бэкенд.

➡️ Принципы, на которых построен

Просто. Бэкап и восстановление не должны быть сложными, иначе их пропускают.

Быстро. Скорость упирается только в сеть или диск, при восстановлении переносятся лишь те данные, что нужны для нужных файлов.

Проверяемо. Восстановление важнее самого бэкапа, поэтому restic даёт легко убедиться, что данные реально восстановимы.

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

Эффективно
. Рост данных не означает рост хранилища в разы за счёт дедупликации.

➡️ Как пользоваться

Сначала создаёте репозиторий под бэкапы. Пароль обязателен, без него данные не восстановить:
restic init --repo /tmp/backup


Затем кладёте данные. restic сам сканирует и показывает прогресс со скоростью и ETA:
restic --repo /tmp/backup backup ~/work


Восстановить можно командой restic restore, либо смонтировать репозиторий через fuse командой restic mount и ходить по файлам прошлых снапшотов как по обычной папке.

➡️ Куда складывать бэкапы

Хранить копию на той же машине это не стратегия. restic нативно поддерживает локальную папку, sftp по SSH, собственный REST сервер, Amazon S3 и совместимый Minio, OpenStack Swift, Backblaze B2, Azure Blob Storage, Google Cloud Storage. Через rclone подключается ещё множество сервисов.

Приятная деталь

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

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

➡️ Репозиторий

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • 👍 5
  • ❤ 2
  • 👾 1
Post #7291 2.8K
🧑‍💻 Монолитные тесты Go API: почему один файл душит проект

Когда проект на Go растёт, тесты часто скатываются в один гигантский *_test.go. В него свалено всё подряд. Настройка окружения, рукописные моки репозитория и сервиса, проверка бизнес логики, симуляция HTTP. Сначала удобно, потом файл пухнет до тысяч строк. В этой части разберём, чем это плохо и как вынести моки в одно место.

⚙️ Две боли монолита

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

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

⚙️ Решение — разделить по ответственности

Монолит разбивается на три файла внутри тестового пакета:
package_test/
mock.go // общие моки сервиса и репозитория
*_service_test.go // юнит тесты бизнес логики
*_handler_test.go // HTTP, роутинг, валидация


Моки в одном месте

Вместо копипаста по разным файлам все структуры заглушек объявляются один раз в отдельном mock.go. Это единый источник правды для тестовых дублей, и именно вынос моков убирает циклические импорты:
type MockAuthRepository struct {
GetUsernameOrEmailFunc func(ctx context.Context, username string) (*models.Users, error)
RegisterFunc func(ctx context.Context, user *models.Users) error
}

func (m *MockAuthRepository) GetUsernameOrEmail(ctx context.Context, username string) (*models.Users, error) {
if m.GetUsernameOrEmailFunc != nil {
return m.GetUsernameOrEmailFunc(ctx, username)
}
return &models.Users{}, nil
}


Один файл с тестами это удобный старт и плохой финал. Разрастание кода и циклические импорты лечатся выносом моков в mock.go и разбиением тестов на слои. В части 2 покажем тесты бизнес логики и HTTP слоя, а также трейдоффы подхода.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • 👍 4
  • ❤ 3
Post #7290 2.53K
✏️ Partition Array According to Given Pivot на Go

LeetCode 2161. Задача простая, но даёт отличный повод вспомнить, как устроен один шаг быстрой сортировки.

Условие: есть массив nums и число pivot. Нужно переставить элементы так, чтобы сначала шли все числа меньше опорного, затем равные ему, потом большие.

Ключевая деталь: внутри групп меньших и больших исходный порядок должен сохраниться. То есть это устойчивая разбивка массива на три части — как партиция в quicksort, только без потери порядка.

➡️ Способ 1: три прохода

Самый прямой подход — пройти массив трижды:

• первый проход собирает всё, что меньше pivot;
• второй добавляет равные;
• третий — большие.

Порядок внутри каждой группы сохраняется сам собой: мы всегда идём слева направо:
func pivotArray(nums []int, pivot int) []int {
res := make([]int, 0, len(nums))
for _, v := range nums {
if v < pivot {
res = append(res, v)
}
}
for _, v := range nums {
if v == pivot {
res = append(res, v)
}
}
for _, v := range nums {
if v > pivot {
res = append(res, v)
}
}
return res
}


Время — O(n), память — O(n). Три прохода по массиву длины n всё равно дают линейную сложность.

➡️ Способ 2: два указателя за один проход

А можно собрать ответ за один проход. Меньшие пишем слева, большие — справа, а середину потом заполняем опорным значением:
func pivotArray(nums []int, pivot int) []int {
n := len(nums)
res := make([]int, n)
left, right := 0, n-1
for i, j := 0, n-1; i < n; i, j = i+1, j-1 {
if nums[i] < pivot {
res[left] = nums[i]
left++
}
if nums[j] > pivot {
res[right] = nums[j]
right--
}
}
for left <= right {
res[left] = pivot
left++
}
return res
}


Как это работает:

• i бежит вперёд и кладёт меньшие числа в левую часть — в исходном порядке;

• j бежит назад и кладёт большие числа в правую часть.

Почему порядок больших не ломается? j движется с конца, поэтому более поздние большие элементы попадают правее — относительный порядок сохраняется.

После цикла промежуток между left и right остаётся под равные pivot. Его и дозаполняем.

❓ Что выбрать

Оба решения линейны по времени, разница в акцентах:

• три прохода читаются проще и отлично смотрятся на собеседовании, когда важна ясность;

• два указателя экономят проходы и делают всё за один цикл — это заметно на больших массивах.

Логика под капотом одна: бьём массив на три части и сохраняем исходный порядок меньших и больших.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#ReadySetGo
  • ❤ 6
Post #7289 2.95K
✅ Graceful Shutdown в Go: полная защита с отклонением новых запросов

server.Shutdown() ждёт текущие запросы, но не закрывает входящий трафик мгновенно. Между сигналом и фактической остановкой сервер продолжает принимать новые соединения. При высокой нагрузке это окно создаёт проблемы.

Финальный паттерн закрывает и эту щель.

📎 Добавляем middleware для отклонения новых запросов

Используем атомарный флаг и промежуточный обработчик:
package main

import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"
)

var isShuttingDown int32

func rejectMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if atomic.LoadInt32(&isShuttingDown) == 1 {
http.Error(w, "server is shutting down", http.StatusServiceUnavailable)
return
}
next.ServeHTTP(w, r)
})
}

func handler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
fmt.Printf("START %s\n", id)
time.Sleep(5 * time.Second)
fmt.Printf("DONE %s\n", id)
w.Write([]byte("COMPLETED!"))
}

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)

server := &http.Server{
Addr: ":8080",
Handler: rejectMiddleware(mux),
}

go func() {
fmt.Println("server started at :8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(err)
}
}()

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

<-ctx.Done()
fmt.Println("shutdown signal received")

atomic.StoreInt32(&isShuttingDown, 1)

shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

server.Shutdown(shutdownCtx)

fmt.Println("graceful shutdown complete")
}


❔ Что здесь происходит

После получения сигнала сначала ставится флаг isShuttingDown = 1. rejectMiddleware проверяет этот флаг перед каждым запросом. Если флаг поднят, новый клиент получает 503 Service Unavailable. Уже выполняющиеся обработчики продолжают работу и завершаются штатно.

atomic.LoadInt32 и atomic.StoreInt32 нужны потому, что флаг читается и пишется из разных горутин. Без атомарных операций здесь была бы гонка данных.

Итоговая картина

Путь от первого паттерна до последнего выглядит так. Сначала сервер просто падает. Потом учится слышать сигналы. Потом начинает ждать текущие запросы. Наконец, закрывает вход для новых до того, как начнёт завершаться.

Каждый уровень добавляет одну гарантию. Финальный вариант даёт полный контроль над состоянием сервера во время остановки.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • ❤ 11
  • 🤔 1
Post #7287 3.02K
👨‍💻 Строка, похожая на комментарий, которая молча сломала прод

В одной команде была здоровая культура тестов. Каждый важный эндпоинт покрыт, CI не пускал PR в мердж, пока тесты не зелёные. Однажды утром прилетел инцидент на проде. Разработчик правил логику обработки ответа стороннего сервиса, прогнал тесты локально, получил 144 passed, отправил PR, CI стал зелёным, фикс уехал в прод. Через час прод лёг.

При разборе выяснилось, что нужный тест TestCartActionHandler вообще не запускался. Не падал, не скипался, его просто не было в списке из 144 тестов. В первой строке файла, над объявлением пакета, стояла строка, которую все приняли за комментарий:
//go:build integration


Это и оказалось причиной. Дальше разберём, как одна такая строка выкидывает целый файл из сборки.

❓ Что такое build tags

Build tag это директива в начале .go файла, которая говорит тулчейну, включать ли файл в конкретную сборку. Работает как условный шлюз на уровне файла. Когда вы запускаете go build или go test, тулчейн проверяет ограничение каждого файла против текущего контекста сборки.

Выражение истинно, файл компилируется. Выражение ложно, файл исключается полностью, как будто его нет на диске.

Это не скип в рантайме, это исключение на этапе компиляции. Файл вообще не попадает в бинарь, поэтому ни одна тестовая функция из него не регистрируется и никакого вывода от него вы не увидите.

🛠 Синтаксис

Тег это однострочная директива в самом верху файла, до package, до импортов:
//go:build <выражение>


Три правила, которые проверяет тулчейн. Директива стоит в первой строке (выше допустимы только пустые строки и другие //go: директивы). Префикс ровно //go:build, без пробела между // и go. Выражение булево, собирается из имён тегов через &&, ||, ! и скобки.

🛠 Язык выражений

Файл с тегом попадёт в сборку, только если вы явно попросите через -tags. Не попросили, файла как бы не существует.
//go:build tagname              // истинно, когда задан -tags tagname
//go:build !tagname // истинно, когда tagname НЕ задан
//go:build tagA && tagB // оба заданы
//go:build tagA || tagB // задан любой из двух
//go:build (tagA || tagB) && !tagC // группировка скобками

Активируется тег через флаг -tags у любой go команды:
go build -tags linux ./...
go test -tags integration ./...
go run -tags debug main.go
go test -tags integration,gpu ./...


Флаг работает одинаково для go build, go test, go run и go vet. То, что вы передаёте, добавляется в активный набор тегов только для этого вызова и не сохраняется.

🛠 Где это нужно

Платформенные реализации. Один пакет, одна сигнатура функции, разный файл под каждую ОС. Компилятор сам подберёт нужный по GOOS, без switch runtime.GOOS и без мёртвого кода в бинаре:
//go:build linux
func watch(path string) { /* inotify */ }

//go:build darwin
func watch(path string) { /* kqueue */ }


Так устроена и стандартная библиотека. У os.RemoveAll три отдельных файла, каждый со своим тегом под unix, windows и фолбэк на остальное.

Разделение unit и интеграционных тестов. Интеграционным часто нужна живая инфраструктура, юнит тестам нет. Тег прячет тяжёлые тесты, чтобы локально не поднимать базу ради простой проверки:
//go:build integration

package repository_test
func TestUserRepository_Create(t *testing.T) { ... } // ходит в реальный Postgres

Опциональные тяжёлые зависимости и debug инструментация работают так же. Под тегом gpu лежит реализация на CUDA, без тега чистый Go фолбэк. Под тегом debug живут дампы состояния, которые в прод бинарь не попадают вообще.

❓ Чем закончился инцидент

Файл с тестом имел тег //go:build integration, а команда make test запускала go test без -tags integration. Файл молча исключался из прогона, и его тесты не запускались ни разу с момента старта проекта. Скелет теста скопировали из соседнего проекта, где make test сознательно прокидывал нужный тег. Тот, кто копировал, принял первую строку за комментарий. Ревьюер тоже не заметил.

Таких файлов в кодовой базе нашлось ещё 13. После снятия тега и прогона набор показал 208 тестов вместо 144.
--- FAIL: TestHandlePaymentPartyResponse (0.31s)
--- FAIL: TestUserSignatureValidation (0.18s)
...
FAIL: 9 tests failed, 208 tests total


64 теста не выполнялись никогда, а среди 9 падений были баги, уже жившие в проде. Зелёный бейдж CI всё это время был ложноположительным.

➡️ Как чуть не сломался прод

📍 Навигация: Вакансии • Задачи • Собесы • Канал в Max

🐸 Библиотека Go-разработчика

#GoDeep
  • ❤ 17
  • 👍 1
  • 🤔 1
Post #7286 3.1K
🌃 Graceful Shutdown в Go: http.Server.Shutdown

Обработка сигналов позволяет среагировать на завершение работы. Но она не ждёт текущие запросы. Чтобы это исправить, в Go есть server.Shutdown().

❔ Как это работает

Создаём http.Server явно и используем его метод Shutdown:
package main

import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Println("handling request...")
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "done")
}

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)

server := &http.Server{
Addr: ":8080",
Handler: mux,
}

go func() {
fmt.Println("server started at :8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(err)
}
}()

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

<-ctx.Done()
fmt.Println("shutdown signal received")

shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

server.Shutdown(shutdownCtx)

fmt.Println("graceful shutdown complete")
}


Ключевой блок:
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

server.Shutdown(shutdownCtx)


server.Shutdown() останавливает приём новых соединений и ждёт, пока текущие запросы завершатся. Таймаут контекста страхует от зависания — если через 10 секунд что-то ещё не завершилось, процесс всё равно выйдет.

🔍 Что остаётся нерешённым

Есть небольшое окно между получением сигнала и вызовом Shutdown. В это время сервер всё ещё принимает новые соединения. При высокой нагрузке это может привести к непредсказуемому поведению — часть запросов получит ответ, часть будет оборвана.

server.Shutdown() решает главную проблему — in-flight запросы дожидаются завершения. Для большинства сервисов этого достаточно. Но если нужна полная управляемость трафиком во время shutdown, нужен ещё один слой.

📍 Навигация: Вакансии • Задачи • Собесы • Канал в Max

🐸 Библиотека Go-разработчика

#GoToProduction
  • 👍 11
Post #7285 2.94K
📰 С днём воскресенья

Поздравляем с выходными и дарим вам дайджест!

— Go или Rust

— Google рассказали что нового в Go

— Сборщик мусора видно прямо в терминале

— Вакансии 400+ денег

— pgCenter 0.10.0

📍 Навигация: Вакансии • Задачи • Собесы • Канал в Max

🐸 Библиотека Go-разработчика

#GoLive
Post #7284 3.22K
🎥 До открытого урока — несколько дней. Подготовили небольшую подборку материалов от нашего спикера Дмитрия Юдина.

Дмитрий руководит AI/ML-направлением в Сloud․ru и развивает Evolution AI Factory — среду для работы с GenAI: от инфраструктуры обучения LLM до внедрения интеллектуальных агентов.

С чего начать:

📺 AI-инструменты для разработчиков — как код, автотесты и ассистенты меняют рутину инженера.
📺 AI-эволюция бизнеса в эпоху генеративных моделей — агентные системы в реальных продуктах.
📺 Разработка мертва? — дискуссия о будущем профессии и роли AI в ней.
📖 Применение LLM в бизнесе — статья Дмитрия о практике внедрения и роли облака.

Одна из ключевых тем Дмитрия — практическое применение агентных систем и их ограничения.

Именно об этом — бесплатный урок 18 июня в 19:00: «Мультиагентные системы: почему большинство архитектур переусложнены» 🔥

🎁 Для участников подготовили промокод на скидку 10 000 ₽ на курс «Разработка ИИ-агентов».

👉 Успей занять место на открытом уроке
  • ❤ 1
  • 🥱 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →