iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 24
0

各位好這個系列到目前為止我們用 Rust + Actix + Diesel 的架構已經做了下面的事情,

  • 透過 cli 呼叫 python 的深度學習程式把中文翻譯成英文
  • 撰寫 restful 的 api 給翻譯程式使用
  • 完成 Diesel 的 tutorial
  • 改寫其中 Diesel 的新增文章變成 resful api

那麼我們今天會繼續把 CRUD 的 api 都改寫完成,那就讓我們繼續吧!

Get Posts

那麼首先我們就來改寫原本的這支程式,

src/bin/show_posts.rs

extern crate diesel;
extern crate actixweb;

use actixweb::*;
use self::models::*;
use diesel::prelude::*;

fn main() {
    // establish db connection
    use self::schema::posts::dsl::*;

    let connection = establish_connection();
    let results = posts.filter(published.eq(true))
        .limit(5)
        .load::<Post>(&connection)
        .expect("Error loading posts");

    println!("Displaying {} posts", results.len());
    for post in results {
        println!("{}", post.title);
        println!("----------\n");
        println!("{}", post.body);
    }
}

首先我們在 lib.rs 裡面增讀取的程式,

src/lib.rs

pub fn read_posts(conn: &PgConnection) -> Vec<Post> {
    use self::schema::posts::dsl::*;

    posts.load::<Post>(conn)
        .expect("Error loading posts")
}

接著在主程式添加這個 api

src/main.rs

#[get("/posts")]
fn get_posts() -> Result<HttpResponse> {
    let connection = establish_connection();
    let result = read_posts(&connection);

    Ok(HttpResponse::Ok().json(result))
}

fn main() {
...
    .service(
        web::scope("/api/v1")
            .service(get_posts)
    )
...
}

這樣就完成了來用 postman 測試一下吧,

https://ithelp.ithome.com.tw/upload/images/20191010/20119807ycJJ5bmYOW.png

Patch Post

接著來改寫這支程式,

src/bin/publish_post.rs

extern crate actixweb;
extern crate diesel;

use self::diesel::prelude::*;
use self::actixweb::*;
use self::models::Post;
use std::env::args;

fn main() {
    use actixweb::schema::posts::dsl::{posts, published};

    let id = args().nth(1).expect("publish_post requires a post id")
        .parse::<i32>().expect("Invalid ID");
    let connection = establish_connection();

    let post = diesel::update(posts.find(id))
        .set(published.eq(true))
        .get_result::<Post>(&connection)
        .expect(&format!("Unable to find post {}", id));
    println!("Published post {}", post.title);
}

這支程式只能修改 published 的屬性我們來讓他也可以修改 title 或是 body。

首先我們新增一個 model,

src/models.rs

#[derive(Serialize, Deserialize, Insertable)]
#[table_name="posts"]
pub struct UpdatePost {
    pub title: String,
    pub body: String,
    pub published: bool,
}

然後在 lib 裡面新增一個方法

src/lib.rs

pub fn update_post<'a>(conn: &PgConnection, id: &'a str, data: UpdatePost) -> Post {
    use schema::posts::dsl::{posts, published, body, title};
    let id = id.parse::<i32>().expect("Invalid ID");

    diesel::update(posts.find(id))
        .set(
            (published.eq(data.published), title.eq(data.title), body.eq(data.body))
        )
        .get_result::<Post>(conn)
        .expect(&format!("Unable to find post {}", id))
}

這支程式就可以幫助我們更新 DB,接著寫 api 介面,

src/main.rs

use self::models::{UpdatePost};

#[patch("/post/{id}")]
fn patch_post(params: web::Json<UpdatePost>, id: web::Path<String>) -> Result<HttpResponse> {
    let connection = establish_connection();
    let result = update_post(&connection, &id, params.0);

    Ok(HttpResponse::Ok().json(result))
}

fn main() {
    .service(
        web::scope("/api/v1")
        .service(patch_post)
    )
}

這樣就完成了我們來測試一下,

https://ithelp.ithome.com.tw/upload/images/20191010/20119807QCD10xknZR.png

Delete Post

最後就是刪除了這次要改寫的是這支程式,

src/bin/delete_post.rs

extern crate actixweb;
extern crate diesel;

use self::diesel::prelude::*;
use self::actixweb::*;
use std::env::args;

fn main() {
    use actixweb::schema::posts::dsl::*;

    let target = args().nth(1).expect("Expected a target to match against");
    let pattern = format!("%{}%", target);

    let connection = establish_connection();
    let num_deleted = diesel::delete(posts.filter(title.like(pattern)))
        .execute(&connection)
        .expect("Error deleting posts");

    println!("Deleted {} posts", num_deleted);
}

首先同樣我們先在 lib 裡面處理刪除的邏輯,

src/lib.rs

pub fn delete_post<'a>(conn: &PgConnection, id: &'a str) -> String {
    use schema::posts::dsl::{posts};
    let id = id.parse::<i32>().expect("Invalid ID");

    diesel::delete(posts.find(id))
        .execute(conn)
        .expect("Error deleting posts");

    format!("Post id {} deleted success.", id)
}

把 post id 傳進來之後找到他並且刪除,跟上面在做更新時差不多,最後回傳刪除成功的字串。

接著來定義 api 介面,

src/main.rs

use actix_web::{delete};

#[delete("/post/{id}")]
fn delete_post_handler(id: web::Path<String>) -> Result<HttpResponse> {
    let connection = establish_connection();
    let result = delete_post(&connection, &id);

    Ok(HttpResponse::Ok().json(result))
}

fn main() {
    .service(
        web::scope("/api/v1")
        .service(delete_post_handler)
    )
}

成功的畫面,

https://ithelp.ithome.com.tw/upload/images/20191010/20119807S2LNzL73fh.png

我們的 api 都完成了可以把原本不用的程式砍掉囉,

https://ithelp.ithome.com.tw/upload/images/20191009/201198076s9bnw8AuW.png

完整專案程式看這邊

總結

完成了上面的 api 之後我們發現一件事情就是 DB 的連線必須在每次 api 請求的時候才去建立,而這是非常浪費資源的並且若在真實世界的環境中 api 流量大的時候會造成嚴重的 bottleneck。

因此我們需要一個方式(DB Connection Pool)來替我們管理 DB 的連線問題,幸好 google 一下就發現了 r2d2 這個 library,因此我們就把改寫 DB 連線當成我們明天的主題吧!

最後一樣有問題歡迎發問

/images/emoticon/emoticon07.gif


上一篇
[Day 23] Rust Actix PART4
下一篇
[Day 25] Rust Actix PART6
系列文
WebAssembly + Rust 的前端應用30

尚未有邦友留言

立即登入留言