Lý do chúng tôi chọn Rust cho Spin

Vượt xa điều hiển nhiên: Hành trình khám phá Rust

Khi Fermyon bắt tay vào xây dựng Spin, framework mã nguồn mở để phát triển các ứng dụng serverless trên WebAssembly (Wasm), quyết định sử dụng Rust không chỉ là một lựa chọn hợp lý – nó gần như là điều không thể tránh khỏi. Chắc chắn, chúng tôi có thể viện dẫn những đặc điểm nổi bật của Rust như đảm bảo an toàn bộ nhớ mà không cần garbage collection, hiệu suất cực nhanh, hay hệ sinh thái đang phát triển mạnh mẽ. Nhưng đối với chúng tôi tại Fermyon, những lý do này chỉ là khởi đầu của câu chuyện. Chúng tôi đánh giá cao chiều sâu trong triết lý và bộ công cụ của Rust, cũng như sự phù hợp của chúng với mục tiêu của chúng tôi dành cho Spin. Dưới đây là lý do tại sao chúng tôi chọn Rust và cách nó đã giúp chúng tôi định hình Spin trở thành công cụ mạnh mẽ như hình dung.

Khởi đầu từ Wasmtime

Spin hỗ trợ bạn xây dựng và chạy các tác vụ WebAssembly (Wasm). Chúng tôi sử dụng wasmtime làm runtime WebAssembly trong Spin. Bản thân wasmtime được viết bằng Rust, và điều này đã mang lại cho chúng tôi một lợi thế đáng kể ngay từ đầu. Bằng cách chọn Rust cho Spin, chúng tôi có được khả năng tương tác trực tiếp với wasmtime và truy cập vào hệ sinh thái phong phú gồm các thư viện và công cụ của nó. Sự cộng hưởng này cho phép chúng tôi tập trung vào việc tạo ra trải nghiệm và tính năng tuyệt vời cho nhà phát triển của Spin, đồng thời tận dụng sự ổn định và hiệu suất của wasmtime. Nói cách khác, Rust không chỉ bổ sung cho mục tiêu của chúng tôi – nó còn tăng tốc chúng một cách đáng kể.

Trải nghiệm nhà phát triển: Mục tiêu cốt lõi

Tại Fermyon, chúng tôi đặt ra một mục tiêu đầy tham vọng: cung cấp trải nghiệm tốt nhất có thể cho các nhà phát triển làm việc với Spin. Mục tiêu này đã định hướng mọi quyết định chúng tôi đưa ra, từ kiến trúc đến triển khai. Cụ thể, chúng tôi hình dung Spin là một framework mà các nhà phát triển có thể mở rộng và cá nhân hóa một cách dễ dàng. Để đạt được điều này, chúng tôi đã giới thiệu:

Spin Plugins

Tính mô-đun và thiết kế tiện dụng của Rust, kết hợp với crate clap mạnh mẽ, đã cho phép chúng tôi xây dựng giao diện dòng lệnh (CLI) có khả năng mở rộng cao của Spin.

clap cung cấp nền tảng để thiết kế và triển khai CLI của Spin cùng kiến trúc plugin của nó, mang lại trải nghiệm nhà phát triển liền mạch và trực quan. Với Spin Plugins, nhà phát triển có thể mở rộng khả năng của Spin bằng cách tận dụng các plugin do cộng đồng đóng góp hoặc thậm chí tự tạo ra plugin của riêng mình. Đáng chú ý, bản thân các plugin có thể được triển khai bằng nhiều ngôn ngữ lập trình khác nhau, trong đó Rust và Go là những lựa chọn phổ biến nhất tính đến thời điểm hiện tại. Nhìn vào mã nguồn, chúng ta có thể thấy các lệnh con được cung cấp thông qua plugin được thêm vào Spin CLI như thế nào bằng cách sử dụng các API do clap cung cấp:

