- Rwf ist ein umfassendes Framework zum Erstellen von Webanwendungen in Rust und bietet auf Basis des klassischen MVC-Patterns standardmäßig die Funktionen, die für den schnellen und sicheren Aufbau von Web-Apps nötig sind
- Zu den Grundfunktionen gehören ein HTTP-Server, ein ORM für PostgreSQL-Queries, dynamische Templates, Authentifizierung und User-Sessions, Middleware, Background-Jobs und geplante Jobs sowie Datenbankmigrationen
- Ebenfalls enthalten sind ein integriertes REST-Framework mit Unterstützung für JSON-Serialisierung, WebSockets, Hosting statischer Dateien, umgebungsspezifische Konfiguration, Logging und Metriken sowie eine CLI
- Durch die enge Integration mit Hotwired Turbo lassen sich backend-driven SPAs erstellen; außerdem bietet Rwf einen WSGI-Server für Migrationen von Django/Flask und einen Rack-Server für Migrationen von Rails
- Derzeit befindet sich Rwf im Beta-Status und sucht Early Adopters; die meisten Funktionen seien in gutem Zustand und die Dokumentation sei nutzbar
MVC-Web-Framework für Rust
- Rwf ist ein umfassendes Framework zum Erstellen von Webanwendungen in Rust
- Es ist nach dem klassischen MVC(model-view-controller)-Pattern aufgebaut
- Ziel ist es, die Funktionen, die zum einfachen Erstellen schneller und sicherer Web-Apps nötig sind, standardmäßig mitzuliefern
- Die Dokumentation ist in der offiziellen Dokumentation verfügbar
Gebotene Funktionen
- Server und Request-Verarbeitung
- Daten und Anwendungsstruktur
- Bietet ein benutzerfreundliches ORM, um PostgreSQL-Queries einfach zu erstellen
- Unterstützt Datenbankmigrationen
- Bietet Dynamic templates
- Enthält Authentication und integrierte User-Sessions
- API und Job-Verarbeitung
- Betriebs- und Entwicklungstools
- Unterstützt umgebungsspezifische Konfiguration
- Bietet Logging und Metriken
- Enthält eine CLI
Hotwired Turbo und Migrationsunterstützung
- Durch die enge Integration mit Hotwired Turbo lassen sich backend-driven SPAs erstellen
- Bietet einen WSGI server für die Migration von Django/Flask-Apps
- Bietet einen Rack server für die Migration von Rails
Schnellstart
- Man erstellt eine Rust-Binary-Anwendung und fügt
rwf als Dependency hinzu
cargo add rwf
- Eine einfache App verwendet
rwf::prelude::* und rwf::http::Server, um Controller und Routen zu definieren
use rwf::prelude::*;
use rwf::http::Server;
#[controller]
async fn index() -> Response {
Response::new().html("Welcome to Rwf!
")
}
#[tokio::main]
async fn main() {
Server::new(vec![
route!("/" => index),
])
.launch()
.await
.unwrap();
}
Beispiele und Projektstatus
- Typische Anwendungsfälle sind in den examples zu finden
- Rwf befindet sich derzeit im Beta-Status und sucht Early Adopters
- Die meisten Funktionen seien in gutem Zustand und die Dokumentation sei nutzbar
- Beiträge sind willkommen; der Guide steht unter CONTRIBUTING, Hinweise zur Codestruktur unter ARCHITECTURE und die Wunschliste für Funktionen unter ROADMAP
1 Kommentare
Meinungen auf Hacker News
Das Release dürfte sich ziemlich unrealistisch anfühlen – Glückwunsch. Die wichtigste Erkenntnis, die ich aus langjähriger Arbeit mit Web-MVC in Rails gezogen habe, ist, dass ein Framework die Business-Logik in der Model-Schicht stark eindämmen sollte.
In Datenmodellen sollte man meiner Meinung nach keine Callbacks im Sinne von Active Record unterbringen, etwa Hooks wie
afterCreate. Wenn sie noch nicht im ORM sind, aber auf der Roadmap stehen, fände ich es besser, sie wegzulassen.Allerdings funktioniert das nur richtig, wenn man eine Service-Schicht, also eine Schicht für Business-Logik, stark empfiehlt. Die meisten meiner Rails-Apps strukturieren Command-/Service-Objekte mit Gems wie Interactor.
Nur mit MVC, und damit nur mit der Standardstruktur von Rails, ist es wegen der fehlenden Service-Schicht meiner Ansicht nach nicht ideal, eine produktionsreife App zu schreiben. Außerdem frage ich mich, warum keine bestehenden ORMs oder Query Builder aus der Community genutzt wurden. Ich habe selbst noch keine einzige Zeile Rust geschrieben, aber es macht mich immer neugieriger.
Services sollten fast immer komplexere, spezifische Code-Stücke sein, die vom Model ausgelöst werden. Dünne Controller, dicke Models waren aus Sicht von Debugging, Strukturierung und Auffindbarkeit für mich am besten; wenn man viele Services nutzt, entsteht meiner Erfahrung nach viel Spaghetti-Code.
Auch aus einer rein objektorientierten Perspektive ist das Model das grundlegende Objekt, das eine Entität definiert.
Usersollte alles wissen, was es über sich selbst wissen muss, und mit anderen Entitäten über Nachrichten kommunizieren.Bei Callbacks stimme ich allerdings zu. Callbacks erzeugen viele merkwürdige Seiteneffekte und machen Debugging wirklich schwer.
Das kann man machen, aber es wird ziemlich schwer nachzuvollziehen, auf welche Weise Entitäten aktualisiert werden können. Denn beliebig viele Prozeduren können sie aktualisieren und jeweils eigene Business-Regeln haben. Dann können die Prozeduren auch direkt in die Datenbank schreiben und man kann das Model ganz weglassen.
Nach über 20 Jahren Entwicklung bin ich aber zu dem Schluss gekommen, dass Kapselung keine Funktion, sondern eine Last ist, und dass Daten von den Operationen getrennt sein sollten, die auf ihnen ausgeführt werden. Das nennt man datenorientiertes Design oder datenorientierte Programmierung, und ich bin nicht der Einzige, der zu diesem Schluss gekommen ist.
Es gibt auch andere Modellierungsansätze, die gut skalieren, und dazu einige Artikel. Von jemandem bei Basecamp gibt es diesen Beitrag: https://dev.37signals.com/vanilla-rails-is-plenty/
Natürlich ist das sehr objektorientiert, was ich selbst auch nicht besonders mag. Das Phoenix-Framework von Elixir verwendet zum Beispiel Contexts, um zusammengehörige Funktionalität zu bündeln; vereinfacht kann man sie als Fassade betrachten.
Wenn man Services mag, kann man sie verwenden, aber zu sagen, MVC sei nicht ausreichend für den Produktionseinsatz, finde ich etwas daneben. Schwere Business-Prozesse über Model-Callbacks abzuwickeln ist keine gute Idee, aber für kleine Aufgaben, die Daten in die richtige Form bringen, sind sie ziemlich in Ordnung.
Vielleicht bin ich da voreingenommen, weil meine Programmiererfahrung mit PHP für Wordpress begann, wo Hooks sehr umfangreich genutzt werden.
Was ich gebaut habe, enthält Tabellenspezifikationen, die sich in SQL-Definitionen umwandeln lassen oder hübsch formatierte HTML-Formulare ausgeben können. Ein separater Controller übernimmt alle DB-Verbindungen und CRUD-Operationen, und es gibt Vorher-/Nachher-Hooks, mit denen man bei Bedarf leicht andere Tabellen und Constraints querverweisen kann.
Es funktioniert ziemlich gut, aber wenn möglich würde ich sofort zu Laravel wechseln.
Bei einem REST-Framework würde ich erwarten, dass es standardmäßig Swagger-/OpenAPI-Definitionen generieren kann. Das ist aus meiner Sicht eines der Killer-Features von FastAPI.
Außerdem verstehe ich nicht ganz, warum ein eigenes ORM entwickelt wurde, statt es mit einem bestehenden ORM wie diesel.rs zu integrieren, und warum eine eigene Template-Sprache gebaut wurde, statt eine der beliebten bestehenden Template-Engines zu wählen.
Abgesehen davon sieht das Projekt wirklich interessant aus, und ich werde es weiter im Auge behalten.
[0] https://diesel.rs/
[1] https://crates.io/categories/template-engine
Insgesamt war es ein guter erster Versuch, Datenbanktypen auf Rust-Typen abzubilden, aber ein ORM sollte flexibler und bequemer zu nutzen sein. Schließlich soll es Abfragen leichter machen, nicht schwerer :)
Bei Templates ist das Erfinden einer eigenen Sprache so etwas wie ein Aufnahmeritus in den Nerd-Club für Leute über 30. Ich habe das Drachenbuch nicht gelesen, hätte den Kurs an der Uni aber unbedingt gern belegt. Für dieselbe Sache gibt es immer mehrere Implementierungen, und es fühlte sich richtig an, diese Implementierung zu verwenden, die sehr nah nachbildet, was ERB in Rails tut.
Aus SRE-Sicht fand ich https://levkk.github.io/rwf/migrating-from-python/ interessant. Einerseits ist es wirklich beeindruckend, dass das umgesetzt wurde, andererseits lässt es einen aus der SRE-Perspektive, die auf Stabilität schaut, innerlich aufschreien.
Wenn das skaliert, sollte das ein Reverse Proxy wie Caddy, Nginx oder Traefik übernehmen.
Einen stabilen WSGI-Server zu schreiben ist möglich und gar nicht so schwer, wenn man ein wenig auf Details wie Thread-Anzahl, Vacuum oder Neustarts nach n Requests wie früher bei php-fpm achtet. Im Grunde ist man auf dem richtigen Weg, wenn man die meisten Optionen implementiert, die uwsgi bietet. Auf der Roadmap steht, Rwf mit einem Gunicorn-Setup vergleichbar zu machen.
Die Namenswahl ist wirklich großartig. Das ist jedenfalls eine Methode, ganz oben in den Suchergebnissen zu landen :P
Im Ernst: gut gemacht. Ein gutes Framework zu bauen ist eine überraschend große Aufgabe, und es ist immer schön zu sehen, wie Leute den Designraum erkunden und neue Ideen ausprobieren.
rwf.RustOnWeb.com kann man auch für 10 $ kaufen :) Nur so dahingesagt.
Mir gefällt die Formulierung „Denk an Rails, nicht an Flask“. Auch statisch typisierte Sprachen brauchen mehr bzw. bessere opinionated Frameworks wie Rails/Django.
Gut gemacht. Es wäre einen Versuch wert, auf https://www.arewewebyet.org/ vorgestellt zu werden.
Schön! Django/Rails in einer erstklassigen Sprache.
Als Vorschlag:
async-traitdürfte inzwischen stabilisiert sein, daher ist das Makro vielleicht nicht mehr nötig, und eine OpenTelemetry-Integration wäre gut, damit man Metriken und Tracing standardmäßig bekommt. Für Linux-Targets wäre jemalloc ebenfalls sinnvoll.Ich werde den Funktionen aus der
tracing-Crate OpenTelemetry-Tags hinzufügen. jemalloc kann man einer binären App hinzufügen, die Rwf verwendet; es muss nicht als Library-Abhängigkeit eingebunden werden.[1] https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-trait...
Ich habe versucht, Rust zu lernen, aber früh aufgegeben. Aus C#, C/C++, Java und Python kommend fühlte sich Rust seltsam an.
Was mich gebrochen hat, war nicht die Seltsamkeit an sich, sondern dass die Geschwindigkeit, eine Aufgabe abzuschließen, im Vergleich zu Sprachen wie C#, Java und Python langsamer war.
Heutzutage mache ich hauptsächlich Webentwicklung, daher stehen Bedienbarkeit und Produktivität für mich an erster Stelle. Rust mag bei Durchsatz und Geschwindigkeit etwas besser sein, aber wenn man sich aktuelle Benchmarks von Web-Frameworks ansieht, schneiden C# und Java in dieser Hinsicht ebenfalls ziemlich gut ab.
Ich hoffe, das wird nicht als Kritik an Rust gelesen oder als Versuch, die große Leistung kleinzureden, ein MVC-Framework von Grund auf zu bauen. Es geht eher um „das richtige Werkzeug für die Aufgabe“.
Um die Lernkurve zu überwinden, braucht man eine starke Motivation. Man muss die Sprache verwenden müssen, von den Vorteilen des Typsystems und der Sicherheit angezogen sein oder einen eigenen konkreten Grund haben.
Wenn man diese Motivation nicht hat, ist das völlig in Ordnung. Ich hatte sie anfangs auch nicht, und deshalb kamen meine frühen Lernversuche nicht weit. Wie viele empfinden, braucht man mit Rust länger, um Dinge fertigzustellen, als mit Java oder Python, und man muss im Vorfeld mehr nachdenken. In bestimmten Situationen ist das wertvoll, aber wenn nicht klar ist, warum man diesen Trade-off akzeptieren sollte, gibt es keinen starken Grund weiterzumachen.
Seit ich vor Kurzem den webpack-JS-Loader rausgeworfen und einen 50-mal schnelleren Rust-Loader verwendet habe, bin auch ich von Rust begeistert. Rust bringt große Performance-Gewinne, und derzeit sind C++ und Rust meine Lieblingssprachen.
Für Backend-Arbeit mit Rust habe ich alle üblichen Kandidaten evaluiert und kleine Prototypen gebaut. Bisher stimme ich, wenn auch widerwillig, dem kollektiven Fazit zu, dass Axum insgesamt die beste Wahl ist, auch wenn es niedriger angesiedelt ist, als ich es gerne hätte.
Die anderen Kandidaten waren Loco und Rocket. Loco war Rails zu ähnlich, und bei Rocket begannen mich die Makros zu stören, nachdem ich mehr Rust geschrieben hatte.
Dieses Framework scheint perfekt zu meinem Kriterium „Batteries included, aber nicht zu stark vorschreibend“ zu passen. Eine gute Ergänzung für das Ökosystem.