full-stack-rust/slides.md
2018-04-16 12:56:34 +02:00

9.9 KiB

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
    (IMHO)

Beispiel: Rocket

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/hello/<name>/<age>")]
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

#![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

use rocket::response::Failure;
use rocket_contrib::Json;

#[get("/person/<id>", format = "application/json")]
fn get_person(id: i32) -> Result<Json<Person>, 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)

#[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 eingeschossen weil es typesafe ist.

Model definition (Diesel)

  1. Table
table! {
        person (id) {
            id -> Integer,
            name -> Text,
            birthday -> Timestamp,
        }
}
  1. Model
#[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)

#[get("/persons")]
pub fn get_person_list(conn: DbConn)
     -> QueryResult<Json<Vec<Person>>>
{
    person::table
        .order(person::id.asc())
        .load::<Person>(&*conn)
        .map(|person| Json(person))
}

#[get("/person/<id>")]
pub fn get_person(id: i32, conn: DbConn)
    -> Result<Json<Person>, Failure>
{
    person::table
        .find(id)
        .first::<Person>(&*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)

#[post("/person", data="<data>")]
pub fn create_person(data: Json<Person>, conn: DbConn)
    -> Result<Json<Person>, 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::<Person>(&*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

#![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

$ rustup target add wasm32-unknown-unknown --toolchain nightly
$ cargo install wasm-bindgen-cli

Compile

$ cargo +nightly build --target wasm32-unknown-unknown
$ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm \
  --out-dir .

Javascript

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 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/


Quellen

Kontakt