Initialize project scaffold and domain model
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
# Rust Code Rules
|
||||
|
||||
## 1. Назначение документа
|
||||
|
||||
Этот документ фиксирует Rust-specific правила кода для проекта:
|
||||
|
||||
- toolchain;
|
||||
- linting;
|
||||
- formatting;
|
||||
- ошибки;
|
||||
- async;
|
||||
- ownership;
|
||||
- visibility;
|
||||
- dependency hygiene.
|
||||
|
||||
Цель документа - убрать плавающие договоренности по стилю и практике командной Rust-разработки.
|
||||
|
||||
## 2. Toolchain
|
||||
|
||||
### 2.1. Версия Rust
|
||||
|
||||
Для проекта должен быть зафиксирован `rust-toolchain.toml`.
|
||||
|
||||
В нем должны быть определены:
|
||||
|
||||
- стабильный `channel`;
|
||||
- `edition`;
|
||||
- при необходимости `components`.
|
||||
|
||||
Рекомендуемый состав:
|
||||
|
||||
- `rustfmt`
|
||||
- `clippy`
|
||||
|
||||
### 2.2. MSRV
|
||||
|
||||
Нужно зафиксировать `MSRV` - минимально поддерживаемую версию Rust.
|
||||
|
||||
Правило:
|
||||
|
||||
- без необходимости не использовать возможности языка новее зафиксированного `MSRV`;
|
||||
- обновление `MSRV` - это отдельное осознанное решение.
|
||||
|
||||
## 3. Formatting и linting
|
||||
|
||||
### 3.1. Formatting
|
||||
|
||||
Обязательное правило:
|
||||
|
||||
- весь код форматируется через `cargo fmt`.
|
||||
|
||||
Ручной стиль форматирования не обсуждается и не поддерживается.
|
||||
|
||||
### 3.2. Clippy
|
||||
|
||||
Обязательное правило:
|
||||
|
||||
- `cargo clippy --all-targets --all-features -- -D warnings`
|
||||
|
||||
Предупреждения считаются ошибками, если нет явно зафиксированного исключения.
|
||||
|
||||
### 3.3. CI quality gates
|
||||
|
||||
Минимально в CI должны запускаться:
|
||||
|
||||
- `cargo fmt --check`
|
||||
- `cargo clippy --all-targets --all-features -- -D warnings`
|
||||
- `cargo test`
|
||||
|
||||
Опционально позже:
|
||||
|
||||
- `cargo deny`
|
||||
- `cargo audit`
|
||||
|
||||
## 4. `unsafe`
|
||||
|
||||
Для проекта принимается правило:
|
||||
|
||||
- `unsafe` запрещен по умолчанию.
|
||||
|
||||
Если когда-либо потребуется `unsafe`, то:
|
||||
|
||||
- это должно быть отдельное осознанное решение;
|
||||
- причина должна быть технически обоснована;
|
||||
- блок должен быть минимальным;
|
||||
- вокруг него должны быть тесты.
|
||||
|
||||
Для MVP можно считать:
|
||||
|
||||
- `unsafe_code = deny`
|
||||
|
||||
## 5. Panic policy
|
||||
|
||||
В production code запрещены:
|
||||
|
||||
- `unwrap()`
|
||||
- `expect()`
|
||||
- `todo!()`
|
||||
- `unimplemented!()`
|
||||
- `dbg!()`
|
||||
- необоснованные `panic!()`
|
||||
|
||||
Допускается:
|
||||
|
||||
- в тестах;
|
||||
- в очень раннем bootstrap-коде, если это действительно аварийное завершение и не часть доменной логики.
|
||||
|
||||
Базовое правило:
|
||||
|
||||
- ошибки возвращаются через `Result`, а не через panic.
|
||||
|
||||
## 6. Правила ошибок
|
||||
|
||||
### 6.1. Domain и service errors
|
||||
|
||||
В домене и сервисах использовать типизированные ошибки.
|
||||
|
||||
Рекомендуемо:
|
||||
|
||||
- `thiserror`
|
||||
|
||||
### 6.2. Application boundary
|
||||
|
||||
На верхних слоях приложений допускается агрегирование ошибок, если это упрощает wiring.
|
||||
|
||||
При необходимости:
|
||||
|
||||
- `anyhow` только на внешних границах приложения, не в доменной модели.
|
||||
|
||||
### 6.3. Error context
|
||||
|
||||
Ошибка должна сохранять стадию отказа:
|
||||
|
||||
- schema
|
||||
- mapping
|
||||
- adapter
|
||||
- persistence
|
||||
- external service
|
||||
- internal runtime
|
||||
|
||||
## 7. Visibility rules
|
||||
|
||||
Правило:
|
||||
|
||||
- по умолчанию все приватное;
|
||||
- `pub(crate)` предпочтительнее `pub`;
|
||||
- публичный API должен быть минимальным.
|
||||
|
||||
Нельзя:
|
||||
|
||||
- открывать модуль наружу "на всякий случай";
|
||||
- делать `pub` просто ради удобства из соседнего файла;
|
||||
- реэкспортировать целые деревья модулей без причины.
|
||||
|
||||
## 8. Ownership и данные
|
||||
|
||||
### 8.1. Клонирование
|
||||
|
||||
Правило:
|
||||
|
||||
- не клонировать данные без необходимости;
|
||||
- клон должен быть осознанным, а не способом обойти borrow checker без понимания причины.
|
||||
|
||||
### 8.2. Shared mutability
|
||||
|
||||
Правило:
|
||||
|
||||
- не использовать `Arc<Mutex<_>>` как универсальный контейнер состояния;
|
||||
- shared mutability допускается только там, где она действительно нужна по архитектуре.
|
||||
|
||||
### 8.3. ID types
|
||||
|
||||
Идентификаторы должны быть отдельными типами, а не просто `String`.
|
||||
|
||||
Примеры:
|
||||
|
||||
- `OperationId`
|
||||
- `DescriptorId`
|
||||
- `AuthProfileId`
|
||||
|
||||
## 9. Async rules
|
||||
|
||||
### 9.1. Где допускается `async`
|
||||
|
||||
`async` используется только там, где есть:
|
||||
|
||||
- I/O;
|
||||
- network;
|
||||
- storage;
|
||||
- async boundary приложения.
|
||||
|
||||
### 9.2. Где `async` не нужен
|
||||
|
||||
Нельзя превращать:
|
||||
|
||||
- schema validation;
|
||||
- mapping;
|
||||
- чистую доменную логику;
|
||||
- небольшие derived methods
|
||||
|
||||
в `async fn` без причины.
|
||||
|
||||
### 9.3. `tokio`
|
||||
|
||||
`tokio` должен находиться:
|
||||
|
||||
- в приложениях;
|
||||
- в I/O слоях;
|
||||
- в адаптерах и runtime orchestration, если там есть реальный async.
|
||||
|
||||
Доменный слой не должен зависеть от `tokio`.
|
||||
|
||||
## 10. API design rules
|
||||
|
||||
### 10.1. Конструкторы
|
||||
|
||||
Использовать:
|
||||
|
||||
- `new()` для гарантированно валидного и простого создания;
|
||||
- `try_new()` там, где есть валидация и возможна ошибка.
|
||||
|
||||
### 10.2. Builders
|
||||
|
||||
Если структура имеет много параметров и прямой конструктор становится нечитаемым, допускается builder.
|
||||
|
||||
Но:
|
||||
|
||||
- builder не должен маскировать плохую модель данных;
|
||||
- builder не должен использоваться как замена нормальной декомпозиции.
|
||||
|
||||
### 10.3. DTO отдельно от domain
|
||||
|
||||
Если HTTP payload начинает расходиться с доменной моделью, нужно вводить отдельный DTO слой.
|
||||
|
||||
Нельзя:
|
||||
|
||||
- тащить `serde`-ориентированный API payload прямо в домен только ради удобства.
|
||||
|
||||
## 11. Dependency rules
|
||||
|
||||
### 11.1. Внешние crates
|
||||
|
||||
Правило:
|
||||
|
||||
- сначала использовать `std`;
|
||||
- потом существующие внутренние abstractions;
|
||||
- только потом тянуть новый внешний crate.
|
||||
|
||||
Нельзя:
|
||||
|
||||
- добавлять зависимость "на всякий случай";
|
||||
- дублировать crates с пересекающейся функцией без причины.
|
||||
|
||||
### 11.2. Макросы
|
||||
|
||||
Правило:
|
||||
|
||||
- не злоупотреблять макросами там, где обычный Rust-код читается лучше;
|
||||
- derive-макросы допустимы;
|
||||
- сложные процедурные макросы без сильной причины не нужны.
|
||||
|
||||
## 12. Serialization rules
|
||||
|
||||
### 12.1. JSON/YAML
|
||||
|
||||
Правило:
|
||||
|
||||
- доменная модель одна;
|
||||
- `JSON` и `YAML` - только два формата сериализации;
|
||||
- нельзя допускать, чтобы YAML export стал отдельной несовместимой моделью.
|
||||
|
||||
### 12.2. Secrets
|
||||
|
||||
Никогда не сериализовать:
|
||||
|
||||
- реальные токены;
|
||||
- пароли;
|
||||
- API keys
|
||||
|
||||
в exports, logs и test snapshots.
|
||||
|
||||
## 13. Тестовые практики на уровне Rust-кода
|
||||
|
||||
Минимально:
|
||||
|
||||
- unit tests рядом с модулем или в `tests`;
|
||||
- integration tests для crate boundaries;
|
||||
- фикстуры для schema/mapping/proto/yaml roundtrip.
|
||||
|
||||
Полезное правило:
|
||||
|
||||
- баг сначала воспроизводится тестом, потом фиксится кодом.
|
||||
|
||||
## 14. Что часто запрещают в Rust-командах
|
||||
|
||||
Практически всегда под запретом:
|
||||
|
||||
- `unwrap()` в production code;
|
||||
- `unsafe` без review;
|
||||
- giant modules;
|
||||
- giant enums со всем подряд;
|
||||
- giant services, где смешаны orchestration и transport;
|
||||
- абстракции "на будущее" без второго реального кейса.
|
||||
|
||||
## 15. Практический итог
|
||||
|
||||
Для этого проекта правильный Rust-профиль такой:
|
||||
|
||||
- фиксированный toolchain;
|
||||
- обязательные `fmt` и `clippy`;
|
||||
- `unsafe` запрещен по умолчанию;
|
||||
- panics запрещены в production code;
|
||||
- ошибки типизированы;
|
||||
- `pub` минимизируется;
|
||||
- `async` только на реальных async boundaries;
|
||||
- код читается за счет имен и декомпозиции, а не за счет комментариев.
|
||||
Reference in New Issue
Block a user