#[derive(Parser)]
#[clap(name = "spin", version = version())]
enum SpinApp {
    // snip
    #[clap(alias = "n")]
    New(NewCommand),
    #[clap(alias = "b")]
    Build(BuildCommand),
    #[clap(alias = "u")]
    Up(UpCommand),
    #[clap(external_subcommand)]
    External(Vec<String>),
}

async fn _main() -> anyhow::Result<()> {
 // snip
 let mut cmd = SpinApp::command();
 for plugin in &plugin_help_entries {
   let subcmd = clap::Command::new(plugin.display_text())
     .about(plugin.about.as_str())
     .allow_hyphen_values(true)
     .disable_help_flag(true)
     .arg(clap::Arg::new("command")
       .allow_hyphen_values(true)
       .multiple_values(true));
   cmd = cmd.subcommand(subcmd);
  }
  if !plugin_help_entries.is_empty() {
    cmd = cmd.after_help("* implemented via plugin");
  }
  // snip
}

Các Spin Plugin tùy chỉnh cho phép nhà phát triển điều chỉnh Spin theo quy trình làm việc và yêu cầu dự án riêng của họ, thúc đẩy sự đổi mới và khả năng thích ứng.

Spin Templates

Hệ thống kiểu biểu cảm và bộ công cụ của Rust cho phép chúng tôi xây dựng Spin Templates, là các khung dựng sẵn để tạo ứng dụng Spin. Các template này cho phép người dùng khởi tạo ứng dụng mới một cách nhanh chóng và hiệu quả. Việc tùy chỉnh template được hỗ trợ bởi ngôn ngữ template Liquid và triển khai Liquid bằng Rust được cung cấp bởi crate liquid, mang lại sự linh hoạt và dễ sử dụng.

Ngoài ra, chúng tôi tận dụng crate dialoguer để hiển thị giao diện người dùng terminal (TUI) mạnh mẽ và thân thiện, đơn giản hóa quá trình thu thập các tham số template và nâng cao trải nghiệm nhà phát triển tổng thể.

Đoạn mã sau cho thấy cách spin new hỏi người dùng về các tham số template – sử dụng Input hoặc Select được cung cấp bởi dialoguer – khi tạo một ứng dụng Spin mới:

pub(crate) fn prompt_parameter(parameter: &TemplateParameter) -> Option<String> {
    let prompt = parameter.prompt();
    let default_value = parameter.default_value();

    loop {
        let input = match parameter.data_type() {
            TemplateParameterDataType::String(constraints) => match &constraints.allowed_values {
                Some(allowed_values) => ask_choice(prompt, default_value, allowed_values),
                None => ask_free_text(prompt, default_value),
            },
        };

        match input {
            Ok(text) => match parameter.validate_value(text) {
                Ok(text) => return Some(text),
                Err(e) => {
                    println!("Invalid value: {}", e);
                }
            },
            Err(e) => {
                println!("Invalid value: {}", e);
            }
        }
    }
}

fn ask_free_text(prompt: &str, default_value: &Option<String>) -> anyhow::Result<String> {
    let mut input = Input::<String>::new();
    input = input.with_prompt(prompt);
    if let Some(s) = default_value {
        input = input.default(s.to_owned());
    }
    let result = input.interact_text()?;
    Ok(result)
}

fn ask_choice(
    prompt: &str,
    default_value: &Option<String>,
    allowed_values: &[String],
) -> anyhow::Result<String> {
    let mut select = Select::new().with_prompt(prompt).items(allowed_values);
    if let Some(s) = default_value {
        if let Some(default_index) = allowed_values.iter().position(|item| item == s) {
            select = select.default(default_index);
        }
    }
    let selected_index = select.interact()?;
    Ok(allowed_values[selected_index].clone())
}

Hơn nữa, người dùng có thể tự tạo template của riêng mình để nâng cao năng suất hoặc để đảm bảo tuân thủ các yêu cầu quy định cụ thể, điều chỉnh quy trình phát triển theo nhu cầu riêng của họ.

Spin Factors

