Микросервис для автоматического назначения ревьюеров на Pull Request'ы. Поддерживает управление командами разработчиков и гибкое переназначение ревьюверов.
Сервис предоставляет следующие возможности:
- Автоматическое назначение до 2 ревьюеров из команды автора PR
- Переназначение ревьюверов с учетом команды заменяемого участника
- Управление командами и списком участников
- Управление активностью пользователей
- Идемпотентная операция слияния PR
- Статистика по пользователям (количество PR и ревью)
- Graceful shutdown с корректным завершением соединений
- Структурированное логирование с настраиваемыми уровнями
- Connection pooling для оптимальной работы с PostgreSQL
- Health check endpoint для проверки работоспособности сервиса
- Конфигурация через переменные окружения
- Транзакционность всех критичных операций
Для запуска сервиса необходимо:
- Docker версии 20.10 или выше
- Docker Compose V2
Выполните команду для запуска всех компонентов:
docker-compose upПосле запуска сервис будет запущен на http://localhost:8080
Протестировать статус http://localhost:8080/health
Процесс запуска автоматически выполнит следующие шаги:
- Сборка Docker образа приложения
- Запуск PostgreSQL 17
- Применение миграций базы данных
- Запуск приложения на порту 8080
Примечание: При первом запуске Docker автоматически соберёт образ приложения. Если вы внесли изменения в код, используйте docker-compose up --build для пересборки.
Для тестирования используйте файл examples.http с расширением REST Client для VSCode, или команды ниже.
Создание новой команды с участниками. Если команда с таким именем уже существует, возвращается ошибка TEAM_EXISTS.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/team/add \
-H "Content-Type: application/json" \
-d '{
"team_name": "backend",
"members": [
{
"user_id": "u1",
"username": "Alexey",
"is_active": true
},
{
"user_id": "u2",
"username": "Dmitry",
"is_active": true
}
]
}'Windows PowerShell:
$body = @'
{
"team_name": "backend",
"members": [
{
"user_id": "u1",
"username": "Alexey",
"is_active": true
},
{
"user_id": "u2",
"username": "Dmitry",
"is_active": true
}
]
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/team/add" -Method POST -ContentType "application/json" -Body $bodyОбновление существующей команды. Добавляет новых участников или обновляет информацию о существующих. Если команда не найдена, возвращается ошибка NOT_FOUND.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/team/update \
-H "Content-Type: application/json" \
-d '{
"team_name": "backend",
"members": [
{
"user_id": "u1",
"username": "Alexey",
"is_active": true
},
{
"user_id": "u2",
"username": "Dmitry",
"is_active": true
},
{
"user_id": "u3",
"username": "Igor",
"is_active": true
}
]
}'Windows PowerShell:
$body = @'
{
"team_name": "backend",
"members": [
{
"user_id": "u1",
"username": "Alexey",
"is_active": true
},
{
"user_id": "u2",
"username": "Dmitry",
"is_active": true
},
{
"user_id": "u3",
"username": "Igor",
"is_active": true
}
]
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/team/update" -Method POST -ContentType "application/json" -Body $bodyПолучение информации о команде с полным списком участников.
Bash/Linux/Mac:
curl http://localhost:8080/team/get?team_name=backendWindows PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/team/get?team_name=backend" -Method GETИзменение статуса активности пользователя. Неактивные пользователи не назначаются на ревью.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/users/setIsActive \
-H "Content-Type: application/json" \
-d '{
"user_id": "u2",
"is_active": false
}'Windows PowerShell:
$body = @'
{
"user_id": "u2",
"is_active": false
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/users/setIsActive" -Method POST -ContentType "application/json" -Body $bodyПолучение списка PR, где пользователь назначен ревьювером.
Bash/Linux/Mac:
curl http://localhost:8080/users/getReview?user_id=u1Windows PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/users/getReview?user_id=u1" -Method GETСоздание нового PR. Автоматически назначает до 2 активных ревьюверов из команды автора, исключая самого автора.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/create \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001",
"pull_request_name": "Add search",
"author_id": "u1"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001",
"pull_request_name": "Add search",
"author_id": "u1"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/create" -Method POST -ContentType "application/json" -Body $bodyОтметка PR как слитого. Операция идемпотентна - повторный вызов для уже слитого PR не вызывает ошибку.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/merge \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/merge" -Method POST -ContentType "application/json" -Body $bodyПереназначение конкретного ревьювера на другого участника из команды заменяемого.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/reassign \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001",
"old_user_id": "u2"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001",
"old_user_id": "u2"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/reassign" -Method POST -ContentType "application/json" -Body $bodyПолучение статистики по всем пользователям. Показывает количество созданных PR, назначенных ревью и активных ревью.
Bash/Linux/Mac:
curl http://localhost:8080/statsWindows PowerShell:
# простой вывод
Invoke-RestMethod -Uri "http://localhost:8080/stats" -Method GET
# читабельный форматированный вывод(этот мне больше нравится)
Invoke-RestMethod -Uri "http://localhost:8080/stats" -Method GET | ConvertTo-Json -Depth 10Ожидаемый результат:
{
"users": [
{
"user_id": "u1",
"username": "Alexey",
"team_name": "backend",
"is_active": true,
"total_prs_authored": 1,
"total_reviews_assigned": 0,
"active_reviews": 0
},
{
"user_id": "u3",
"username": "Igor",
"team_name": "backend",
"is_active": true,
"total_prs_authored": 0,
"total_reviews_assigned": 1,
"active_reviews": 0
},
{
"user_id": "u6",
"username": "Pavel",
"team_name": "backend",
"is_active": true,
"total_prs_authored": 0,
"total_reviews_assigned": 1,
"active_reviews": 0
},
{
"user_id": "u2",
"username": "Dmitry",
"team_name": "backend",
"is_active": false,
"total_prs_authored": 0,
"total_reviews_assigned": 0,
"active_reviews": 0
}
]
}HTTP статус: 200 OK
Пояснение: статистика показывает всех пользователей с их активностью:
- total_prs_authored - общее количество PR, созданных пользователем
- total_reviews_assigned - общее количество раз, когда пользователь был назначен ревьювером (учитываются только текущие назначения, переназначенные ревьюверы не учитываются)
- active_reviews - количество открытых PR, где пользователь назначен ревьювером (только PR со статусом OPEN)
Пользователи отсортированы по количеству назначенных ревью (убывание), затем по количеству созданных PR. Это помогает быстро оценить загрузку участников команды.
В данном примере все active_reviews равны 0, потому что PR pr-1001 находится в статусе MERGED. Если бы запросили статистику до слияния (после шага 7), то у u3 и u6 было бы active_reviews=1.
Рассмотрим полный сценарий использования сервиса с пояснением каждого шага(надеюсь не зря расписываю это всё).
Bash/Linux/Mac:
curl -X POST http://localhost:8080/team/add \
-H "Content-Type: application/json" \
-d '{
"team_name": "backend",
"members": [
{"user_id": "u1", "username": "Alexey", "is_active": true},
{"user_id": "u2", "username": "Dmitry", "is_active": true},
{"user_id": "u3", "username": "Igor", "is_active": true}
]
}'Windows PowerShell:
$body = @'
{
"team_name": "backend",
"members": [
{"user_id": "u1", "username": "Alexey", "is_active": true},
{"user_id": "u2", "username": "Dmitry", "is_active": true},
{"user_id": "u3", "username": "Igor", "is_active": true}
]
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/team/add" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"team": {
"team_name": "backend",
"members": [
{"user_id": "u1", "username": "Alexey", "is_active": true},
{"user_id": "u2", "username": "Dmitry", "is_active": true},
{"user_id": "u3", "username": "Igor", "is_active": true}
]
}
}HTTP статус: 201 Created
Пояснение: команда backend успешно создана с тремя участниками. Все участники добавлены в таблицу users с привязкой к команде backend. Транзакция гарантирует что либо создастся команда со всеми участниками, либо операция полностью откатится.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/team/add \
-H "Content-Type: application/json" \
-d '{
"team_name": "backend",
"members": [
{"user_id": "u4", "username": "Pavel", "is_active": true}
]
}'Windows PowerShell:
$body = @'
{
"team_name": "backend",
"members": [
{"user_id": "u4", "username": "Pavel", "is_active": true}
]
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/team/add" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"error": {
"code": "TEAM_EXISTS",
"message": "TEAM_EXISTS: team_name already exists"
}
}HTTP статус: 400 Bad Request
Пояснение: команда backend уже существует в базе данных. Согласно спецификации OpenAPI, эндпоинт /team/add должен возвращать ошибку TEAM_EXISTS при попытке создать существующую команду. Это защищает от случайной перезаписи данных команды.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/team/update \
-H "Content-Type: application/json" \
-d '{
"team_name": "backend",
"members": [
{"user_id": "u1", "username": "Alexey", "is_active": true},
{"user_id": "u2", "username": "Dmitry", "is_active": true},
{"user_id": "u3", "username": "Igor", "is_active": true},
{"user_id": "u6", "username": "Pavel", "is_active": true}
]
}'Windows PowerShell:
$body = @'
{
"team_name": "backend",
"members": [
{"user_id": "u1", "username": "Alexey", "is_active": true},
{"user_id": "u2", "username": "Dmitry", "is_active": true},
{"user_id": "u3", "username": "Igor", "is_active": true},
{"user_id": "u6", "username": "Pavel", "is_active": true}
]
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/team/update" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"team": {
"team_name": "backend",
"members": [
{"user_id": "u1", "username": "Alexey", "is_active": true},
{"user_id": "u2", "username": "Dmitry", "is_active": true},
{"user_id": "u3", "username": "Igor", "is_active": true},
{"user_id": "u6", "username": "Pavel", "is_active": true}
]
}
}HTTP статус: 200 OK
Пояснение: эндпоинт /team/update позволяет обновлять состав существующих команд. Новый участник Pavel добавлен в команду. Существующие участники обновляются при необходимости. Этот эндпоинт был добавлен дополнительно для практического удобства работы с командами, при этом строгое соблюдение спецификации OpenAPI для /team/add сохранено.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/create \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001",
"pull_request_name": "Add search feature",
"author_id": "u1"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001",
"pull_request_name": "Add search feature",
"author_id": "u1"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/create" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"pr": {
"pull_request_id": "pr-1001",
"pull_request_name": "Add search feature",
"author_id": "u1",
"status": "OPEN",
"assigned_reviewers": ["u2", "u3"],
"createdAt": "2025-11-14T10:30:00Z",
"mergedAt": null
}
}HTTP статус: 201 Created
Пояснение: PR успешно создан со статусом OPEN. Автоматически назначены два ревьювера из команды автора. Алгоритм выбора:
- Определяется команда автора (u1 из команды backend)
- Выбираются активные участники команды, исключая автора: u2, u3, u6
- Случайным образом выбираются до 2 ревьюверов
- В данном случае назначены u2 и u3
Автор u1 исключается из списка кандидатов, так как нельзя назначать себя ревьювером собственного PR.
Bash/Linux/Mac:
curl http://localhost:8080/users/getReview?user_id=u2Windows PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/users/getReview?user_id=u2" -Method GETОжидаемый результат:
{
"user_id": "u2",
"pull_requests": [
{
"pull_request_id": "pr-1001",
"pull_request_name": "Add search feature",
"author_id": "u1",
"status": "OPEN"
}
]
}HTTP статус: 200 OK
Пояснение: пользователь u2 видит все PR, где он назначен ревьювером. В данном случае это только pr-1001. Этот эндпоинт полезен для отслеживания рабочей нагрузки каждого участника команды.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/users/setIsActive \
-H "Content-Type: application/json" \
-d '{
"user_id": "u2",
"is_active": false
}'Windows PowerShell:
$body = @'
{
"user_id": "u2",
"is_active": false
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/users/setIsActive" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"user": {
"user_id": "u2",
"username": "Dmitry",
"team_name": "backend",
"is_active": false
}
}HTTP статус: 200 OK
Пояснение: пользователь u2 помечен как неактивный. Это не удаляет его из существующих PR, где он уже назначен ревьювером, но исключает его из автоматического назначения на новые PR. Такой механизм полезен когда участник в отпуске или временно недоступен.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/reassign \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001",
"old_user_id": "u2"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001",
"old_user_id": "u2"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/reassign" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"pr": {
"pull_request_id": "pr-1001",
"pull_request_name": "Add search feature",
"author_id": "u1",
"status": "OPEN",
"assigned_reviewers": ["u3", "u6"],
"createdAt": "2025-11-14T10:30:00Z",
"mergedAt": null
},
"replaced_by": "u6"
}HTTP статус: 200 OK
Пояснение: ревьювер u2 заменен на u6. Алгоритм переназначения:
- Проверяется что u2 действительно назначен ревьювером на pr-1001
- Определяется команда заменяемого ревьювера (u2 из команды backend)
- Формируется список кандидатов из команды backend, исключая:
- Заменяемого ревьювера (u2)
- Автора PR (u1)
- Текущих ревьюверов (u3)
- Из доступных кандидатов (u6) случайно выбирается один
- u2 удаляется из списка ревьюверов, вместо него добавляется u6
Важно: новый ревьювер выбирается из команды заменяемого участника, а не из команды автора PR. Это позволяет сохранить распределение нагрузки внутри команды.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/merge \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/merge" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"pr": {
"pull_request_id": "pr-1001",
"pull_request_name": "Add search feature",
"author_id": "u1",
"status": "MERGED",
"assigned_reviewers": ["u3", "u6"],
"createdAt": "2025-11-14T10:30:00Z",
"mergedAt": "2025-11-14T10:45:00Z"
}
}HTTP статус: 200 OK
Пояснение: PR переведен в статус MERGED с фиксацией времени слияния. После этого список ревьюверов становится неизменяемым. Операция идемпотентна - повторный вызов вернет тот же результат без ошибки.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/reassign \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001",
"old_user_id": "u3"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001",
"old_user_id": "u3"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/reassign" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"error": {
"code": "PR_MERGED",
"message": "PR_MERGED: cannot reassign on merged PR"
}
}HTTP статус: 409 Conflict
Пояснение: после слияния PR изменение списка ревьюверов запрещено. Это бизнес-требование обеспечивает историческую точность данных о том, кто именно ревьювил конкретный PR. Попытка изменить список после merge всегда возвращает ошибку PR_MERGED.
Bash/Linux/Mac:
curl -X POST http://localhost:8080/pullRequest/merge \
-H "Content-Type: application/json" \
-d '{
"pull_request_id": "pr-1001"
}'Windows PowerShell:
$body = @'
{
"pull_request_id": "pr-1001"
}
'@
Invoke-RestMethod -Uri "http://localhost:8080/pullRequest/merge" -Method POST -ContentType "application/json" -Body $bodyОжидаемый результат:
{
"pr": {
"pull_request_id": "pr-1001",
"pull_request_name": "Add search feature",
"author_id": "u1",
"status": "MERGED",
"assigned_reviewers": ["u3", "u6"],
"createdAt": "2025-11-14T10:30:00Z",
"mergedAt": "2025-11-14T10:45:00Z"
}
}HTTP статус: 200 OK
Пояснение: операция merge идемпотентна. Повторный вызов для уже слитого PR не вызывает ошибку, а возвращает актуальное состояние PR с кодом 200. Время слияния mergedAt остается неизменным (первоначальное время). Это упрощает интеграцию с внешними системами, которым не нужно отслеживать текущий статус PR перед вызовом merge.
Попытка создать PR для несуществующего пользователя:
{
"error": {
"code": "NOT_FOUND",
"message": "NOT_FOUND: author not found"
}
}HTTP статус: 404 Not Found
Попытка переназначить ревьювера, который не назначен на PR:
{
"error": {
"code": "NOT_ASSIGNED",
"message": "NOT_ASSIGNED: reviewer is not assigned to this PR"
}
}HTTP статус: 409 Conflict
Попытка переназначить ревьювера, когда нет доступных кандидатов:
{
"error": {
"code": "NO_CANDIDATE",
"message": "NO_CANDIDATE: no active replacement candidate in team"
}
}HTTP статус: 409 Conflict
Пояснение: ошибка NO_CANDIDATE возникает когда в команде заменяемого ревьювера не осталось активных участников, которые могли бы стать новым ревьювером (за исключением автора PR, текущих ревьюверов и самого заменяемого).
Проект организован по принципам чистой архитектуры с четким разделением на слои:
проект/
├── cmd/
│ └── server/ - точка входа приложения
├── internal/
│ ├── config/ - загрузка конфигурации
│ ├── logger/ - система логирования
│ ├── models/ - модели данных
│ ├── database/ - работа с БД
│ ├── repository/ - слой доступа к данным
│ ├── service/ - бизнес-логика
│ └── handlers/ - HTTP обработчики
├── migrations/ - SQL миграции
└── docker-compose.yml - оркестрация контейнеров
Взаимодействие между слоями: Handlers -> Service -> Repository -> Database
Каждый слой зависит только от интерфейсов нижележащих слоев. Dependency Injection используется для всех компонентов. Контекст передается через все слои для возможности отмены операций.
- Go 1.23 - язык программирования
- PostgreSQL 17 - система управления базами данных
- pgx/v5 - драйвер для работы с PostgreSQL
- gorilla/mux - HTTP роутер
- golang-migrate - инструмент для миграций
- Docker & Docker Compose - контейнеризация
Создание PR: При создании нового PR система автоматически выбирает до 2 активных участников из команды автора. Автор PR исключается из списка кандидатов. Если в команде меньше 2 доступных участников, назначается доступное количество (может быть 0 или 1).
Переназначение ревьювера: Заменяемый ревьювер удаляется из списка, вместо него назначается случайный активный участник из команды заменяемого ревьювера (не автора PR). Из кандидатов исключаются:
- Заменяемый ревьювер
- Автор PR
- Все текущие ревьюверы данного PR
Слияние PR: После слияния PR изменение списка ревьюверов становится невозможным. Операция слияния идемпотентна - повторный вызов не вызывает ошибку и возвращает актуальное состояние PR.
Статус активности: Пользователи со статусом is_active = false не участвуют в автоматическом назначении на ревью.
TEAM_EXISTS - попытка создать команду с существующим именем PR_EXISTS - попытка создать PR с существующим идентификатором PR_MERGED - попытка изменить PR после слияния NOT_ASSIGNED - указанный пользователь не назначен ревьювером на данный PR NO_CANDIDATE - нет доступных кандидатов для переназначения NOT_FOUND - запрашиваемый ресурс не найден
Установка зависимостей:
go mod downloadЗапуск только PostgreSQL:
docker-compose up postgres -dПрименение миграций:
Bash/Linux/Mac:
make migrate-upWindows PowerShell (или если make недоступен):
migrate -path migrations -database "postgresql://user:password@localhost:5432/pr_reviewer?sslmode=disable" upЗапуск сервиса локально:
Bash/Linux/Mac:
make runWindows PowerShell:
go run cmd/server/main.goСборка бинарного файла:
Bash/Linux/Mac:
make buildWindows PowerShell:
go build -o bin/server.exe cmd/server/main.goЗапуск тестов:
make test
# или напрямую
go test -v -race -coverprofile=coverage.out ./...Запуск линтера (опционально, требует установки golangci-lint):
make lint
# или напрямую
golangci-lint runПримечание: Для работы make lint необходимо установить golangci-lint. Это опциональный инструмент для разработки, не требуется для запуска сервиса через docker-compose up.
Очистка временных файлов и остановка контейнеров:
Bash/Linux/Mac:
make clean
docker-compose down -vWindows PowerShell (если make недоступен):
# очистка бинарных файлов и coverage
Remove-Item -Recurse -Force bin -ErrorAction SilentlyContinue
Remove-Item coverage.out, coverage.html -ErrorAction SilentlyContinue
# остановка контейнеров
docker-compose down -vДополнительный эндпоинт для обновления команд: Спецификация OpenAPI определяет что POST /team/add должен возвращать ошибку TEAM_EXISTS при попытке создать существующую команду. Для практического использования добавлен дополнительный эндпоинт POST /team/update, который позволяет обновлять состав существующих команд. Это решение соблюдает спецификацию и одновременно обеспечивает необходимый функционал для управления командами.
Переназначение из команды заменяемого: Согласно техническому заданию, новый ревьювер выбирается из команды того участника, которого заменяют, а не из команды автора PR. Это позволяет сохранить распределение ревью внутри команды заменяемого.
Идемпотентность операции слияния: Если PR уже находится в статусе MERGED, повторный вызов операции merge возвращает текущее состояние с кодом 200 вместо ошибки. Это упрощает интеграцию с внешними системами.
Исключения при переназначении: Для предотвращения назначения одного человека несколько раз на один PR, система исключает из кандидатов:
- Заменяемого ревьювера
- Автора PR
- Всех текущих ревьюверов
Порядок полей в JSON: Используется стандартная сериализация Go. Порядок полей может отличаться от примеров в спецификации OpenAPI, но структура данных полностью соответствует.
Генерация случайных значений: Для выбора случайных ревьюверов используется пакет math/rand с перемешиванием списка кандидатов. Для повышения энтропии в production окружении рекомендуется использовать crypto/rand.
Эндпоинт статистики: Добавлен дополнительный эндпоинт GET /stats для получения аналитики по пользователям. Для каждого пользователя показывается количество созданных PR, общее количество назначенных ревью и количество активных (открытых) ревью. Статистика упорядочена по количеству назначенных ревью в порядке убывания, что помогает увидеть наиболее загруженных участников команды. Это один из дополнительных заданий из технического задания.
Все параметры настраиваются через переменные окружения:
PORT (по умолчанию 8080) - порт HTTP сервера DATABASE_URL (обязательный) - строка подключения к PostgreSQL DB_MAX_CONNS (по умолчанию 25) - максимум соединений в пуле DB_MIN_CONNS (по умолчанию 5) - минимум соединений в пуле LOG_LEVEL (по умолчанию info) - уровень логирования (debug, info, warn, error)
Конфигурация автоматически загружается из файла .env при запуске приложения. Если файл .env не найден, используются значения по умолчанию или переменные окружения, установленные системой (например, через docker-compose).
Connection Pooling:
- Диапазон соединений с PostgreSQL: 5-25
- Период проверки соединений: 1 минута
- Максимальное время жизни соединения: 1 час
- Максимальное время простоя соединения: 30 минут
Индексы базы данных:
- Индекс на users.team_name для поиска по команде
- Индекс на users.is_active для фильтрации активных
- Индекс на pull_requests.status для фильтрации по статусу
- Индекс на pull_requests.author_id для поиска по автору
- Индекс на pull_request_reviewers.user_id для поиска PR по ревьюверу
Таймауты HTTP:
- Read Timeout: 15 секунд
- Write Timeout: 15 секунд
- Idle Timeout: 60 секунд
- Graceful Shutdown: 30 секунд
Транзакции:
- Создание команды с участниками выполняется в одной транзакции
- Создание PR с назначением ревьюверов в одной транзакции
- Переназначение ревьювера в одной транзакции
Для проверки состояния сервиса используйте health check endpoint.
Bash/Linux/Mac:
curl http://localhost:8080/healthWindows PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/health" -Method GETОжидаемый(примерный) результат при успешной проверке:
{
"status": "healthy",
"database": {
"status": "healthy",
"response_time": "7.038695ms"
},
"service": "pr-reviewer-service",
"version": "1.0.0",
"uptime": "1h4m45.721163252s"
}HTTP статус: 200 OK
Пояснение: endpoint возвращает детальную информацию о состоянии сервиса, включая статус подключения к базе данных, время отклика БД, версию сервиса и время работы с момента запуска. Таймаут проверки составляет 2 секунды.
Если база данных недоступна:
{
"error": {
"code": "UNHEALTHY",
"message": "Database connection failed"
}
}HTTP статус: 503 Service Unavailable
Подключение к PostgreSQL в Docker контейнере:
docker exec -it pr_reviewer_db psql -U user -d pr_reviewerПросмотр всех таблиц:
\dtПросмотр структуры конкретной таблицы:
\d users
\d teams
\d pull_requests
\d pull_request_reviewersПросмотр данных в таблице:
SELECT * FROM users;
SELECT * FROM teams;
SELECT * FROM pull_requests;
SELECT * FROM pull_request_reviewers;Просмотр PR с ревьюверами:
SELECT
pr.pull_request_id,
pr.pull_request_name,
pr.status,
u.username as author,
array_agg(r.username) as reviewers
FROM pull_requests pr
JOIN users u ON pr.author_id = u.user_id
LEFT JOIN pull_request_reviewers prr ON pr.pull_request_id = prr.pull_request_id
LEFT JOIN users r ON prr.user_id = r.user_id
GROUP BY pr.pull_request_id, pr.pull_request_name, pr.status, u.username;Очистка всех таблиц:
TRUNCATE TABLE pull_request_reviewers, pull_requests, users, teams CASCADE;Выход из psql:
\qТестовое задание для Авито (осенняя волна 2025)
.env файл загружен в репозиторий специально для удобства запуска
Надеюсь на апрув))