Rustango: приносимо «батарейки» Django у Rust
Веб-фреймворк «з батарейками в комплекті», що переносить досвід розробника з Django — типізований ORM, міграції, admin, автентифікацію та команди керування — в асинхронний Rust, не відмовляючись від гарантій на етапі компіляції.
Я люблю Django. За роки випуску продуктів на ньому філософія фреймворку вбудувалася в те, як я мислю: типізований шар моделей, міграції, що пишуть себе самі, адмінка, яку ти отримуєш безкоштовно, і CLI для управління, що зв'язує все це докупи. Я також люблю Rust - компілятор, який відмовляється пропускати цілий клас багів у продакшн, і рантайм, якому не потрібен ані збирач сміття, ані нянька.
Довгий час ці дві любові жили в окремих світах. Rustango - це моя спроба звести їх разом: веб-фреймворк із батарейками в комплекті, який переносить досвід розробника Django на асинхронний Rust - не відмовляючись від гарантій на етапі компіляції.
Типізований ORM, що читається як Django
Серцем будь-якого фреймворку в стилі Django є шар моделей. У Rustango ви виводите Model на звичайній структурі й анотуєте її таблицею, первинним ключем та обмеженнями стовпців. Макрос derive генерує типізований набір стовпців і ланцюжковий QuerySet - тож конструктор запитів перевіряється компілятором, а не виявляється під час виконання.
#[derive(Model, Default, Serialize, Deserialize)]
#[rustango(table = "blog_post", app = "blog")]
pub struct Post {
#[rustango(primary_key)]
pub id: Auto<i64>,
#[rustango(max_length = 200)]
pub title: String,
pub published: bool,
#[rustango(auto_now_add)]
pub created_at: Auto<DateTime<Utc>>,
}
// A chainable, type-checked QuerySet - column names are real symbols.
let recent = Post::objects()
.where_(Post::published.eq(true))
.order_by(&[("created_at", false)])
.fetch(pool)
.await?;
Немає рядкового (stringly-typed) DSL запитів, у якому можна зробити описку. Post::published - це згенерований символ; напишіть його з помилкою, і збірка впаде. Ті самі метадані стовпців живлять вставки, оновлення та diff схеми, який споживає рушій міграцій.
Міграції, згенеровані та застосовані
Моделі Rustango несуть SCHEMA, яку фреймворк може порівнювати з базою даних. CLI генерує файли міграцій з різниці й застосовує їх для кожної бази - і оскільки Rustango мультитенантний з самого фундаменту, він знає різницю між спільною схемою реєстру та схемою кожного тенанта.
cargo run -- makemigrations # diff models -> migration files
cargo run -- migrate-registry # apply the shared/registry schema
cargo run -- migrate-tenants # apply every tenant database
Адмінка, яку вам не довелося писати
Анотуйте модель блоком admin(...) - перелічіть колонки, порядок сортування, пошук - і Rustango згенерує для неї повноцінну CRUD-адмінку: списки з фільтрами та пагінацією, форми створення/редагування, згенеровані з ваших віджетів полів, а також масові дії. Це та єдина функція, завдяки якій Django відчувається як чіт-код, і саме її мені найбільше бракувало в Rust.
#[rustango(
table = "blog_post", app = "blog",
admin(list_display = "title, published, created_at", ordering = "-created_at")
)]
pub struct Post { /* ... */ }
Усе з коробки: автентифікація, сесії, форми, CSRF, кешування
Фреймворк заслуговує на слово batteries-included завдяки тим нудним речам, які він постачає, щоб ви ніколи не писали їх знову. Rustango приносить підписані сесії, стек пароля/потоку автентифікації, шар форм із CSRF-захистом, кеш (включно з повносторінковим кешуванням), сигнали, підписані URL та manage::Cli, який поєднує маршрутизацію, мультитенантність, міграції та ваші власні команди в один бінарний файл. Маршрутизація базується на Axum і Tokio, тож асинхронна історія — це зріле рішення, якому екосистема Rust уже довіряє.
Обіцянка Django ніколи не полягала в синтаксисі — вона полягала в тому, що фреймворк уже ухвалив за вас усі нудні рішення. Rustango дотримується цієї обіцянки, а компілятор тримає вас у чесних рамках.
Чи воно було того варте?
Портування фреймворку - це не проєкт на вихідні, і Rust вимагає більше зусиль наперед, ніж Python. Але віддача реальна: одне й те саме визначення моделі керує базою даних, адмінкою та API, а borrow checker перетворює цілу категорію продакшн-інцидентів на помилки компіляції. Якщо ви коли-небудь мріяли, щоб Django працював зі швидкістю Rust і гарантіями Rust, Rustango - це те, як ця мрія виглядає в коді.