1. Карта екосистеми Go
Коли кажуть «екосистема Go», легко уявити собі ринок зі спеціями: тут роутери, там логери, а он там ORM на будь-який смак і колір. Але корисніше тримати в голові приземленішу, зате робочу картину: у нас є стандартна бібліотека, є «напівофіційні» пакети (наприклад, golang.org/x/...) і є зовнішні бібліотеки від спільноти. І це нормально: Go не намагається включити до стандартної поставки все, що тільки можливо, але при цьому дає дуже потужний базовий набір.
Важлива думка для розробника-початківця така: ви не зобовʼязані знати всі бібліотеки. Вам досить розуміти, які завдання взагалі трапляються (CLI, HTTP, конфіг, логи, БД, тести…), що покриває стандартна бібліотека і як оцінювати вартість зовнішньої залежності. Це як з інструментами на кухні: базовий ніж і дошка вирішують 80% завдань, а от «ультразвуковий ніж для помідорів» варто купувати лише тоді, коли ви справді ріжете помідори потоком.
Стандартна бібліотека: ваш перший рюкзак
Стандартна бібліотека Go — це не «мінімальний набір, щоб можна було скомпілюватися», а дуже практична база, на якій справді можна будувати застосунки. Причому ця база розвивається: інколи зʼявляються нові пакети, які закривають те, що раніше доводилося вирішувати вручну або через сторонні рішення. Наприклад, у Go 1.21 додали log/slog для структурованого логування та пакети slices, maps, cmp, які спрощують роботу з типовими операціями.
Корисно сприймати стандартну бібліотеку як «набір перевірених часом цеглин». Вони не завжди наймодніші й не завжди найзручніші в один виклик, але зазвичай стабільні, передбачувані, документовані й підтримуються командою Go. А ще вони майже завжди дають вам правильні абстракції, які потім легко тестувати й супроводжувати: io.Reader/io.Writer, context.Context, net/http, encoding/json, database/sql (як інтерфейс до SQL-драйверів) і так далі.
Давайте зафіксуємо це у вигляді таблиці: що зазвичай потрібно застосунку і чим на це відповідає stdlib.
| Задача в застосунку | Стандартна бібліотека (перший кандидат) | Коментар «як мислити» |
|---|---|---|
| CLI-аргументи та прапорці | |
Просто, достатньо для більшості утиліт |
| HTTP-сервер | |
База, на якій працює й багато фреймворків |
| URL та query params | |
Безпечніше, ніж склеювати рядок |
| JSON | |
Не найшвидший, але де-факто стандарт |
| Логи | |
slog — це структуровані поля та рівні |
| Тести | |
Сильна база, особливо в табличному стилі |
| Робота з часом | |
Таймаути, дедлайни, форматування |
| Файли/файлова система | |
Ними закривається більшість практичних завдань |
Мініприклад у стилі нашого tasker: логер зі стандартної бібліотеки для структурованого логування — це вже не «один рядок на все», а цілком нормальні поля.
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
logger.Info("завдання створено", "id", 42, "title", "купити молоко")
// завдання створено id=42 title="купити молоко"
}
Зверніть увагу на приємну річ: ми нічого не підключали, не сперечалися про «найкращий логер 2026 року», а вже отримали корисний формат. Саме так Go часто й працює: спочатку беремо stdlib — і дуже часто на цьому можна зупинитися.
2. Коли stdlib — найкращий вибір
Початківці інколи сприймають вибір стандартної бібліотеки як «я просто не знаю нормальних бібліотек». На практиці в досвідчених Go-розробників часто навпаки: stdlib — це усвідомлений вибір, бо вона зменшує кількість рухомих частин. Чим менше рухомих частин — тим менше місць, де все може зламатися в пʼятницю ввечері (а пʼятниця, як відомо, спеціально створена для падінь у продакшені).
Є простий мисленнєвий тест: якщо ви можете розвʼязати задачу за допомогою стандартної бібліотеки за розумний час і зі зрозумілим кодом, то stdlib майже завжди виграє. Ви економите час на читанні документації, оновленнях, транзитивних залежностях і на сюрпризах на кшталт «у новій версії змінили поведінку за замовчуванням». І головне: ви зменшуєте площу змін у проєкті — менше чужого коду, який ви не контролюєте.
Для нашого tasker це дуже добре видно на HTTP-рівні. Базовий сервер на net/http виглядає просто й прозоро:
package main
import (
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
_ = http.ListenAndServe(":8080", mux)
}
І так: це вже схоже на «справжній застосунок». Можна додавати middleware (у Go це часто просто обгортки навколо handlerʼів), можна підключати логування, можна робити JSON-відповіді — і все це залишатиметься читабельним.
Окремий бонус stdlib — її еволюція. Те, що раніше робили вручну або через сторонні пакети, поступово зʼявляється «офіційно», якщо це масова потреба. Наприклад, сімейство функцій для роботи зі слайсами в пакеті slices розвивалося так, щоб зменшувати типові помилки, зокрема під час роботи з «хвостами» та звільненням посилань через clear. Це гарний приклад того, як Go намагається спрощувати життя без перетворення мови на «зоопарк магії».
3. Навіщо потрібні зовнішні бібліотеки
Якщо стандартна бібліотека така класна, навіщо взагалі потрібні зовнішні пакети? Бо світ завжди складніший за базовий набір. Інколи вам потрібен кращий досвід розробника, інколи — вища продуктивність у критичній частині виконання, інколи — готові інтеграції (наприклад, конкретна база даних, конкретна система метрик), а інколи — просто влучніший API для вашої команди.
Найважливіше правило: зовнішня бібліотека — це не лише функціональність, а й зобовʼязання. Ви берете чужий код у свій проєкт, а значить, погоджуєтеся жити з його оновленнями, можливими вразливостями, змінами API і навіть із тим, що автор одного дня може «втомитися» та кинути підтримку. Тому правильна логіка звучить так: «ми беремо залежність, бо вона дає вимірювану користь, і ми розуміємо її ціну».
Є і другий аспект: Go-проєкти часто будують архітектуру так, щоб зовнішні бібліотеки жили на межі системи — в адаптерах, а ядро, тобто домен і use cases, залишалося максимально простим. Тоді ви можете замінити бібліотеку, не переписуючи весь застосунок. Це ми вже робили концептуально, коли відокремлювали CLI/HTTP від бізнес-логіки і коли зіставляли доменні помилки з кодами завершення або HTTP-статусами.
4. Орієнтири щодо популярних бібліотек
Зараз буде важливе зауваження: популярність бібліотек змінюється, і задача цієї лекції — не видати вам «список єдино правильних пакетів», а дати карту місцевості. Тобто: які категорії бувають і які назви ви регулярно зустрічатимете в реальних репозиторіях. Навіть якщо ви не оберете ці бібліотеки, ви принаймні не дивитиметеся на них як на прибульців.
Нижче — практична таблиця: «задача», «альтернатива зі stdlib», «зовнішні варіанти, які часто трапляються» — саме як орієнтири.
| Категорія | Що є в stdlib | Що часто використовують зовні (орієнтири) | Коли це буває виправдано |
|---|---|---|---|
| CLI-команди/підкоманди | |
|
Коли багато підкоманд, потрібні автогенерація help і складний UX |
| HTTP-роутинг | |
chi, gin, echo, (інколи старі gorilla/mux) | Коли потрібні зручний роутинг, групи middleware та «красиві» параметри |
| Конфіг | |
|
Коли багато налаштувань і хочеться єдиного шару «flags/env/file» |
| Логування | |
|
Коли важливі швидкість, формат чи інтеграції, але stdlib уже часто вистачає |
| Валідація структур | вручну (за допомогою if) | |
Коли багато DTO та правил валідації, особливо на межах (HTTP/JSON) |
| Робота з SQL | |
pgx (як драйвер/клієнт), sqlx, ORM на кшталт gorm | Коли потрібні зручні скани/мапінг, але важливо не втратити контроль |
| Міграції БД | (немає) | |
Коли в проєкті є БД і потрібна дисципліна схем |
| Тест-асерти та моки | |
|
Коли хочеться коротших тестів, але важливо не зробити їх «магічними» |
| Метрики/трейсинг | (немає повного набору) | |
Коли сервіс уже працює в продакшені й потрібна спостережуваність |
| UUID | (базового генератора, який підходив би всюди, немає) | |
Коли формат ID має бути стандартним |
Як цим користуватися на практиці? Дуже просто: коли ви бачите в чужому проєкті chi, ви розумієте «це роутер поверх net/http»; коли бачите cobra, ви розумієте «це комбайн для CLI»; коли бачите pgx, ви розумієте «це про PostgreSQL». І ви можете читати код без паніки (а паніка, нагадаю, у нашому курсі взагалі не для всього підряд).
5. Ціна залежності: граф, оновлення та підтримка
Коли ви додаєте залежність, ви додаєте не лише рядок у go.mod, а й цілий ланцюг. Бо залежності бувають прямі — ті, що ви імпортуєте, — і транзитивні, тобто ті, що імпортують ваші залежності. У підсумку невеликий зручний пакет може притягнути десятки інших модулів, а разом із ними — оновлення, CVE, несумісності та сюрпризи.
Go Modules якраз влаштовані так, щоб залежностями можна було керувати дисципліновано. Команди на кшталт go get і go mod tidy змінюють go.mod і go.sum, і це очікувана поведінка: інструмент підтримує проєкт у узгодженому стані. А ще важливо памʼятати про встановлення інструментів фіксованої версії через go install module@version, щоб «учора працювало, сьогодні теж працює», а не «у мене на ноутбуці магія».
Корисно інколи намалювати собі просту картину залежностей. Наприклад, наш tasker (спрощено) виглядає так:
flowchart TD
cmd[cmd/tasker/main.go] --> cli[CLI-адаптери]
cmd --> http[HTTP-адаптери]
cli --> app[app/usecases]
http --> app
app --> domain[домен/модель]
app --> storage[адаптери/сховище]
cli -.-> std[stdlib: flag, fmt, os]
http -.-> std2[stdlib: net/http, encoding/json]
Якщо ви додаєте, скажімо, зовнішній роутер, він має залишатися всередині adapters/http, а не проникати в domain і app. Тоді ціна залежності контрольована: ви можете замінити її, не руйнуючи пів проєкту разом із фундаментом.
6. Як обрати бібліотеку: чек-лист мислення
Проблема вибору бібліотек у початківців зазвичай не в тому, що вони «погано обирають», а в тому, що вони обирають за неправильними сигналами. Найчастіший неправильний сигнал — «у туторіалі так зробили». Другий за поширеністю — «у цієї бібліотеки багато зірок». Зірки корисні, але вони не гарантують, що бібліотека підходить саме вам і саме для вашого контексту.
Натомість тримайте більш інженерну логіку. Спочатку ви формулюєте, що саме вам потрібно: роутинг із параметрами? middleware chaining? генерація help у CLI? структуровані JSON-логи? Потім ви перевіряєте, чи розвʼязує це stdlib достатньо добре. Якщо так — ви економите місяці майбутнього життя. Якщо ні — ви обираєте зовнішню бібліотеку, але так, щоб вона не розповзлася по проєкту.
Далі ви оцінюєте «життєздатність» залежності. Чи підтримується проєкт, чи є релізи, чи зрозуміла документація, чи є тести, наскільки важким є граф залежностей, наскільки болючими будуть несумісні зміни. І — дуже важливо — наскільки бібліотека дружить із підходом Go «простий API, явні помилки, мінімум магії». Інколи «надто розумний» фреймворк робить код коротшим, але підвищує ціну підтримки. А Go зазвичай обирають саме за передбачуваність і простоту.
7. Підключаємо зовнішнє акуратно: через адаптери
У цьому розділі ми привʼяжемо все до нашого tasker і покажемо головний практичний прийом: ми не «використовуємо бібліотеку всюди», ми робимо адаптер. І тоді зовнішня бібліотека стає замінною деталлю, а не частиною ДНК застосунку.
Уявімо, що в нас є доменний рівень, який узагалі не знає про HTTP і CLI:
package domain
type Task struct {
ID int
Title string
Done bool
}
А в app-шарі ми залежимо від інтерфейсів, а не від конкретних бібліотек:
package app
import "context"
type Task struct {
ID int
Title string
Done bool
}
type Storage interface {
Create(ctx context.Context, title string) (Task, error)
}
Тепер HTTP-адаптер може бути на чистому net/http, а може бути на зовнішньому роутері — але app про це не дізнається.
Так само можна зробити і з логуванням: усередині застосунку ви можете залежати від дуже маленького інтерфейсу, а зовні підключати хоч log/slog, хоч інший логер.
package app
type Logger interface {
Info(msg string, args ...any)
Error(msg string, args ...any)
}
І адаптер на slog виглядатиме максимально нудно (а нудно — це комплімент):
package adapters
import "log/slog"
type SlogLogger struct {
L *slog.Logger
}
func (l SlogLogger) Info(msg string, args ...any) { l.L.Info(msg, args...) }
func (l SlogLogger) Error(msg string, args ...any) { l.L.Error(msg, args...) }
Чому це круто? Бо якщо через рік ви вирішите «нам потрібен інший логер» — ви перепишете один файл адаптера, а не весь проєкт. І це та сама ідея пакування результату, про яку ми говорили наприкінці курсу: якість — це не лише «працює», а й «змінюється без болю».
8. Типові помилки під час вибору stdlib vs зовнішніх бібліотек
Помилка № 1: брати зовнішню бібліотеку «про всяк випадок».
Це виглядає нешкідливо: «хай буде cobra, раптом знадобиться». Проблема в тому, що залежність починає жити своїм життям: оновлення, несумісності, транзитивні модулі. Якщо ви реально не використовуєте можливості бібліотеки, ви платите за неї просто фактом її існування.
Помилка № 2: тягнути бібліотеку в домен і use cases.
Найдорожчий сценарій — коли domain починає імпортувати щось зовнішнє (роутер, логер, ORM-модель із тегами, HTTP-типи). Тоді бібліотека перестає бути деталлю й стає архітектурним рішенням «назавжди». Значно дешевше тримати все зовнішнє на межі: CLI/HTTP/storage/adapters.
Помилка № 3: плутати «зручніше писати» і «дешевше супроводжувати».
Деякі бібліотеки справді дозволяють написати менше коду сьогодні. Але супровід — це про читання, тестування, оновлення та налагодження через рік. У Go вигідно писати трохи багатослівніше, але передбачувано. Це як листування: можна скоротити слова до емодзі, але потім сам же не зрозумієш, що мав на увазі.
Помилка № 4: обирати за хайпом, а не за вимогами.
«Усі використовують X» — слабкий аргумент, доки ви не розумієте, яку проблему розвʼязує X. Правильний аргумент звучить так: «нам потрібно A, B, C; stdlib закриває A і B, але не закриває C; бібліотека X закриває C і не ламає нам архітектуру».
Помилка № 5: забувати про дисципліну модулів і версій інструментів.
Якщо в команді «в кожного свої версії інструментів», ви отримуєте дива на кшталт «у мене тести проходять, у тебе — ні». У Go прийнято фіксувати залежності й підтримувати порядок через go mod tidy, а інструменти ставити з конкретною версією (через go install ...@version). Це зменшує випадковість і робить проєкт відтворюваним.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