Spin Factors là một nguyên tắc thiết kế cốt lõi và mẫu trong codebase của Spin. Chúng cho phép người dùng và các tổ chức thêm hoặc tùy chỉnh hành vi của Spin Runtime (Host). Sự linh hoạt này rất quan trọng trong việc điều chỉnh Spin theo nhu cầu và trường hợp sử dụng cụ thể, biến nó không chỉ là một công cụ mà còn là một nền tảng phát triển cùng với người dùng. Bằng cách tận dụng Spin Factors, các nhà phát triển có khả năng định hình chính runtime, trao quyền cho họ đổi mới ở cấp độ cao hơn.

Để minh họa, hãy xem xét một trong các bài kiểm thử đơn vị mà nhóm đã viết cho Spin Factors. Việc triển khai bài kiểm thử sẽ cho bạn thấy sự linh hoạt của Spin khi tạo một runtime tùy chỉnh để chạy các ứng dụng Spin trong môi trường hạn chế và chỉ cung cấp một tập hợp con của các Spin Factors mặc định (trong trường hợp này là key-value store).

#[derive(RuntimeFactors)]
struct TestFactors {
    key_value: KeyValueFactor,
}

impl From<RuntimeConfig> for TestFactorsRuntimeConfig {
    fn from(value: RuntimeConfig) -> Self {
        Self {
            key_value: Some(value),
        }
    }
}

#[tokio::test]
async fn works_when_allowed_store_is_defined() -> anyhow::Result<()> {
    let mut runtime_config = RuntimeConfig::default();
    runtime_config.add_store_manager("default".into(), mock_store_manager());
    let factors = TestFactors {
        key_value: KeyValueFactor::new(),
    };
    let env = TestEnvironment::new(factors).extend_manifest(toml! {
        [component.test-component]
        source = "does-not-exist.wasm"
        key_value_stores = ["default"]
    });
    let mut state = env
        .runtime_config(runtime_config)?
        .build_instance_state()
        .await?;

    assert_eq!(
        state.key_value.allowed_stores(),
        &["default".into()].into_iter().collect::<HashSet<_>>()
    );

    assert!(state.key_value.open("default".to_owned()).await?.is_ok());
    Ok(())
}

Bằng cách kết hợp các tính năng và mẫu này, chúng tôi đã tích hợp khả năng mở rộng trực tiếp vào Spin, trao quyền cho người dùng định hình framework thay vì bị ràng buộc bởi nó.

Quản lý mã nguồn quy mô lớn với Rust

Spin là một dự án quy mô lớn và việc duy trì sự phức tạp của nó mà không mất đi tính đơn giản đòi hỏi kỷ luật. May mắn thay, hệ sinh thái của Rust không chỉ giúp viết mã – nó còn giúp viết mã dễ quản lý, bảo trì và mở rộng. Dưới đây là ba ví dụ cho thấy tại sao Rust là vô giá để xây dựng và quản lý các codebase quy mô lớn:

1. Cargo Workspaces

Cargo Workspaces là một yếu tố thay đổi cuộc chơi để quản lý các dự án lớn. Spin bao gồm nhiều crate phụ thuộc lẫn nhau, và việc sử dụng workspace cho phép chúng tôi tổ chức các crate này thành các đơn vị logic mà không làm giảm hiệu quả. Workspace đặt nền tảng cho việc tăng hiệu suất xây dựng end-to-end, quản lý các dependencies dùng chung, quản lý cấu hình tập trung, đơn giản hóa quá trình phát triển và giảm chi phí hoạt động. Ví dụ, kiến trúc plugin độc đáo của Spin (crates/plugins), Spin Templates (crates/templates), và Spin Factors (crates) được quản lý dưới dạng các crate riêng biệt trong một workspace chung, đảm bảo sự phân tách rõ ràng các mối quan tâm trong khi vẫn duy trì sự gắn kết.

2. Hệ thống kiểu mạnh mẽ của Rust

