Rustango: przenosimy baterie Django do Rusta
Framework webowy z pełnym wyposażeniem, który przenosi doświadczenie deweloperskie Django - typowany ORM, migracje, panel admina, uwierzytelnianie i polecenia zarządzające - do asynchronicznego Rusta, bez rezygnowania z gwarancji na etapie kompilacji.
Uwielbiam Django. Po latach dostarczania z jego pomocą produktów filozofia tego frameworka wrosła w mój sposób myślenia: typowana warstwa modeli, migracje, które piszą się same, panel administracyjny, który dostajesz za darmo, oraz CLI zarządzające, które spina to wszystko w całość. Uwielbiam też Rust - kompilator, który nie pozwala, aby cała klasa błędów trafiła na produkcję, oraz środowisko uruchomieniowe, które nie potrzebuje garbage collectora ani niańki.
Przez długi czas te dwie miłości żyły w oddzielnych światach. Rustango to moja próba połączenia ich razem: framework webowy z bateriami w zestawie, który przenosi doświadczenie programistyczne Django do asynchronicznego Rusta - bez rezygnowania z gwarancji na etapie kompilacji.
Typowany ORM, który czyta się jak Django
Sercem każdego frameworka w stylu Django jest warstwa modelu. W Rustango wyprowadzasz Model na zwykłej strukturze i adnotujesz ją tabelą, kluczem głównym oraz ograniczeniami kolumn. Makro derive generuje typowany zestaw kolumn oraz łańcuchowalny QuerySet - dzięki czemu builder zapytań jest sprawdzany przez kompilator, a nie odkrywany w czasie wykonania.
#[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ńcuchowalny, sprawdzany typowo QuerySet - nazwy kolumn to prawdziwe symbole.
let recent = Post::objects()
.where_(Post::published.eq(true))
.order_by(&[("created_at", false)])
.fetch(pool)
.await?;
Nie ma tekstowo typowanego DSL zapytań, w którym można zrobić literówkę. Post::published to wygenerowany symbol; wpisz go błędnie, a kompilacja się nie powiedzie. Te same metadane kolumn napędzają wstawianie, aktualizacje oraz różnicę schematu, którą pochłania silnik migracji.
Migracje, wygenerowane i zastosowane
Modele Rustango niosą SCHEMA, który framework może porównać z bazą danych. CLI generuje pliki migracji z różnicy i stosuje je per baza danych - a ponieważ Rustango jest wielodzierżawcowy od podstaw, zna różnicę między współdzielonym schematem rejestru a schematem każdego dzierżawcy.
cargo run -- makemigrations # diff models -> migration files
cargo run -- migrate-registry # apply the shared/registry schema
cargo run -- migrate-tenants # apply every tenant database
Panel administracyjny, którego nie musiałeś pisać
Wystarczy opatrzyć model blokiem admin(...) - kolumny listy, sortowanie, wyszukiwanie - a Rustango wyrenderuje dla niego pełny panel administracyjny CRUD: widoki list z filtrami i paginacją, formularze tworzenia/edycji generowane na podstawie widżetów pól oraz akcje zbiorcze. To jedyna funkcja, która sprawia, że Django przypomina cheat code, i której najbardziej brakowało mi w Ruście.
#[rustango(
table = "blog_post", app = "blog",
admin(list_display = "title, published, created_at", ordering = "-created_at")
)]
pub struct Post { /* ... */ }
Wbudowane funkcje: uwierzytelnianie, sesje, formularze, CSRF, buforowanie
Framework zasługuje na określenie batteries-included dzięki nudnym rzeczom, które dostarcza, byś nigdy więcej nie musiał ich pisać. Rustango wnosi podpisane sesje, stos haseł/przepływów uwierzytelniania, warstwę formularzy z ochroną CSRF, cache (w tym cache'owanie całych stron), sygnały, podpisane URL-e oraz manage::Cli, które łączy routing, wielodostępność (tenancy), migracje i Twoje własne polecenia w jeden plik binarny. Routing opiera się na Axum i Tokio, więc historia asynchroniczności jest tą dojrzałą, której ekosystem Rusta już ufa.
Obietnicą Django nigdy nie była składnia — chodziło o to, że framework już podjął za Ciebie nudne decyzje. Rustango dotrzymuje tej obietnicy, a kompilator trzyma Cię w ryzach.
Czy było warto?
Portowanie frameworka nie jest weekendowym projektem, a Rust wymaga więcej na starcie niż Python. Ale zwrot jest realny: ta sama definicja modelu napędza bazę danych, panel administracyjny i API, a borrow checker zamienia całą kategorię incydentów produkcyjnych w błędy kompilacji. Jeśli kiedykolwiek marzyłeś, aby Django działało z prędkością Rusta i z gwarancjami Rusta, Rustango jest tym marzeniem zapisanym w kodzie.