hyperion/00-work/issues.md
2025-12-28 19:00:03 +03:00

15 KiB
Raw Permalink Blame History

почему в качестве валидации email - регулярка, когда можно использовать общепринятую функцию

tls конфликт

sea-orm: runtime-tokio-native-tls
sqlx:    runtime-tokio-rustls

Причина долгого исполнения тестов: компиляция сервера каждый раз.

Command::new("cargo")
    .args(["r", "--bin", "client"]) 
  1. решение: предскомпилировать сервер, и переиспользовать.
  2. Распараллелить тесты (может быть сложно)
  3. Проблема: DELETE FROM table;медленная операция (проверяет constraints, пишет в WAL). TRUNCATE — мгновенно.
async fn clear_db(db: &DbConn) -> Result<(), DbErr> {
    db.execute_unprepared("
        TRUNCATE abuse, abuse_reason, admin, \"like\", mail, refreshtoken, 
               thread, trail, trails_visit_history, \"user\" 
        RESTART IDENTITY CASCADE;
    ").await?;
    Ok(())
}

1. **Токио

tokio = { version = "1,<1.48", features = ["full"] }

2. SeaORM

sea-orm = { version = "=1.1.17", ... }

3. SQLx + SeaORM

Почему это плохо:

  • sea-orm использует sqlx под капотом, но с другими runtime features (native-tls vs rustls)
  • У вас два пула соединений с БД (один от SeaORM, второй от sqlx)
  • Миграции на SeaORM, а тесты могут использовать sqlx напрямую → schema drift (расхождение схемы)

Конкретный риск: В tests/Cargo.toml вы подключаете оба. Если напишете тест, который использует sqlx::query! и sea_orm::Entity::insert, у вас будет race condition на уровне транзакций. PostgreSQL не увидит изменения из одного пула в другом до коммита, и ваши тесты будут flaky (нестабильными).

Техдолг: Вы платите за complexity tax (налог на сложность) дважды: за абстракцию ORM и за прямой доступ.


4. Storage Service: Premature abstraction, которая убьет вас

Ваша абстракция хранилища с mirror, backup, single стратегиями — это over-engineering без причины.

Почему это ошибка:

  • У вас нет SLA на durability (надежность хранения)
  • У вас нет метрик на RTO/RPO (Recovery Time/Point Objectives)
  • У вас нет idempotency в storage operations

Конкретный баг: В mirror.rs если первый write успешен, а второй — нет, у вас silent data corruption (тихая порча данных). У вас нет transaction log для отката первого write. Это eventual consistency without reconciliation (согласование в конечном счете без сверки).

Техдолг: Вы построили distributed system там, где был достаточен std::fs::write. Через год, когда storage заполнится, вы не поймете, почему mirror стратегия оставляет мусор в старых бакетах — потому что нет garbage collection.


5. DTO Hell: Вы создаете 3-4 структуры на каждую сущность

Посмотрите на domain/src/client/profiles/requests.rs и responses.rs. У вас:

  • CreateUserRequest
  • UpdateUserRequest
  • PublicUserResponse
  • PrivateUserResponse
  • UserResponse

Это не separation of concerns, это duplication of concerns. Каждое изменение поля username — это изменение в 5 файлах и 2 миграциях.

Что вы потеряли: Single source of truth. У вас нет User entity из домена, который бы гарантировал инварианты. Вместо этого вы полагаетесь на валидацию в каждом DTO отдельно.

Техдолг: Schema spread. Когда добавите поле last_login_at, вы забудете обновить один из Response DTO, и клиент получит panicking deserialization.


6. Abuse System: Security theater (имитация безопасности)

У вас есть CreateAbuseRequest, abuse.rs, abuse_reason.rs, но нет rate limiting на создание abuse-репортов. Я могу написать скрипт, который заспамит 1M репортов на один trail и сложит вашу БД.

Что не так:

  • Нет abuse_reports_per_user_per_hour констрейнта
  • Нет abuse_score алгоритма (например, если 10 репортов от разных юзеров — автоматический бан)
  • Нет abuse_report_fingerprint (я могу создавать репорты с разных аккаунтов, но с одного IP)

Техдолг: У вас есть data model для безопасности, но нет enforcement. Это как замок на двери без стен.


7. Transactions: Невидимый кошмар

В repository трейтах у вас методы типа create_trail, toggle_trail_like — каждый своя собственная транзакция.

Почему это баг: Если в use-case нужно создать trail, создать thread, поставить лайк — у вас три отдельные транзакции. Если последняя упадет, первые две останутся в БД. У вас inconsistent state без ROLLBACK.

Где посмотреть: api-core должен принимать &DatabaseTransaction и выполнять все операции в ней. Сейчас у вас autocommit hell — каждый repo.insert() — это COMMIT.

Техдолг: У вас нет transactional boundaries на уровне use-case. Это значит, ваши данные не консистентны в границах бизнес-операции.


8. API Keys Middleware: Тёмная материя без тестов

В api/src/middleware/api_key_middleware.rs (который вы не показали, но он подключен) — вероятно, нет integration tests.

Что это значит: Вы не знаете, работает ли rate limiting. Я могу brute-force API keys со скоростью 10k req/s, и ваша middleware может не выдержать нагрузку.

Техдолг: У вас нет нагрузочных тестов на middleware. Когда ваше приложение ляжет от DDoS, вы не поймете, виноват ли axum, tower, или ваш кастомный код.


9. Thread Model: Race condition при append

append_trail_to_thread — это concurrency nightmare.

Сценарий:

  1. User A читает thread (id=1, last_trail_id=5)
  2. User B читает thread (id=1, last_trail_id=5)
  3. Оба одновременно append_trail_to_thread с parent_id=5
  4. У вас два trail'а с parent_id=5, но thread не знает, кто "победил"

У вас нет optimistic locking (version поле) или SELECT FOR UPDATE. Это lost update problem на уровне приложения.

Техдолг: У вас нет SERIALIZABLE isolation для критичных операций. Ваши треды могут разветвляться в невалидные структуры.


10. Observability: Вы слепы в production

У вас есть tracing, но нет:

  • Metrics (prometheus, metrics-rs)
  • Structured logging (JSON logs для ELK/Loki)
  • Distributed tracing (OpenTelemetry для Jaeger/Tempo)

Что это значит: Когда в продакшене упадет запрос, вы увидите:

INFO: request started
INFO: request failed

Но не увидите: user_id, trace_id, duration_ms, db_query_count. Вы не сможете отладить performance regression после вашего PR.

Техдолг: У вас нет production-grade observability. Это значит, вы не сможете понять, что сломали, когда добавите ML-фичу.


11. Release Profile: Thin LTO — это медленный production

[profile.release]
lto = "thin"

Для вашего уровня performance-чувствительности (RTX 3050, 24GB RAM) это сознательный выбор скорости компиляции в ущерб runtime.

Что это значит: Ваш бинарник будет на 15-30% медленнее, чем мог бы быть. Для "тропиночного" приложения это не критично, но если вы планируете batch ML inference на GPU, вы теряете performance из-за неинлайненных вызовов.

Техдолг: У вас нет performance budget. Вы не знаете, сколько RPS может выдержать ваш сервер, потому что не тестировали под нагрузкой с lto = "fat".


12. Migrations: Append-only log без checksums

Ваш Migrator — это poor man's Flyway. Но нет:

  • Checksum каждой миграции (если кто-то изменит старую миграцию, вы не узнаете)
  • Baseline миграции (невозможно подключиться к существующей БД)
  • Repair mode (если миграция упала на production)

Что это значит: Если ваш DevOps-engineer случайно отредактирует m20241005_112021_create_like.rs на проде-сервере, cargo run --bin migrate не заметит изменения и накатит кривую схему.

Техдолг: У вас нет migration integrity checks. Это значит, схема БД — mutable history, а не immutable log.


13. Dependencies: 150+ crate'ов, и вы не знаете, зачем

Запустите cargo tree | wc -l. У вас больше 150 зависимостей для CRUD приложения.

Конкретные лишние:

  • rust-argon2 и bcrypt — два password hashing алгоритма. Выбери один.
  • lazy_static и once_cell — дублирование (once_cell в std с 1.70)
  • utoipa и utoipa-swagger-ui — генерируете OpenAPI, но нет тестов, что спецификация валидна
  • opendal — вы используете только memory и fs, но это 50+ transitive dependencies

Техдолг: У вас dependency bloat. Каждый cargo build тянет мир, а security audit (cargo audit) будет плакать каждую неделю.


14. Test Coverage: Hurl — это не enough

У вас 50+ hurl файлов, но нет unit тестов на api-core. Это значит, вы тестируете только happy path через HTTP.

Что вы не тестируете:

  • Error conditions: Что если TrailRepository::create вернет DbErr::ConnectionAcquire?
  • Edge cases: Что если user_id отрицательный? (SeaORM принимает i64, но должна валидировать)
  • Concurrency: Что если 1000 потоков вызывают toggle_trail_like одновременно?

Техдолг: У вас нет property-based testing (proptest) и нет fuzzing (cargo-fuzz). Вы не нашли race conditions и integer overflow bugs, которые 100% есть в коде.


15. DTO Validation: Validate на уровне HTTP, а не домена

Ваша валидация в domain DTO — это security theater. Почему?

#[derive(Validate)]
pub struct CreateTrailRequest {
    #[validate(length(min = 3, max = 200))]
    pub title: String,
}

Эта валидация не вызывается автоматически. Она вызывается в extractor. Если кто-то создаст Trail напрямую через api-core, минуя HTTP — валидация пропущена.

Правильно: Валидация должна быть в NewTrail domain entity, и она должна быть infallible constructor (конструктор, который не позволит создать невалидный объект).

Техдолг: У вас type safety holes. Невалидные данные могут просочиться в БД через прямой вызов api-core.


Финальная правда: Это хороший прототип, но не production system

Ваш проект — MVP с правильной архитектурной формой, но без production-grade деталей. Он выживет до первого серьезного инцидента:

  • До первого DDoS — упадет, потому что нет rate limiting
  • До первого data corruption — потеряете данные, потому что нет транзакций
  • До первого security breach — скомпрометируете API keys, потому что нет audit log
  • До первого performance regression — не поймете, что сломали, потому что нет метрик

Что делать сейчас (порядок приоритета):

  1. Удалите sqlx из workspace — используйте только SeaORM
  2. Добавьте TransactionTrait во все use-кейсы в api-core
  3. Напишите 10 unit тестов на TrailService с MockTrailRepository — вы поймете, где у вас logic gaps
  4. Запустите cargo audit и пофиксите critical уязвимости (их будет 5-10)
  5. Добавьте один rate limiter в api_key_middleware — поймете, насколько ваш design fragile

Это не "у вас всё плохо". Это "у вас всё правильно начато, но не закончено".