iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0
Software Development

Rust Web API 從零開始系列 第 8

Day08 - 透過重構了解Rust的模組管理

  • 分享至 

  • xImage
  •  

今天要對程式碼做大量的調整,所以會看到很多重複的程式碼請見諒。

main.rs

到目前為止的程式碼都放在main.rs裡面:

use axum::{http::StatusCode, routing::get, Form, Router};
use serde::Deserialize;

#[tokio::main]
async fn main() {
    // build our application with a single route
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    // run it with hyper on localhost:3000
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

pub async fn health_check() -> StatusCode {
    StatusCode::OK
}

#[derive(Deserialize)]
pub struct NewSubscriber {
    /// Subscriber Email
    pub email: Option<String>,
    /// Subscriber Name
    pub name: Option<String>,
}

pub async fn subscribe(Form(data): Form<NewSubscriber>) -> StatusCode {
    if data.email.is_some() && data.name.is_some() {
        StatusCode::OK
    } else {
        StatusCode::BAD_REQUEST
    }
}

這樣的程式碼有點雜亂無章,而且可以想見在增加更多功能後,整個專案會變得難以管理,不過在昨天我們已經準備好了整合測試,就可以在測試保護的前提下對專案的結構做整理。

lib.rs

首先把Router的部份抽成build方法,啟動Server的部份抽成run方法,這時候main裡面只剩下:

//// main.rs
#[tokio::main]
async fn main() {
    let app = build();
    run(app).await;
}

這一步的目的是為了把運行的入口點與程式碼的核心邏輯拆開來。
接下來建立lib.rs並把所有的東西都移進去:

//// lib.rs
use axum::{Form, Router};
use axum::routing::get;
use serde::Deserialize;
use axum::http::StatusCode;

pub async fn run(app: Router) {
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

pub fn build() -> Router {
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));
    app
}

pub async fn health_check() -> StatusCode {
    StatusCode::OK
}

#[derive(Deserialize)]
pub struct NewSubscriber {
    /// Subscriber Email
    pub email: Option<String>,
    /// Subscriber Name
    pub name: Option<String>,
}

pub async fn subscribe(Form(data): Form<NewSubscriber>) -> StatusCode {
    if data.email.is_some() && data.name.is_some() {
        StatusCode::OK
    } else {
        StatusCode::BAD_REQUEST
    }
}

這時候下達cargo build指令,突然發現編譯不過:

❯ cargo run
   Compiling zero_to_production v0.1.0 (/home/marvinhsu/Documents/test/zero_to_production)
error[E0425]: cannot find function `build` in this scope
 --> src/main.rs:5:15
  |
5 |     let app = build();
  |               ^^^^^ not found in this scope
  |
help: consider importing this function
  |
3 + use zero_to_production::build;
  |

error[E0425]: cannot find function `run` in this scope
 --> src/main.rs:6:5
  |
6 |     run(app).await;
  |     ^^^ not found in this scope
  |
help: consider importing this function
  |
3 + use zero_to_production::run;
  |

For more information about this error, try `rustc --explain E0425`.
error: could not compile `zero_to_production` (bin "zero_to_production") due to 2 previous errors

rust優秀的編譯器告訴你,原來少加了方法的參考,所以再調整一下main.rs

//// main.rs
use zero_to_production::{build, run};

#[tokio::main]
async fn main() {
    let app = build();
    run(app).await;
}

到這邊先暫停一下,我們來了解一下發生什麼事。

Package與Crate

目前的檔案結構如下:

.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── lib.rs
    └── main.rs

我們的zero_to_production專案就是一個套件(Package),套件的名稱被定義在cargo.toml中。crate是rust編譯的最小單位,原則上一個檔案就會是一個crate,一個套件中可以多個執行檔crate(main.rs),但只能有一個函式庫crate(lib.rs),在編譯的時候cargo默認會由執行檔crate開始,細節可以藉由cargo.toml調整(文件)。

Module

目前我們把程式碼一股腦塞在lib.rs中,接下來要進一步模組化,這部份就是依照團隊或個人的偏好。首先我想先把handler拆出來,所以建立一個資料夾並在裡面建立health_check.rssubscription.rs,並把handler函數各自放入,另外也把NewSubscriber這個結構放到subscription.rs,這時候編譯當然不會過。
在我們要使用模組來組織我們的程式碼,rust會尋找mod.rs或是把資料夾作為一個模組。首先在handler資料夾中加入mod.rs

//// mod.rs
mod health_check;
mod subscribe;

pub use health_check::*;
pub use subscribe::*;

在這邊我就組織了handler模組,接下來在lib.rs一開始添加參考就可以順利編譯了

//// lib.rs
mod handler;
use handler::*;

稍微講解一下mod health_check就是把health_check放到handler模組下面,
pub use health_check::*則是把health_check.rs公開的東西暴露出模組,模組還有其他種撰寫方式,只是這個方法可以自行決定模組的公開程度(我這邊使用*只是因為懶而已,實際上應該要仔細思考模組要暴露的部份)。至於lib.rs中只用了use handler::*是因為目前的程式碼都在lib內,實在不需要再對外公開。

小結

目前我們的檔案結構如下:

.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── handler
    │   ├── health_check.rs
    │   ├── mod.rs
    │   └── subscribe.rs
    ├── lib.rs
    └── main.rs

進行到這裡可以跑一下整合測試確保一切正常,明天仍然會繼續重構,我希望可以融入最近讀到模組設計的觀念。


上一篇
Day07 - 用python進行整合測試
下一篇
Day09 - 應用程式模組
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言