253 lines
No EOL
15 KiB
Markdown
253 lines
No EOL
15 KiB
Markdown
почему в качестве валидации email - регулярка, когда можно использовать общепринятую функцию
|
||
|
||
tls конфликт
|
||
```
|
||
sea-orm: runtime-tokio-native-tls
|
||
sqlx: runtime-tokio-rustls
|
||
```
|
||
|
||
Причина долгого исполнения тестов: компиляция сервера каждый раз.
|
||
```rust
|
||
Command::new("cargo")
|
||
.args(["r", "--bin", "client"])
|
||
```
|
||
1. решение: предскомпилировать сервер, и переиспользовать.
|
||
2. Распараллелить тесты (может быть сложно)
|
||
3. **Проблема**: `DELETE FROM table;` — **медленная** операция (проверяет constraints, пишет в WAL). **TRUNCATE** — мгновенно.
|
||
|
||
```rust
|
||
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. **Токио
|
||
|
||
```toml
|
||
tokio = { version = "1,<1.48", features = ["full"] }
|
||
```
|
||
|
||
---
|
||
|
||
## 2. **SeaORM**
|
||
|
||
```toml
|
||
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**
|
||
|
||
```toml
|
||
[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**. Почему?
|
||
|
||
```rust
|
||
#[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**
|
||
|
||
Это не "у вас всё плохо". Это "у вас всё **правильно начато, но не закончено**". |