218 lines
11 KiB
Markdown
218 lines
11 KiB
Markdown
# План GO-001: убрать прямые зависимости handler-ов от repos и modules
|
||
|
||
## Цель
|
||
|
||
`internal/app` принимает transport-вход, извлекает пользователя и параметры,
|
||
вызывает прикладной service и преобразует результат в protobuf/HTTP. Handler не
|
||
импортирует repository и не владеет прикладной операцией.
|
||
|
||
Не трогаем в этой задаче:
|
||
|
||
- `proto/`, API-контракт и protobuf-генерацию;
|
||
- wiring в `cmd`, где repositories и modules должны собираться;
|
||
- `roles` и `processor_jwt`, если они используются только для извлечения
|
||
identity/проверки transport-доступа;
|
||
- единое исправление `err.Error()` во всех handler-ах — это GO-003.
|
||
|
||
## Текущие точки нарушения
|
||
|
||
| Участок | Прямые зависимости app | Что оставить в app | Что должно уйти в service |
|
||
| --- | --- | --- | --- |
|
||
| `app/team_registration.go` | `teams_repo` | HTTP/JSON, auth token, timeout | смысл ошибок регистрации |
|
||
| `app/team_qr.go` | `games_repo`, `team_qr_pdf` | auth, headers, `HttpBody` | ошибки игры и PDF |
|
||
| `app/users.go` | `users_repo` | claims, metadata, transport auth | ошибка отсутствующего пользователя |
|
||
| `app/scenario_archive.go` | `scenarios_repo`, archive/string modules | auth, ZIP headers, `HttpBody` | лимиты/ошибки сценарного архива |
|
||
| `app/files.go` | `file_storage` | auth, headers, `HttpBody` | ошибка отсутствующего файла |
|
||
| mapper-ы app | `game_model`, `formatter_utils` | protobuf mapping | модели и преобразования use-case |
|
||
|
||
## Маленькие этапы
|
||
|
||
### GO-001.0 — зафиксировать baseline и границы
|
||
|
||
1. Проверить текущий список импортов:
|
||
|
||
```sh
|
||
rg -n 'internal/(repos|modules)' evening_detective_server/internal/app --glob '*.go'
|
||
cd evening_detective_server && go test ./internal/app ./internal/services/...
|
||
```
|
||
|
||
2. Выписать в diff только файлы из таблицы выше.
|
||
3. После каждого следующего этапа проверять, что импорт repository исчез только
|
||
из app, а wiring в `cmd` остался.
|
||
|
||
Готово, если до изменений есть зафиксированный список импортов и понятен
|
||
targeted test для каждого участка.
|
||
|
||
### GO-001.1 — webhook регистрации команды
|
||
|
||
Файлы: `internal/app/team_registration.go`,
|
||
`internal/services/team_service/registration.go` и связанные тесты.
|
||
|
||
1. Найти, какие sentinel-ошибки `TeamService.RegisterTeam` уже возвращает.
|
||
2. Сделать ошибку закрытой регистрации частью контракта `team_service`, чтобы
|
||
handler не импортировал `teams_repo` только ради `errors.Is`.
|
||
3. Оставить `TeamRegistrationHandler` ответственным только за проверку bearer
|
||
token, разбор JSON/JSON-RPC, timeout и HTTP-код.
|
||
4. Обновить тесты handler-а на публичную ошибку service; не переносить JSON
|
||
parsing в service.
|
||
|
||
Проверка этапа:
|
||
|
||
```sh
|
||
cd evening_detective_server
|
||
go test ./internal/app ./internal/services/team_service
|
||
! rg 'internal/repos' internal/app/team_registration.go
|
||
```
|
||
|
||
Ожидаемый результат: регистрация с закрытым окном по-прежнему даёт `409`,
|
||
ошибочный payload — `400`, а app больше не знает `teams_repo`.
|
||
|
||
### GO-001.2 — выдача QR-кодов команд
|
||
|
||
Файлы: `internal/app/team_qr.go`,
|
||
`internal/services/team_service/service.go`, `team_qr.go` и тесты.
|
||
|
||
1. Перенести наружный смысл ошибок из repository/PDF-модуля на границу
|
||
`team_service`: игра не найдена и у команды нет карточек.
|
||
2. Не переносить в service формирование `HttpBody` и HTTP-заголовков.
|
||
3. В handler оставить проверку claims/ролей, валидацию `id`, mapping ошибок в
|
||
gRPC-коды и headers.
|
||
4. Проверить существующие тесты `team_qr_test.go` и добавить только недостающий
|
||
regression case на обе ошибки.
|
||
|
||
Проверка этапа:
|
||
|
||
```sh
|
||
cd evening_detective_server
|
||
go test ./internal/app ./internal/services/team_service
|
||
! rg 'internal/repos|team_qr_pdf' internal/app/team_qr.go
|
||
```
|
||
|
||
Ожидаемый результат: `DownloadTeamQR` сохраняет PDF и заголовки без прямой
|
||
зависимости app от repository/PDF implementation.
|
||
|
||
### GO-001.3 — операции пользователей
|
||
|
||
Файлы: `internal/app/users.go`, `internal/services/users_service/*` и тесты.
|
||
|
||
1. Найти, почему app использует `users_repo.ErrUserNotFound` только в
|
||
`ChangeUserPassword`.
|
||
2. Передать наружу ошибку через контракт `users_service` (владелец прикладного
|
||
смысла), не раскрывая в app storage-пакет.
|
||
3. Не менять здесь многочисленные protobuf-поля `Error`: их единое исправление
|
||
относится к GO-003.
|
||
4. Оставить в app извлечение claims, пароля из metadata, роли и вызов service.
|
||
5. Обновить unit-тесты на `NotFound` через service-owned error.
|
||
|
||
Проверка этапа:
|
||
|
||
```sh
|
||
cd evening_detective_server
|
||
go test ./internal/app ./internal/services/users_service
|
||
! rg 'internal/repos' internal/app/users.go
|
||
```
|
||
|
||
Ожидаемый результат: app не импортирует `users_repo`, а код gRPC для
|
||
отсутствующего пользователя не меняется.
|
||
|
||
### GO-001.4 — архив сценария
|
||
|
||
Файлы: `internal/app/scenario_archive.go`,
|
||
`internal/services/scenarios_service/*` и тесты.
|
||
|
||
1. Отделить transport-часть от прикладной: app оставляет auth, `HttpBody`,
|
||
`Content-Disposition` и проверку размера запроса.
|
||
2. Ошибку сценария не найдено получать через `scenarios_repo` в app; использовать
|
||
service-owned контракт ошибки.
|
||
3. Проверить, нужен ли app доступ к `scenario_archive.MaxArchiveSize()` только
|
||
как к transport-лимиту. Если да, предоставить лимит через service-owned
|
||
константу/функцию; не тащить repository или storage-модель.
|
||
4. `string_tools` оставить только если это чистая transport-санитизация имени;
|
||
иначе вернуть готовое безопасное имя из service. Не переносить бизнес-логику
|
||
в mapper.
|
||
5. Не менять формат ZIP и правила авторизации.
|
||
|
||
Проверка этапа:
|
||
|
||
```sh
|
||
cd evening_detective_server
|
||
go test ./internal/app ./internal/services/scenarios_service
|
||
! rg 'internal/repos' internal/app/scenario_archive.go
|
||
```
|
||
|
||
Ожидаемый результат: archive handler знает HTTP-представление, а не repository
|
||
и внутреннюю реализацию архива.
|
||
|
||
### GO-001.5 — скачивание файлов
|
||
|
||
Файлы: `internal/app/files.go`, `internal/services/file_service/*` и тесты.
|
||
|
||
1. Перенести `ErrFileNotFound` на публичную границу `file_service` (или сделать
|
||
service-owned alias без нового слоя), чтобы handler не импортировал
|
||
`file_storage`.
|
||
2. Оставить в app авторизацию автора, `nosniff`, `HttpBody` и mapping MIME-типа.
|
||
3. Проверить, что service уже возвращает полную модель файла и контекст;
|
||
новых DTO и интерфейсов не добавлять.
|
||
4. Добавить/обновить тест на `404` скачивания отсутствующего файла.
|
||
|
||
Проверка этапа:
|
||
|
||
```sh
|
||
cd evening_detective_server
|
||
go test ./internal/app ./internal/services/file_service
|
||
! rg 'internal/modules/file_storage' internal/app/files.go
|
||
```
|
||
|
||
### GO-001.6 — app mapper-ы и чистка импортов
|
||
|
||
Файлы: `internal/app/game_mapper.go`, `team_mapper.go`, `story_mapper.go`,
|
||
`internal/app/*`.
|
||
|
||
1. Не переносить protobuf mapping в service: он остаётся в `app`.
|
||
2. Проверить, что входные типы mapper-ов являются контрактом service, а не
|
||
типами repository/module. Если mapper использует `game_model` или
|
||
`formatter_utils` только как промежуточный use-case тип, перенести границу
|
||
в service минимальным изменением, сохранив существующий protobuf output.
|
||
3. Не переименовывать пакеты `*_utils` в рамках GO-001 — это GO-006.
|
||
4. После каждого переноса удалить неиспользуемые импорты и проверить callers.
|
||
|
||
Проверка этапа:
|
||
|
||
```sh
|
||
cd evening_detective_server
|
||
find internal/app internal/services -name '*.go' -print0 | xargs -0 gofmt -l
|
||
go vet ./...
|
||
go test ./internal/app ./internal/services/...
|
||
```
|
||
|
||
### GO-001.7 — финальная проверка архитектурного правила
|
||
|
||
1. Runtime-файлы `internal/app` не должны импортировать `internal/repos`.
|
||
2. Каждый RPC/HTTP handler должен вызывать service, а не repository/module
|
||
с бизнес-операцией.
|
||
3. `cmd/evening_detective_server/main.go` продолжает собирать repositories,
|
||
modules и services — его зависимости не удалять.
|
||
4. Проверить все callers изменённых ошибок и конструкторов.
|
||
5. Запустить полный набор проверок:
|
||
|
||
```sh
|
||
cd evening_detective_server
|
||
find internal/app internal/services -name '*.go' -print0 | xargs -0 gofmt -l
|
||
go vet ./...
|
||
go test ./...
|
||
```
|
||
|
||
6. В `todo.md` отметить GO-001 выполненной только после успешной проверки
|
||
за пределами sandbox, где разрешён loopback TCP.
|
||
|
||
## Критерий готовности GO-001
|
||
|
||
- Runtime `internal/app` не импортирует `internal/repos`.
|
||
- В app не осталось прямого вызова repository или технического module для
|
||
выполнения бизнес-операции.
|
||
- Существующие transport-коды, headers, JSON payloads и protobuf responses не
|
||
изменились намеренно.
|
||
- Для каждого перенесённого error mapping есть targeted test.
|
||
- `gofmt`, `go vet ./...` и полный `go test ./...` проходят в поддерживаемом
|
||
окружении.
|