Клиент

Карту курса ты уже видел в 00-01 — теперь первый технический шаг. Где-то крутится Postgres с меню, заказами и клиентами Brew, и тебе с ним работать: писать приложение, которое читает и пишет данные, а не администрировать сервер. Прежде чем браться за типы, индексы и транзакции, нужно понять две скучные, но обязательные вещи: что вообще находится на том конце соединения, и как завести локальную копию, которую не страшно сломать.

Это весь сюжет юнита. Никакого SQL-героизма — подключиться, спросить у сервера его версию и прочитать меню. Дальше на этот же конвейер ляжет весь курс.

Клиент и сервер: что на том конце сокета

Postgres — это серверный процесс. Он владеет файлами с данными, и никто, кроме него, в эти файлы не лезет. Всё, что у тебя в руках — psql, драйвер pgx в Go, веб-морда Adminer — это клиенты. Клиент открывает соединение, по сети (TCP на порт 5432 или локальный unix-сокет) отправляет текст запроса и читает обратно строки. Протокол между ними фиксированный — Postgres wire protocol, — но тебе его знать не нужно: за тебя его говорит драйвер.

Одно соединение, два конца:

plaintext
   N клиентов                                               один сервер-процесс
   (шлют SQL, читают строки)                                (делает всю работу)
 
   psql ──┐
   pgx ───┼──▶  соединение по сокету (TCP :5432 / unix) ──▶  postgres
   Adminer┘     wire protocol                                 • парсит SQL, строит план
                                                              • исполняет, держит MVCC и блокировки
                                                              • владеет файлами данных — только он

Почему это важно на практике, а не только в теории администрирования. Каждое слово, которое всплывёт дальше — «соединение», «пул», «таймаут», «коннект отвалился» — про этот самый сокет. Работу делает сервер: парсит SQL, строит план, исполняет его, держит MVCC и блокировки. Клиент только посылает запрос и забирает результат. Один сервер обслуживает много клиентов одновременно — и вся история про конкурентность (модуль 05) растёт именно отсюда: несколько соединений, один набор данных.

Практический вывод на сегодня простой: чтобы что-то сделать с данными, нужно соединение. Откроем его и проверим, что на том конце действительно живой Postgres 18.

Песочница: один Postgres на весь курс

Локальный стенд — один контейнер Postgres 18 плюс Adminer как веб-клиент. Поднимается из корня репозитория:

sh
docker compose up -d

Одна база brew обслуживает весь курс. Каждый юнит докатывает свою схему поверх общей схемы Brew (schema/brew.sql) командой make db-reset — отдельный контейнер на юнит не нужен. Базовые таблицы — это таблицы кофейни: drinks (меню), customers, orders, shops и другие. Их мы наполняем детерминированными seed-данными: фиксированные id, фиксированные created_at. Поэтому вывод демо воспроизводится дословно — и его не стыдно вставить прямо в README.

make db-reset идемпотентен: внутри он накатывает схему через IF NOT EXISTS, а seed — через TRUNCATE ... RESTART IDENTITY перед вставкой. Гоняй сколько угодно раз — состояние БД всегда одно и то же. Это не косметика: воспроизводимость — то, что отличает «у меня на машине работает» от «работает у всех».

Adminer висит на http://localhost:8090 (System: PostgreSQL, Server: postgres, логин/пароль brew/brew) — удобно глазами посмотреть на таблицы, но это опционально. Рабочий клиент курса — Go.

Параметры подключения по умолчанию заданы под этот стенд:

plaintext
DATABASE_URL=postgres://brew:brew@localhost:5432/brew?sslmode=disable

internal/pg.NewPool читает DATABASE_URL (или собирает строку из PG*-переменных), так что в коде юнита строки подключения нет — есть один вызов.

Что показывает наш код

В центре урока — query.sql. Это не вспомогательный файл, а сам урок: SQL мы пишем руками, и он остаётся читаемым SQL, а не растворяется в билдере запросов.

sql
-- name: ServerVersion :one
SELECT version();
 
-- name: ListDrinks :many
SELECT id, sku, name, category, base_price
FROM drinks
ORDER BY id;

