class: center, middle # Full stack Rust ### Hyper Diesel Rust Rocket
Johannes Schriewer aka. Dunkelstern
--- class: center, middle Wenn es fertig ist sieht das ganze so aus... ??? - Ein bisschen rostig für den Charme - Flink - Mit kleinem sidekick - Mit ein bisschen glück kann man auch den Todesstern vernichten. --- class: center, middle Wenn man genau hinschaut ist sogar R2D2 mit an Board: ??? Und tausend anderen Modulen, hello javascript --- class: center, middle In Go sähe es dann so aus: ??? Ok ein bisschen Spaß muss sein... --- # Übersicht 1. Wer bin ich 2. Rust im Backend 3. Rust für Datenbanken 4. Rust im Browser (WASM) --- # Wer bin ich - Backend-Entwickler bei anfema - Normalerweise schreibe ich Python und Node.js - Code seit 2003 in diversen Bereichen - Enttäuscht von Swift -- - Manche sagen ich bin ein irrer Bastler -- - Wenn ich nicht Code bastel ich Elektronikgerümpel --- # Rust im Backend - Es gibt verschiedene Frameworks -- - Iron - Nickel - Conduit - Rocket - Gotham - ... ??? - Iron: Juni 2014, Aktuell kein Maintainer - Nickel: Juni 2014, API ähnlich Express - Conduit: Juni 2014, Wahrscheinlich tot - Rocket: März 2016, Aktive Entwicklung - Gotham: März 2017, Viele Beispiele, Aktive Entwicklung -- - Das Benutzerfreundlichste ist [Rocket](https://rocket.rs) -- (IMHO) --- ## Beispiel: Rocket ```rust #![feature(plugin)] #![plugin(rocket_codegen)] extern crate rocket; #[get("/hello//")] fn hello(name: String, age: u8) -> String { format!("Hello, {} year old named {}!", age, name) } fn main() { rocket::ignite().mount("/", routes![hello]).launch(); } ``` ??? - Wir brauchen Compiler Plugins - ... und den Rocket Code-Generator für weniger Boilerplate - Dann bauen wir uns einen Endpunkt - Python Flask - Return String, `format` ist ein macro - Start mit einer einzelnen route --- ## JSON API Beispiel ### Model ```rust #![feature(custom_derive)] extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; extern crate chrono; use chrono::prelude::*; #[derive(Serialize, Deserialize, Debug)] pub struct Person { pub id: i32, pub name: String, pub birthday: NaiveDateTime, } ``` ??? - Wieder ein neues rust-Feature - Serde: Serializer-Deserializer - Chrono: Date/Time lib - Boilerplate lassen wir wieder generieren - `Person` ist unser Model --- ### View ```rust use rocket::response::Failure; use rocket_contrib::Json; #[get("/person/", format = "application/json")] fn get_person(id: i32) -> Result, Failure> { Ok(Json(Person { id, name: String::from("Max Musterman"), birthday: Local::now().naive_local(), })) } ``` Die anderen HTTP Methoden funktionieren natürlich auch... ??? - JSON-Support laden - Mock-Endpoint - Statische Daten als Beispiel - Reagiert nur auf `Accept: application/json` --- #### Ergebnis
$ curl -v http://localhost:8080/person/1 \ -H 'Accept: application/json' > GET /person/1 HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.59.0 > Accept: application/json > < HTTP/1.1 200 OK < Content-Type: application/json < Server: Rocket < Content-Length: 74 < Date: Sun, 15 Apr 2018 22:32:27 GMT < { "id": 1, "name": "Max Musterman", "birthday":"2018-04-16T00:32:27.480509862" }
??? - `Accept`-Header nicht vergessen sonst 404 - JSON formatiert zur besseren lesbarkeit --- ### Request Guards (aka Middleware) ```rust #[get("/admin")] fn admin_panel(admin: AdminUser) -> &'static str { "Hello, administrator. This is the admin panel!" } #[get("/admin", rank = 2)] fn admin_panel_user(user: User) -> &'static str { "You must be an administrator to access this page." } #[get("/admin", rank = 3)] fn admin_panel_redirect() -> Redirect { Redirect::to("/login") } ``` Rocket wählt automatisch denjenigen View der alle Request guards unterstützt (und den niedrigsten Rank hat) ??? - Middleware als opt-in - Die Parameter-Typen implementieren `FromRequest`-Trait - Nach Rank - Request Guards müssen erfüllt werden sonst 404 - `format` im Decorator funktioniert ähnlich --- # Rust für Datenbanken - Auch hier wieder verschiedene Dinge: -- - Postgres-Lib direkt - Rustorm - Diesel - ... ??? - Postgres-Lib: Nicht direkt typesafe, bzw dynamisch - Langsam - Rustorm: Rewrite in progress, schlechte Doku - Diesel: Dürftige Doku aber es geht voran - Schneller als postgres-lib direkt -- - Ich habe mich auf [Diesel](https://diesel.rs) eingeschossen weil es typesafe ist. --- ## Model definition (Diesel) 1. Table ```rust table! { person (id) { id -> Integer, name -> Text, birthday -> Timestamp, } } ``` 2. Model ```rust #[derive(Serialize, Deserialize, Debug, Queryable, \ Insertable, Identifiable, AsChangeset)] #[table_name = "person"] pub struct Person { ... ``` ??? - Migrations: SQL direkt - `table!` definition lässt sich mit `diesel-cli` automatisieren - Model wie vorher, mehr `derive` - Boilerplate wird wieder generiert --- ## View (Diesel) ```rust #[get("/persons")] pub fn get_person_list(conn: DbConn) -> QueryResult>> { person::table .order(person::id.asc()) .load::(&*conn) .map(|person| Json(person)) } #[get("/person/")] pub fn get_person(id: i32, conn: DbConn) -> Result, Failure> { person::table .find(id) .first::(&*conn) .map_err(|_| Failure(Status::NotFound)) .map(|person| Json(person)) } ``` ??? - Sehr vereinfacht - Kein DB-Connection Error-Handling - `DbConn` kommt als Request-Guard - Kaputte DB -> 404 - `map` konvertiert nur _Success_ - `map_err` konvertiert nur _Error_ --- ## View (continued) ```rust #[post("/person", data="")] pub fn create_person(data: Json, conn: DbConn) -> Result, Failure> { let rows_inserted = insert_into(person::table) .values(&data.into_inner()) .execute(&*conn) .unwrap(); if rows_inserted != 1 { Err(Failure(Status::InternalServerError)) } else { person::table .order(person::id.desc()) .first::(&*conn) .unwrap() .map(|person| Json(person)) } } ``` ??? - Kurzes beispiel für nen DB-Save - Der `DbConn`-Request-Guard erzeugt und committed transaction - Nur deshalb ist der Query-Code safe - Insert and Return geht noch nicht - Serde und Diesel übernehmen das Decoding der `Person` - Kein error handling beim insert: `unwrap()` crashed den worker -> 500 --- # Rust im Browser 1. _wasm-bindgen_ und Webpack, JS module in Rust 2. _yew_, React-style Framework, Everything Rust ??? - Beides nur kurz angerissen - Alles sehr alpha - Aber es funktioniert und wird weiterentwickelt --- ## WebAssembly Module für Javascript ### Rust Teil ```rust #![feature(proc_macro, wasm_custom_section, wasm_import_module)] extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { fn alert(s: &str); } #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } ``` ??? - Wir brauchen `bindgen` und ne Menge **nightly** Kram - Bindgen baut den Typ-Konverter von JS -> WASM - Die Bindgen Macros bauen Typ-Konverter WASM -> Rust - Externe Funktion `alert`, kann aber jede beliebige JS funktion sein - Export von unserer `greet` funktion für JS --- ### Vorbereiten ```bash $ rustup target add wasm32-unknown-unknown --toolchain nightly $ cargo install wasm-bindgen-cli ``` ### Compile ```bash $ cargo +nightly build --target wasm32-unknown-unknown $ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm \ --out-dir . ``` ### Javascript ```js const rust = import("./wasm_greet"); rust.then(m => m.greet("World!")); ``` ??? - Erstmal brauchen wir tooling - Dann compilen wir den Rust-Code - Dann bauen wir den JS Glue-Code - In Production sollte das dann Webpack machen - Am Ende ist es einfach ein JS module --- ## Direkt in Rust rendern: Yew https://github.com/DenisKolodin/yew - JSX Style templates mittels _html!_ macro direkt im Rust code - Application state management mittels Message passing - ReactJS und elm waren die inspiration - Eigener Virtual DOM - Components und Fragments wie bereits bekannt ??? - Nur angerissen was theoretisch geht - Projekt existiert seit Dezember 2017 - TODO-MVC geht aber schon -- - **Very Alpha** ??? - Das ist als Warnung zu verstehen, Ich kenne ja die Javascript leute... --- Dank [crates.io](https://crates.io) findet man aber noch viele Zusatzmodule -- ??? So wird dann aus unserem kleinen X-Wing ein großer Millenium Falcon. Aber trotzdem: > This is the ship that made the Kessel Run in fourteen parsecs? --- Es gibt übrigens noch kein Crate namens _Solo_. C3PO ist leider an den verkehrten Anwendungsfall gebunden, es wäre so schön gewesen wenn das ein I18N Crate wäre. --- class: center, middle # Danke für's Zuhören Kommt gerne auf mich zu wenn ihr mehr wissen wollt! [Are we web yet? http://www.arewewebyet.org/](http://www.arewewebyet.org/) --- # Quellen - [Rocket documentation / Getting started guide](https://rocket.rs/guide/) - [Diesel documentation](http://diesel.rs/guides/getting-started/) - [Mozilla Hacks Blog](https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/) - [Wookiepedia](http://starwars.wikia.com/wiki/Main_Page) # Kontakt - hallo@dunkelstern.de - Twitter: @dunkelstern - Blog: blog.dunkelstern.de