521 lines
9.9 KiB
Markdown
521 lines
9.9 KiB
Markdown
class: center, middle
|
|
|
|
# Full stack Rust
|
|
|
|
### Hyper Diesel Rust Rocket
|
|
|
|
<div style="font-size: 16px">Johannes Schriewer aka. Dunkelstern</div>
|
|
|
|
---
|
|
class: center, middle
|
|
|
|
<img src="/images/xwing.png" width="100%">
|
|
|
|
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:
|
|
|
|
<img src="/images/r2d2.png" width="100%">
|
|
|
|
???
|
|
|
|
Und tausend anderen Modulen, hello javascript
|
|
|
|
---
|
|
class: center, middle
|
|
|
|
In Go sähe es dann so aus:
|
|
|
|
<img src="/images/tie-fighter.png" width="60%">
|
|
|
|
???
|
|
|
|
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/<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
|
|
|
|
```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/<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
|
|
|
|
<div style="margin-top: -20px; white-space: pre-wrap; font-family: Ubuntu Mono; font-size: 20px;">
|
|
<span style="color: #808000;">$ curl -v http://localhost:8080/person/1 \
|
|
-H 'Accept: application/json'</span>
|
|
<span style="color: #008000;">> GET /person/1 HTTP/1.1
|
|
> Host: localhost:8080
|
|
> User-Agent: curl/7.59.0
|
|
> Accept: application/json
|
|
></span>
|
|
<span style="color: #800000;">< HTTP/1.1 200 OK
|
|
< Content-Type: application/json
|
|
< Server: Rocket
|
|
< Content-Length: 74
|
|
< Date: Sun, 15 Apr 2018 22:32:27 GMT
|
|
<</span>
|
|
<span style="color: #0000c0;">{
|
|
"id": 1,
|
|
"name": "Max Musterman",
|
|
"birthday":"2018-04-16T00:32:27.480509862"
|
|
}</span>
|
|
</div>
|
|
|
|
???
|
|
|
|
- `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<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)
|
|
|
|
```rust
|
|
#[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
|
|
|
|
```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
|
|
|
|
--
|
|
<img src="/images/falcon.png" width="100%">
|
|
|
|
???
|
|
|
|
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
|