Hệ thống kiểu mạnh mẽ của Rust, cùng với sự hỗ trợ cho traits và generics, cung cấp một nền tảng mạnh mẽ để xây dựng các codebase có khả năng mở rộng và bảo trì như Spin. Traits cho phép định nghĩa các hành vi chung trên các thành phần đa dạng, đảm bảo tính nhất quán và giảm trùng lặp, trong khi generics cho phép nhà phát triển viết các trừu tượng có thể tái sử dụng cao, vừa an toàn kiểu (type-safe) vừa hiệu quả. Những tính năng này tăng cường đáng kể độ tin cậy và khả năng thích ứng của codebase Spin, đơn giản hóa việc quản lý sự phức tạp của nó khi phát triển. Bằng cách tận dụng hệ thống kiểu biểu cảm của Rust, Spin đạt được mức độ đáng tin cậy và rõ ràng cao hơn, giúp dễ dàng mở rộng và bảo trì mà không ảnh hưởng đến tốc độ hoặc an toàn.

3. Bộ công cụ mạnh mẽ, ổn định và hiệu quả

cargo, rustfmt, clippy, rust-analyzer, và khả năng kiểm thử đơn vị mạnh mẽ của Rust cùng nhau tạo thành một hệ sinh thái mạnh mẽ để quản lý các dự án quy mô lớn như Spin.

cargo sắp xếp hợp lý việc quản lý dependency và xây dựng, trong khi rustfmt đảm bảo định dạng mã nhất quán trên toàn bộ dự án.

clippy cung cấp khả năng linting sâu sắc, hướng dẫn nhà phát triển theo các thực tiễn tốt nhất và ngăn chặn các lỗi lập trình phổ biến.

Trong khi đó, rust-analyzer nâng cao quy trình làm việc phát triển thông qua điều hướng mã và gợi ý thông minh, tăng tốc đáng kể năng suất.

Kết hợp với sự hỗ trợ tự nhiên của Rust cho kiểm thử đơn vị, các công cụ này cùng nhau trao quyền cho nhà phát triển duy trì sự rõ ràng, độ tin cậy và khả năng mở rộng ngay cả trong các codebase phức tạp nhất.

Tại sao Rust lại quan trọng?

Rust không chỉ là một ngôn ngữ lập trình; đó là một tư duy. Sự nhấn mạnh của nó vào an toàn, hiệu suất và khả năng bảo trì cộng hưởng sâu sắc với các mục tiêu của chúng tôi dành cho Spin. Bằng cách chọn Rust, chúng tôi đã có thể cung cấp một bộ công cụ nhanh, đáng tin cậy và có khả năng mở rộng – tất cả mà không ảnh hưởng đến trải nghiệm nhà phát triển.

Rust cho phép chúng tôi phát hành các tính năng mới với tốc độ đáng kinh ngạc. Khi Spin tiếp tục phát triển, chúng tôi vẫn tự tin rằng Rust sẽ mở rộng cùng với chúng tôi, hỗ trợ sứ mệnh định nghĩa lại phát triển serverless của chúng tôi.

Kết luận

Đối với những người đang khám phá Rust hoặc đặt câu hỏi tại sao những người khác lại chọn nó, kinh nghiệm của chúng tôi với Spin đưa ra một trường hợp thuyết phục. Vượt qua các ưu điểm kỹ thuật, Rust cung cấp một nền tảng cho sự sáng tạo và khả năng mở rộng mà ít ngôn ngữ khác có thể sánh kịp. Tại Fermyon, chúng tôi không chỉ chọn Rust vì những gì nó là – chúng tôi chọn nó vì những gì nó trao quyền cho chúng tôi để xây dựng.

Nếu bạn là một nhà phát triển bị thu hút bởi Rust, hãy cân nhắc tìm hiểu sâu vào codebase của Spin. Bạn có thể khám phá ra rằng Rust không chỉ là một công cụ để xây dựng phần mềm – đó là một framework để xây dựng các khả năng.

Chỉ mục