Команда make gen запускает sqlc generate: sqlc читает query.sql вместе со схемой (схема Brew + добавки юнита) и генерирует типизированный Go-код в internal/db/. Из -- name: ListDrinks :many получается метод ListDrinks(ctx) ([]ListDrinksRow, error), где ListDrinksRow — структура с полями ровно тех типов, что в таблице (base_price BIGINTint64). Сгенерированный код мы коммитим: он часть репозитория, ревьюится в diff'е и не требует кодогенерации на чужой машине.

main.go после этого — тонкий. Вся его суть в четырёх строчках:

go
pool, err := pg.NewPool(ctx)      // пул соединений к песочнице
queries := db.New(pool)           // типизированная обёртка из sqlc
version, err := queries.ServerVersion(ctx)
drinks, err := queries.ListDrinks(ctx)

Никакого ручного rows.Scan, никаких строковых литералов SQL в коде Go — всё это сгенерировано из query.sql. Этот конвейер — «SQL руками → sqlc generate → типизированный pgx-код» — позвоночник каждого юнита курса. Дальше меняются только запросы.

Запуск

Подними песочницу (из корня репозитория) и накати схему Brew:

sh
docker compose up -d
make lecture L=00-getting-connected/00-02-client-server-and-sandbox T=db-reset

Запусти демо:

sh
make lecture L=00-getting-connected/00-02-client-server-and-sandbox

(T=run — значение по умолчанию, поэтому без T=... сразу запускается демо. Изнутри каталога юнита это просто make db-reset и make run.)

Вывод:

plaintext
Сервер: PostgreSQL 18.4 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
Напитков в меню Brew: 5
 
ID  SKU     НАЗВАНИЕ     КАТЕГОРИЯ  ЦЕНА
1   ESP-01  Эспрессо     coffee     3.00
2   CAP-01  Капучино     coffee     4.50
3   LAT-01  Латте        coffee     4.80
4   CLD-01  Колд брю     cold       5.20
5   TEA-01  Зелёный чай  tea        2.50

Строка версии у тебя может отличаться хвостом — aarch64 против x86_64, версия gcc — это зависит от архитектуры машины, на которой собран образ postgres:18-alpine. Главное — PostgreSQL 18.x в начале: на том конце сокета именно та версия, на которую рассчитан курс. Таблица меню воспроизводится дословно: id, цены и порядок заданы seed-данными.

Заборчик

  • Песочница — это docker compose на твоём ноуте. В проде сервер держит и обслуживает DBA: версия Postgres и апгрейды, max_connections, память, диск, бэкапы — их зона, не твоя. Ты на том конце сокета — клиент, а не оператор сервера.
  • sslmode=disable годится только для локального стенда. В проде соединение шифруют (TLS), а пароль приходит из секрет-менеджера — он не лежит в DATABASE_URL под версионным контролем.
  • Одна общая база brew на весь курс — учебное упрощение. На реальном проекте окружения разведены по разным базам и инстансам, а make db-reset (внутри — TRUNCATE) по живым данным не запускают никогда.

Что забрать с собой

  • Postgres — сервер; psql, pgx, Adminer — клиенты, которые говорят с ним по соединению. Данных клиент не трогает, он шлёт SQL и читает строки.
  • Песочница — один контейнер на весь курс; make db-reset идемпотентно возвращает БД в эталонное состояние, поэтому вывод демо воспроизводим.
  • Конвейер курса: query.sql (руками) → make gen (sqlc) → типизированный код в internal/db/ (коммитим) → тонкий main.go.

Дальше — юнит 00-03 «psql survival kit»: тот же сервер, но смотрим на него через psql напрямую — \dt, \d, \x, \timing. Это рабочий инструмент, который пригодится в каждом следующем юните, когда захочется заглянуть в БД руками, без Go.

·Модуль 01

Этот урок ещё впереди

Курс изучается по порядку — чтобы открыть этот шаг, сначала завершите предыдущие. Так контекст накапливается без пропусков.

/ вы пытались открыть
Подключение и ориентация / Клиент