REST API бэкенд электронного журнала университета — учёт посещаемости, управление оценками, формы оценивания и асинхронная генерация отчётов.
Ещё слайды защиты — результат, UI, нагрузка
Полная презентация защиты ВКР (Google Slides)
10 слайдов · проблематика · цель и задачи · архитектура · результат · тестирование
Все сервисы поднимаются через docker compose up -d.
| Сервис | Порт | URL | Учётные данные | Назначение |
|---|---|---|---|---|
| Nginx | 80 |
http://localhost |
— | Главная точка входа. Rate limiting, security headers, reverse proxy → Edge |
| Edge | 8080 |
http://localhost:8080 |
— | HTTP-шлюз. Валидация JWT, CORS, middleware-цепочка, маршрутизация в Domain |
| Domain | 8081 |
http://localhost:8081 |
— | Бизнес-логика. Посещаемость, оценки, отчёты, аудит, фоновые задачи |
| PostgreSQL | 5432 |
— | journal_user / journal_pass |
Единственный источник истины. Все данные журнала |
| Keycloak | внешний SSO | KEYCLOAK_URL |
управляется вне репозитория | IAM / OIDC. Выдаёт JWT-токены, управляет пользователями и ролями |
| Prometheus | 9090 |
http://localhost:9090 |
— | Сбор метрик с Edge, Domain, cAdvisor каждые 15 секунд |
| Grafana | 3000 |
http://localhost:3000 |
admin / admin |
Дашборды метрик + просмотр логов из Loki |
| Loki | 3100 |
http://localhost:3100 |
— | Агрегация и хранение логов (бэкенд для Grafana) |
| cAdvisor | 8090 |
http://localhost:8090 |
— | CPU / RAM / сеть по каждому контейнеру → Prometheus |
| node_exporter | 9100 |
http://localhost:9100 |
— | Метрики хоста: CPU, RAM, disk, filesystem |
| postgres_exporter | 9187 |
http://localhost:9187 |
— | Метрики PostgreSQL |
| Tempo | 3200, 4317, 4318 |
http://localhost:3200 |
— | Хранилище OpenTelemetry trace-ов для Grafana |
| Promtail | — | — | — | Агент логов: читает stdout Docker-контейнеров, шлёт в Loki |
- Что это такое?
- Быстрый старт (5 минут)
- Развёртывание prod-local (внешний Keycloak)
- Архитектура
- Справочник API
- Ключевые файлы
- Рабочий процесс разработчика
- Observability
- Переменные окружения
- Заметки по ролям
University Journal System — бэкенд-платформа для ведения академических записей университета. Решает задачи:
- Посещаемость — отметка, массовая отметка и запрос посещаемости по занятию
- Оценки — создание и изменение записей об оценках студентов
- Формы оценивания — группировка оценок по занятию или семестру
- Отчёты — асинхронная генерация PDF/Excel с опросом статуса
Система разделена на два независимо разворачиваемых Go-бинарника за Nginx. Аутентификация делегирована Keycloak (OIDC/JWT). Все данные хранятся в PostgreSQL; отдельный кэш-сервис сейчас не используется.
| Инструмент | Версия | Установка |
|---|---|---|
| Go | 1.26.4 | go version |
| Docker + Compose | 24+ | docker compose version |
| sqlc | 1.26+ | go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.26.0 |
| oapi-codegen | 2.x | go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest |
| goose | latest | go install github.com/pressly/goose/v3/cmd/goose@latest |
| golangci-lint | 2.12+ | make install-golangci-lint или go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 |
Утилиты из таблицы ставятся через go install; бинарники попадают в $(go env GOPATH)/bin (часто это ~/go/bin). Пока этот каталог не в PATH, команды sqlc, oapi-codegen, goose не находятся — make generate падает с make: sqlc: No such file or directory или Error 127.
Один раз в текущей сессии:
export PATH="$PATH:$(go env GOPATH)/bin"Чтобы не повторять после каждого входа, добавьте ту же строку в ~/.bashrc или ~/.profile (для login-shell — смотрите, какой файл читает ваш дистрибутив).
Проверка:
command -v sqlc && command -v oapi-codegen && command -v goose
sqlc version
oapi-codegen -versionЕсли бинарника всё ещё нет — сначала выполните go install из таблицы «Требования», затем снова проверьте command -v.
Docker без sudo: пользователь должен входить в группу docker (sudo usermod -aG docker "$USER", затем перелогиниться). Иначе make docker-up завершится отказом в доступу к сокету Docker.
Иногда удобно сначала поднять только БД: например, применить миграции и сиды с хоста до полного docker-up, или работать против Postgres локально без Edge.
Compose поднимает только сервис postgres (тот же volume postgres_data, те же учётные данные journal_user / journal_pass, порт 5432 на хосте). Образ приложения не собирается.
Основной стек (docker-compose.yml, Keycloak внешний):
make docker-db-up
# эквивалент: docker compose up -d --wait postgresСтек prod-local (файл docker-compose.prod-local.yml; нужен .env.prod-local):
make docker-db-up-ext
# эквивалент: docker compose -f docker-compose.prod-local.yml --env-file .env.prod-local up -d --wait postgresФлаг --wait дождётся healthcheck Postgres (pg_isready), после чего с хоста обычно срабатывает make migrate-up с дефолтным DB_DSN из Makefile (localhost:5432/journal_db). Затем можно запускать остальной стек (make docker-up или make docker-up-ext) — Postgres уже будет работать; Compose подключится к существующему контейнеру и volume.
Перед локальной сборкой go build (без Docker) выполните make generate: каталоги internal/db/generated/ и api/generated/ в .gitignore, иначе компиляция на машине разработчика упадёт.
Сборка образов Dockerfile.edge и Dockerfile.domain сама генерирует недостающий код (Edge — oapi-codegen, Domain — sqlc, те же версии, что в Makefile) и выполняет go mod tidy, чтобы не хранить артефакты в git и подтянуть недостающие записи в go.sum. Для работы в IDE и тестов без контейнеров по-прежнему удобнее один раз выполнить make generate локально.
# 1. Клонировать и войти в проект
git clone <repo-url>
cd university-journal-system
# 2. Создать файл переменных окружения
cp .env.example .env.local
# Отредактировать .env.local при необходимости (дефолты работают с docker-compose)
# 3. Сгенерировать код из SQL-миграций (sqlc) и OpenAPI-спеки (oapi-codegen)
# Нужны установленные sqlc и oapi-codegen (см. таблицу «Требования»).
make generate
# 4. Запустить все сервисы и дождаться готовности (Compose --wait).
# Первый запуск Keycloak может занять 1–3 минуты — это нормально.
# Если нужна только БД на первом шаге: make docker-db-up (раздел «Только PostgreSQL» выше),
# затем шаг 5 и позже полный стек — make docker-up.
make docker-up
# или вручную: docker compose up -d --wait --wait-timeout 600
# 5. Применить миграции БД с хоста (DSN по умолчанию — localhost:5432, совпадает с compose)
make migrate-upЕсли make migrate-up сразу после шага 4 всё же вернул «connection refused», подождите несколько секунд и повторите: реже на очень медленном диске порт PostgreSQL на хосте открывается с задержкой. Если вы меняли пароль БД в compose, передайте тот же DSN в goose: DB_DSN='postgres://...' make migrate-up.
# Health-check
curl http://localhost/health # через nginx
curl http://localhost:8080/health # edge напрямую
# Readiness на Edge (пока без опроса Domain — см. internal/edge/handler/health.go)
curl http://localhost:8080/ready
# Проверка Domain (здесь реально пингуется БД и проверяются миграции)
curl http://localhost:8081/ready
# Интерактивная документация API (только вне prod)
# Windows: start http://localhost/api/docs
# macOS: open http://localhost/api/docs
# Linux: xdg-open http://localhost/api/docsОжидаемые ответы:
// GET /health → 200
{"status": "ok"}
// GET :8080/ready → 200 (заглушка на Edge; детальные проверки — на Domain)
{"status": "ready"}
// GET :8081/ready → 200 когда БД доступна и миграции применены
{"status": "ready"}Стек docker-compose.prod-local.yml поднимает Edge, Domain, Postgres, Nginx и observability-контур; Keycloak находится во внешнем серверном контуре, а подключение задаётся переменными KEYCLOAK_* в .env.prod-local. Краткая инструкция также в шапке compose-файла.
Сборка образов не запускает SQL-миграции. Данные Postgres лежат в именованном volume (postgres_data); goose выполняется отдельно с хоста (или из CI) против той же БД, к которой подключается контейнер domain.
| Шаг | Что сделать |
|---|---|
| 1 | Убедиться, что при необходимости выполнен make generate (менялись OpenAPI или SQL/sqlc). |
| 2 | Пересобрать и запустить контейнеры (см. ниже). |
| 3 | Применить миграции к БД на хосте: make migrate-up с корректным DB_DSN (см. ниже). Пропуск шага при новых файлах в internal/db/migrations/ приводит к тому, что Domain долго отвечает 503 на GET /ready с причиной вроде database migrations not up to date. |
| 4 | Проверить curl http://localhost:8081/ready и при необходимости Edge curl http://localhost:8080/ready. |
Скопируйте шаблон и заполните значения для вашего Keycloak:
cp .env.example .env.prod-local
# Отредактировать KEYCLOAK_URL, KEYCLOAK_REALM, KEYCLOAK_CLIENT_ID и при необходимости остальноеБазовая команда (пересобирает edge и domain из текущего репозитория и поднимает сервисы в фоне):
docker compose -f docker-compose.prod-local.yml --env-file .env.prod-local up -d --buildЭквивалент с дополнительными флагами уже зашит в Makefile: ожидание healthcheck-ов, удаление осиротевших контейнеров после правок compose:
make docker-up-extПри подозрении на устаревший слой кэша Docker (образ «не подхватывает» изменения):
docker compose -f docker-compose.prod-local.yml --env-file .env.prod-local build --no-cache
docker compose -f docker-compose.prod-local.yml --env-file .env.prod-local up -dПосле изменения списка сервисов в compose имеет смысл добавлять --remove-orphans, чтобы убрать старые контейнеры, которые больше не описаны в файле (в make docker-up-ext это уже есть).
Строка подключения по умолчанию в Makefile совпадает с учётными данными Postgres из compose и пробросом порта 5432 на localhost:
# Дефолт: postgres://journal_user:journal_pass@localhost:5432/journal_db?sslmode=disable
make migrate-upЯвная форма (удобно при нестандартном порте или удалённом хосте):
DB_DSN='postgres://journal_user:journal_pass@127.0.0.1:5432/journal_db?sslmode=disable' make migrate-upПроверить состояние миграций:
make migrate-statusДля локальных smoke-прогонов после миграций можно загрузить тестовые данные (идемпотентный сидер; на общей «боевой» БД используйте осознанно):
make prod-local-bootstrap # migrate-up + seed-test-datamake docker-down-ext
# или
docker compose -f docker-compose.prod-local.yml --env-file .env.prod-local down --remove-orphansmake docker-logs-extКлиент (браузер / мобайл / Postman)
│
▼
[Nginx :80] ← rate limiting, security headers, reverse proxy
│
▼
[Edge :8080] ← валидация JWT (Keycloak JWKS), роутинг, middleware
│
├──→ [Keycloak :8180] ← выдача OIDC-токенов и JWKS-endpoint
│
▼
[Domain :8081] ← бизнес-логика: посещаемость, оценки, отчёты
│
└──→ [PostgreSQL :5432] ← все персистентные данные
Observability (отдельный пайплайн):
Docker logs ──→ [Promtail] ──→ [Loki :3100] ──→ [Grafana :3000]
/metrics ──→ [Prometheus :9090] ─────────────→ [Grafana :3000]
контейнеры ──→ [cAdvisor :8090] ──→ [Prometheus]
хост/БД ──→ [node_exporter/postgres_exporter] ──→ [Prometheus]
traces ──→ [Tempo :4318] ─────────────────────→ [Grafana]
| Сервис | Бинарник | Зона ответственности |
|---|---|---|
| Edge | cmd/edge |
HTTP-шлюз: auth middleware, CORS, request ID, Prometheus-метрики, проксирование в Domain |
| Domain | cmd/domain |
Бизнес-логика: посещаемость, оценки, формы оценивания, асинхронные отчёты, аудит-лог, фоновые воркеры |
Edge никогда не обращается к БД напрямую. Domain никогда не занимается аутентификацией.
| Слой | Технология | Зачем |
|---|---|---|
| Язык | Go 1.26.4 | Один бинарник, быстрый старт, отличный конкурентный I/O |
| HTTP-фреймворк | Gin | Минимальные накладные расходы, middleware-первичность |
| Аутентификация | Keycloak 24 + go-oidc/v3 |
Стандарт OIDC, поддержка университетского SSO |
| База данных | PostgreSQL 16 | Реляционная, ACID, драйвер pgx/v5 |
| Кодогенерация БД | sqlc 1.26 | Type-safe SQL → Go, без ORM |
| Кодогенерация API | oapi-codegen 2.x | OpenAPI-спека → Go-стабы сервера + модели |
| Миграции | goose | SQL-based, транзакционные, идемпотентные |
| Observability | Prometheus + Grafana + Loki + Tempo | Метрики, логи и базовые OpenTelemetry traces |
| Прокси | Nginx 1.25 | Rate limiting, TLS-терминация, security headers |
Доступны два endpoint-а (по умолчанию при APP_ENV != prod; в prod — при API_DOCS_ENABLED=true):
| URL | Описание |
|---|---|
http://localhost/api/docs |
Swagger UI — интерактивный просмотр и тестирование всех эндпоинтов |
http://localhost/api/openapi.yaml |
OpenAPI 3.0 YAML — для импорта в Postman, кодогенерации клиентов, CI |
В prod оба endpoint-а выключены, пока не задано
API_DOCS_ENABLED=true.
Все эндпоинты /api/v1/* требуют Bearer JWT-токен, выданный Keycloak:
Authorization: Bearer <access_token>Получение токена:
Web и mobile логинятся во внешнем Keycloak через Authorization Code + PKCE (journal-web / journal-mobile) и отправляют в API только access_token. Backend client journal-backend используется как audience API, а не как пользовательский login-client.
| Метод | Путь | Авторизация | Описание |
|---|---|---|---|
GET |
/health |
Нет | Liveness probe — сервис запущен |
GET |
/ready |
Нет | Readiness probe — PostgreSQL доступна, миграции применены |
GET |
/metrics |
Нет | Endpoint сбора Prometheus-метрик |
| Метод | Путь | Авторизация | Описание |
|---|---|---|---|
GET |
/api/v1/lessons/{lesson_id}/attendance |
Bearer | Получить все записи посещаемости занятия |
POST |
/api/v1/lessons/{lesson_id}/attendance/mark |
Bearer (teacher/admin) | Отметить посещаемость одного студента |
POST |
/api/v1/lessons/{lesson_id}/attendance/bulk |
Bearer (teacher/admin) | Массовая отметка посещаемости группы |
Пример — отметить посещаемость:
curl -X POST http://localhost/api/v1/lessons/3fa85f64-5717-4562-b3fc-2c963f66afa6/attendance/mark \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"student_id": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
"status": "present",
"note": ""
}'| Метод | Путь | Авторизация | Описание |
|---|---|---|---|
POST |
/api/v1/assessment-forms |
Bearer (teacher/admin) | Создать форму оценивания для занятия |
GET |
/api/v1/assessment-forms/{form_id}/grades |
Bearer | Получить все оценки формы оценивания |
POST |
/api/v1/grades |
Bearer (teacher/admin) | Создать запись об оценке студента |
PUT |
/api/v1/grades/{grade_id} |
Bearer (teacher/admin) | Обновить существующую оценку |
| Метод | Путь | Авторизация | Описание |
|---|---|---|---|
POST |
/api/v1/reports |
Bearer | Запустить асинхронную генерацию отчёта |
GET |
/api/v1/reports/{report_id}/status |
Bearer | Опросить статус генерации отчёта |
Все ошибки возвращаются в единой обёртке:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "lesson_id must be a valid UUID",
"request_id": "01HX..."
}
}| HTTP-статус | Код | Значение |
|---|---|---|
400 |
VALIDATION_ERROR |
Невалидное тело запроса или параметры пути |
401 |
UNAUTHORIZED |
Отсутствует или недействителен Bearer-токен |
403 |
FORBIDDEN |
Токен валидный, но недостаточно прав |
404 |
NOT_FOUND |
Ресурс не найден |
409 |
CONFLICT |
Дубликат записи |
422 |
UNPROCESSABLE_ENTITY |
Нарушение бизнес-правила |
500 |
INTERNAL_ERROR |
Неожиданная ошибка сервера |
503 |
SERVICE_UNAVAILABLE |
Критичная зависимость недоступна |
university-journal-system/
├── cmd/
│ ├── edge/main.go # Точка входа Edge-сервиса
│ └── domain/main.go # Точка входа Domain-сервиса
├── api/
│ ├── openapi/journal.yaml # OpenAPI 3.0 спека (единый источник истины)
│ ├── openapi/spec.go # go:embed обёртка — спека встроена в бинарник
│ └── generated/journal.gen.go # Вывод oapi-codegen (StrictServerInterface)
├── internal/
│ ├── edge/
│ │ ├── config/config.go # Конфиг Edge (переменные окружения)
│ │ ├── handler/ # HTTP-хендлеры (посещаемость, оценки, health, docs)
│ │ ├── middleware/ # Auth (JWT), RequestID, Recovery
│ │ └── server/server.go # Gin-роутер и его сборка
│ ├── domain/
│ │ ├── config/ # Конфиг Domain
│ │ ├── journal/
│ │ │ ├── domain/entities.go # Доменные модели
│ │ │ ├── service/ # Бизнес-логика (AttendanceService, GradesService)
│ │ │ └── repository/ # Слой доступа к БД (использует sqlc Querier)
│ │ ├── shared/ # Аудит-лог, фоновые воркеры
│ │ └── server/server.go # Gin-роутер Domain
│ ├── db/
│ │ ├── migrations/ # goose SQL-миграции
│ │ ├── queries/ # Сырые SQL-файлы (входные данные для sqlc)
│ │ └── generated/ # Вывод sqlc: Querier, models, *.sql.go
│ └── pkg/
│ ├── middleware/logging.go # Общее структурное логирование + Prometheus-метрики
│ └── metrics/ # Определения Prometheus-метрик
├── config/
│ ├── nginx/nginx.conf # Rate limiting, правила проксирования, security headers
│ ├── prometheus/prometheus.yml
│ ├── grafana/ # Datasources + автопровизия дашбордов
│ ├── loki/
│ └── promtail/
├── docker-compose.yml # Полный стек из 11 сервисов
├── Dockerfile.edge # Многоэтапная сборка Edge
├── Dockerfile.domain # Многоэтапная сборка Domain
├── Makefile # Задачи разработчика
├── oapi-codegen.yaml # Конфиг oapi-codegen
├── sqlc.yaml # Конфиг sqlc
└── .env.example # Шаблон обязательных переменных окружения
# Показать все доступные make-таргеты
make help
# Собрать оба бинарника
make build
# Перегенерировать код после изменений journal.yaml или SQL-запросов
make generate # запускает generate-sqlc + generate-api
# Миграции базы данных
make migrate-up # применить все ожидающие
make migrate-down # откатить последнюю миграцию
make migrate-status # статус применённых/ожидающих
make migrate-create NAME=add_student_table # создать новую миграцию
# Тесты
make test # все тесты с флагом -race
make test-unit # только юнит-тесты (без Docker)
make test-integration # требует Docker
# Линтинг
make lint
# Горячая пересборка одного сервиса (не трогает остальные контейнеры)
docker compose up -d --build edge
docker compose restart nginx # если edge пересобирался (DNS-кэш)- Добавить в
api/openapi/journal.yaml— описать путь, параметры, схемы запроса/ответа - Запустить
make generate-api— регенерируетapi/generated/journal.gen.goс новым методом - Реализовать хендлер в
internal/edge/handler/ - Зарегистрировать роут в
internal/edge/server/server.go - Добавить SQL-запрос в
internal/db/queries/при необходимости, затемmake generate-sqlc
journal.yaml ──[oapi-codegen]──→ api/generated/journal.gen.go
StrictServerInterface
RegisterHandlers()
Все модели запросов/ответов
internal/db/queries/*.sql ──[sqlc]──→ internal/db/generated/
querier.go (интерфейс Querier)
models.go (DB-структуры)
*.sql.go (типизированные функции)
make docs-validate # требует vacuum (https://quobix.com/vacuum)
# Или с помощью spectral:
npx @stoplight/spectral-cli lint api/openapi/journal.yamlPrometheus опрашивает три цели каждые 15 секунд:
| Цель | Endpoint | Что измеряет |
|---|---|---|
| Edge | :8080/metrics |
Частота HTTP-запросов, латентность, коды статусов по роутам |
| Domain | :8081/metrics |
То же + пул соединений с БД, статистика фоновых воркеров |
| cAdvisor | :8090/metrics |
CPU, RAM, сетевой I/O по каждому контейнеру |
Просмотр дашбордов: http://localhost:3000 (Grafana, admin/admin)
На production Grafana слушает только 127.0.0.1:3000 — откройте через SSH-туннель, например ssh -L 3000:127.0.0.1:3000 user@server.
Плашки edge/domain UP смотрят up{job=...} в Prometheus. Графики CPU / Memory / Network и Live Logs зависят от переменных дашборда и лейблов cAdvisor/Loki.
| Причина | Что сделать |
|---|---|
| Service не «All» / пустой multi-select | Вверху дашборда выберите Service → All (иначе service=~"" → 0 точек) |
Compose project ≠ university-journal-system |
На хосте могут быть другие стеки (project-monopoly, …); выберите university-journal-system |
Нет compose_project / compose_service в cAdvisor (часто Docker Desktop) |
Графики CPU/RAM берут uj:service_* (process-метрики Edge/Domain). На Linux пересоздайте cadvisor prometheus grafana; cAdvisor нормализует labels из Docker или имени контейнера |
| Root filesystem «No data» на Host-дашборде | Используется uj:node_primary_disk_avail_ratio (Linux /, Desktop /mnt/docker-desktop-disk); перезапустите Prometheus после git pull |
| Старый дашборд в БД Grafana | git pull, docker compose restart grafana (provisioning с allowUiUpdates: false) |
| Другой compose project при деплое | Скопируйте .env.example → .env.production и не меняйте COMPOSE_PROJECT_NAME без правки Grafana/Promtail |
Проверка на сервере:
bash scripts/check-observability.shPrometheus (должно быть > 0):
count(uj:service_cpu:rate5m{compose_project="university-journal-system"})
Предпровизированные дашборды:
- HTTP Overview — частота запросов, частота ошибок, P99-латентность
- DB Connections — загрузка пула, продолжительность запросов
- Background Jobs — глубина очереди воркеров, успехи/ошибки задач
- Container Resources — CPU/RAM на сервис (через cAdvisor)
Все сервисы пишут структурированный JSON в stdout. Promtail автоматически отправляет их в Loki.
Запросы логов в Grafana → Explore:
# Все ошибки Edge
{service="edge"} | json | level="error"
# Конкретный request_id сквозь все сервисы
{} | json | request_id="01HX..."
# Медленные запросы Domain (> 100ms)
{service="domain"} | json | duration > 100
Просмотр сырых логов через Docker:
docker compose logs -f edge
docker compose logs -f domain --tail=100# Liveness — процесс запущен?
GET /health
# Readiness — критичные зависимости доступны?
GET /readyСкопируйте .env.example в .env.local и заполните значения. Docker Compose читает .env.local.
| Переменная | Дефолт | Описание |
|---|---|---|
APP_ENV |
local |
Окружение: local, dev, prod |
API_DOCS_ENABLED |
(пусто) | true — включить /api/docs и /api/openapi.yaml; в prod нужно явно true, в local/dev docs включены по умолчанию |
LOG_LEVEL |
debug |
Уровень логирования: debug, info, warn, error |
EDGE_APP_PORT |
8080 |
HTTP-порт Edge |
DOMAIN_APP_PORT |
8081 |
HTTP-порт Domain |
DOMAIN_SERVICE_URL |
http://domain:8081 |
URL апстрима Edge → Domain |
DB_DSN |
см. пример | Строка подключения к PostgreSQL |
DB_MAX_CONNS |
20 |
Максимальный размер пула соединений с БД |
DB_MIN_CONNS |
5 |
Минимальный размер пула соединений с БД |
KEYCLOAK_URL |
https://sso.example.edu |
Базовый URL внешнего Keycloak |
KEYCLOAK_REALM |
Test |
Имя realm во внешнем Keycloak |
KEYCLOAK_CLIENT_ID |
journal-backend |
Client ID Keycloak для валидации JWT |
KEYCLOAK_ALLOWED_AZP |
пусто или journal-web,journal-mobile |
Опциональный allow-list azp для web/mobile access token |
CORS_ALLOWED_ORIGINS |
http://localhost:4173,... |
Разрешённые CORS-источники через запятую |
Безопасность: никогда не коммитьте
.env.localи файлы с реальными секретами — они в.gitignore.
- Начать отсюда: прочитайте
api/openapi/journal.yaml— это единственный источник истины для всех API-контрактов - Запустить
make docker-up && make migrate-up, затем открытьhttp://localhost:3000(Grafana) иhttp://localhost:8080/api/docs(Swagger) - Keycloak Realm не хранится в репозитории: используйте внешний тестовый/production Realm и значения
KEYCLOAK_*из окружения - Придерживайтесь паттерна из
internal/edge/handler/attendance.goпри создании новых хендлеров - Весь SQL — в
internal/db/queries/, никаких сырых запросов в Go-коде
- Swagger UI:
http://localhost/api/docs— просмотр всех эндпоинтов и тестирование прямо в браузере - Машиночитаемая спека:
http://localhost/api/openapi.yaml— импорт в Postman или генерация клиентского кода - Все эндпоинты возвращают
application/json - Аутентификация: получите Bearer-токен из Keycloak и передавайте его как
Authorization: Bearer <token> - В ошибках всегда есть
request_id— указывайте его при сообщениях об ошибках
- CI: при каждом push в
masterworkflow.github/workflows/main.ymlпо SSH заходит на сервер и выполняетscripts/deploy-server.sh:git pull,docker compose build --no-cache,up -d --wait,make migrate-up(нуженgooseвPATHили Go), загрузкаSEED_SQLчерезpsqlв контейнере Postgres. Секреты GitHub описаны в комментарии в начале workflow-файла (обязательные:SSH_*,WORK_DIR_DEV,BRANCH_DEV; опционально:DOCKER_COMPOSE_ARGS,DB_DSN_DEV,SEED_SQL_PATH). - Локально полный цикл как на сервере:
DEPLOY_BRANCH=master make deploy-server(нужен Docker и совпадающий compose). - Пошаговый сценарий для
docker-compose.prod-local.yml(миграции, пересборка, кэш, orphans): раздел Развёртывание prod-local - Два Docker-образа:
Dockerfile.edgeиDockerfile.domain(многоэтапные; в builder —go mod tidy, затемoapi-codegenилиsqlcс GitHub Releases иgo build) - Если сборка
domainпадает сpanic: unable to mmap memory/cannot allocate memoryна шагеsqlc generate: контейнеру не хватает RAM (часто при жёстком лимите Docker, без swap или приdocker build --platform=...через QEMU). Увеличьте память или swap для Docker; по возможности собирайте образ на той же архитектуре, что и рантайм. ВDockerfile.domainиспользуется готовый бинарник sqlc с GitHub (неgo run) иGOMAXPROCS=1при генерации, чтобы снизить пиковое потребление. - Health-пробы:
/health(liveness) и/ready(readiness) на обоих сервисах - Swagger в prod: оставьте
APP_ENV=prodи не задавайтеAPI_DOCS_ENABLED(илиAPI_DOCS_ENABLED=false) - Конфиг Prometheus:
config/prometheus/prometheus.yml - Datasources и дашборды Grafana автопровизируются из
config/grafana/(allowUiUpdates: false; после деплоя —restart grafana) - Диагностика на хосте:
bash scripts/check-observability.sh - Пересборка одного сервиса без даунтайма:
docker compose up -d --build edge && docker compose restart nginx
- Каждый роут должен иметь соответствующую запись в OpenAPI-спеке
- Весь доступ к БД — только через
Querier-интерфейс, сгенерированныйsqlc - Хендлеры не должны содержать бизнес-логику — она принадлежит
internal/domain/journal/service/ - Ошибки — только через
handler.NewErrorResponse()для единообразной обёртки - Новые миграции — только forward-only (никаких деструктивных изменений в
up)



