From b5c271677f2ba5115608329944a5cb255d6dd23c Mon Sep 17 00:00:00 2001 From: voyage200 Date: Mon, 27 Oct 2025 23:11:19 +0800 Subject: [PATCH 1/4] docs(volo/motore): add some content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change "Getting Started" to "Features" in English documentation - Change "快速上手" to "特色介绍" in Chinese documentation - Add new pages --- content/en/docs/volo/motore/_index.md | 2 +- .../en/docs/volo/motore/getting_started.md | 377 +++++++++++++++++ .../volo/motore/motore_volo_relationship.md | 66 +++ content/zh/docs/volo/motore/_index.md | 2 +- .../zh/docs/volo/motore/getting_started.md | 381 ++++++++++++++++++ .../volo/motore/motore_volo_relationship.md | 66 +++ 6 files changed, 892 insertions(+), 2 deletions(-) create mode 100644 content/en/docs/volo/motore/getting_started.md create mode 100644 content/en/docs/volo/motore/motore_volo_relationship.md create mode 100644 content/zh/docs/volo/motore/getting_started.md create mode 100644 content/zh/docs/volo/motore/motore_volo_relationship.md diff --git a/content/en/docs/volo/motore/_index.md b/content/en/docs/volo/motore/_index.md index 0d02b312658..e46aa764f71 100644 --- a/content/en/docs/volo/motore/_index.md +++ b/content/en/docs/volo/motore/_index.md @@ -28,7 +28,7 @@ pub trait Service { } ``` -## Getting Started +## Features Using AFIT, we can write asynchronous code in a very concise and readable way. diff --git a/content/en/docs/volo/motore/getting_started.md b/content/en/docs/volo/motore/getting_started.md new file mode 100644 index 00000000000..c7de60a9f6e --- /dev/null +++ b/content/en/docs/volo/motore/getting_started.md @@ -0,0 +1,377 @@ +--- +title: "Getting Started" +linkTitle: "Getting Started" +weight: 1 +keywords: ["Motore", "Volo", "Rust", "Middleware", "Getting Started"] +description: "This document introduces the core concepts of the Motore framework through a complete example to help you quickly understand Motore." +--- + +```rust +/* + * Motore: A Tower-inspired asynchronous middleware abstraction library for Rust + * + * Core Concepts: + * 1. `Service`: Represents an asynchronous service (Request -> Response). + * 2. `Layer`: Represents middleware that wraps a `Service` and returns a new `Service`. + * 3. `Cx`: A mutable context that is passed through the entire call chain. You can use it to pass data throughout the call chain (such as database connections, tracing spans, user information, etc.). This is a major feature of Motore and one of the main differences from Tower. + */ + +// ----------------------------------------------------------------------------- +// 1. Core Abstraction: `Service` Trait (Recommended: Use the macro) +// ----------------------------------------------------------------------------- + +// The core of `motore` is the `Service` trait (defined in motore/src/service/mod.rs). +// It represents a service that receives a `Cx` context and a `Request`, and asynchronously returns a `Response`. + +// `motore-macros/src/lib.rs` provides the `#[motore::service]` macro, +// which is our recommended and most convenient way to implement the `Service` trait. +use motore::service; +use motore::service::Service; + +// We define a context +#[derive(Debug, Clone)] +struct MyContext { + request_id: u32, + processing_steps: u32, // Example: a writable context state +} + +struct MyMacroService; + +// --- Implement `Service` using the `#[service]` macro --- +#[service] +impl Service for MyMacroService { + async fn call(&self, cx: &mut MyContext, req: String) -> Result { + // --- Demonstrate modifying &mut Cx --- + cx.processing_steps += 1; + + println!("[MacroService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + let res = Ok(req.to_uppercase()); + println!("[MacroService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); + res + } +} + + +// ----------------------------------------------------------------------------- +// 2. Deeper Dive: The `Service` Trait +// ----------------------------------------------------------------------------- + +// In fact, behind the scenes, the `#[service]` macro: +// - Automatically infers from `Result`: +// - `type Response = String;` +// - `type Error = Infallible;` +// - Automatically converts `async fn call` to the `fn call(...) -> impl Future` signature required by the trait +// - Automatically wraps the function body in an `async move { ... }` block + +// Finally, the macro transforms the Service you just implemented into the real core `Service` trait in `motore/src/service/mod.rs` + +/* +pub trait Service { + /// The response type returned when the service processes successfully + type Response; + /// The error type returned when the service fails to process + type Error; + + /// Core method: process the request and return the response asynchronously + /// Note this signature: it is *not* `async fn call`. + /// It is a regular function that returns `impl Future` (RPITIT style). + fn call( + &self, + cx: &mut Cx, + req: Request, + ) -> impl std::future::Future> + Send; +} +*/ + +// Because it defines `fn call(...) -> impl Future`, +// if you don't use the macro, you have to *manually* match this signature: + +use std::convert::Infallible; +use std::future::Future; + +// This is our "business logic" service +struct MyManualService; + +// --- Manually implement `Service` without the macro --- +// +// This is very tedious. You need to: +// 1. Explicitly define `type Response` +// 2. Explicitly define `type Error` +// 3. Write the correct `fn call(...) -> impl Future` signature +// 4. Return an `async move { ... }` block inside `call` +// +// This is exactly what the `#[service]` macro does for you automatically! +impl Service for MyManualService { + type Response = String; + type Error = Infallible; // Infallible means this service will never fail + + // Manually implement `call` + fn call( + &self, + cx: &mut MyContext, + req: String, + ) -> impl Future> + Send { + // In this example, we only read the context, not modify it + println!("[ManualService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + + // You must return something that implements Future, usually an async block + async move { + let res = Ok(req.to_uppercase()); + println!("[ManualService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); + res + } + } +} + +// Conclusion: The macro greatly simplifies the implementation of Service, allowing you to focus on the `async fn` business logic instead of the `impl Future` trait signature boilerplate. + + +// ----------------------------------------------------------------------------- +// 3. Middleware: The `Layer` Trait +// ----------------------------------------------------------------------------- + +use motore::layer::Layer; + +// `Layer` (from `motore/src/layer/mod.rs`) is a factory +// that takes an inner service `S` and returns a new, wrapped service `Self::Service`. +/* +pub trait Layer { + /// The new Service type returned after wrapping + type Service; + + /// Wraps the inner service S into a new service Self::Service + fn layer(self, inner: S) -> Self::Service; +} +*/ + +// --- Implement a `Layer` (logging middleware) --- + +// This is the standard pattern for a Layer: "two structs" +// 1. `LogLayer`: The Layer itself (the factory) +#[derive(Clone)] +struct LogLayer { + target: &'static str, +} + +// 2. `LogService`: The new Service returned by the Layer (the wrapper) +#[derive(Clone)] +struct LogService { + inner: S, // The inner service + target: &'static str, +} + +// Implement the `Layer` trait +impl Layer for LogLayer { + type Service = LogService; // Specify the return type + + fn layer(self, inner: S) -> Self::Service { + // Return the new, wrapped Service + LogService { + inner, + target: self.target, + } + } +} + +// --- Manually implement the `Service` trait for `LogService` --- +// +// Again, this is tedious. +impl Service for LogService +where + // `S` must also be a Service and satisfy constraints like Send/Sync + S: Service + Send + Sync, + S::Response: Send, + S::Error: Send, + Cx: Send, // LogService is generic, it doesn't care about the concrete type of Cx + Req: Send, +{ + // The response and error types are usually the same as the inner service + type Response = S::Response; + type Error = S::Error; + + fn call( + &self, + cx: &mut Cx, + req: Req, + ) -> impl Future> + Send { + println!("[LogLayer] (Manual) target: {}, enter", self.target); + + // Must return an async block + async move { + // Execute logic before calling the inner service + + // Call the inner service + let result = self.inner.call(cx, req).await; + + // Execute logic after the inner service returns + match &result { + Ok(_) => println!("[LogLayer] (Manual) target: {}, exit (Ok)", self.target), + Err(_) => println!("[LogLayer] (Manual) target: {}, exit (Err)", self.target), + } + + result + } + } +} + +// ----------------------------------------------------------------------------- +// 4. Implementing the `Service` part of a `Layer` with a macro +// ----------------------------------------------------------------------------- + +// We can also use the macro on the `impl` block for `LogService` +// (Note: the `impl` block for the `Layer` trait remains unchanged, the macro is only for the `Service` trait) + +#[derive(Clone)] +struct LogServiceMacro { + inner: S, + target: &'static str, +} + +// (The `impl Layer` part is omitted, it's the same as above, returning `LogServiceMacro`) + +// --- Implement `LogService` using the macro --- +#[service] +impl Service for LogServiceMacro +where + S: Service + Send + Sync, // Inner service constraints + Cx: Send + 'static, + Req: Send + 'static, +{ + // Again, we just need to write `async fn` + // The macro will automatically infer `Response = S::Response` and `Error = S::Error` + async fn call(&self, cx: &mut Cx, req: Req) -> Result { + println!("[LogLayer] (Macro) target: {}, enter", self.target); + + // The logic is identical, but the code is cleaner + let result = self.inner.call(cx, req).await; + + match &result { + Ok(_) => println!("[LogLayer] (Macro) target: {}, exit (Ok)", self.target), + Err(_) => println!("[LogLayer] (Macro) target: {}, exit (Err)", self.target), + } + + result + } +} + +// ----------------------------------------------------------------------------- +// 5. Composition: `ServiceBuilder` +// ----------------------------------------------------------------------------- + +use motore::builder::ServiceBuilder; +use motore::timeout::TimeoutLayer; // A Layer that comes with Motore (motore/src/timeout.rs) +use std::time::Duration; + +// `ServiceBuilder` (from `motore/src/builder.rs`) +// allows you to compose multiple Layers onto a Service. + +async fn run_builder() { + // 1. Create a ServiceBuilder + let builder = ServiceBuilder::new() + // 2. Add Layers. + // Request execution order: top to bottom + // Response execution order: bottom to top + .layer(LogLayer { target: "Outer" }) + .layer(TimeoutLayer::new(Some(Duration::from_secs(1)))) // A Layer provided by default in Motore + .layer(LogLayer { target: "Inner" }); + + // 3. Apply the Layer stack to an "innermost" service + // Here we use `MyMacroService` as the core business service + let service = builder.service(MyMacroService); + + // 4. Prepare the context and request + // Note: processing_steps starts at 0 + let mut cx = MyContext { request_id: 42, processing_steps: 0 }; + let req = "hello motore".to_string(); + + // 5. Call it! + let res = service.call(&mut cx, req).await; + + println!("\nFinal response: {:?}", res); + + /* + * Expected output: + * + * [LogLayer] (Manual) target: Outer, enter + * [LogLayer] (Manual) target: Inner, enter + * [MacroService] handling req id: 42, step: 1 <-- step becomes 1 + * [MacroService] responding req id: 42, step: 1 + * [LogLayer] (Manual) target: Inner, exit (Ok) + * [LogLayer] (Manual) target: Outer, exit (Ok) + * + * Final response: Ok("HELLO MOTORE") + */ + + // Finally, the original cx has been modified + println!("Final context steps: {}", cx.processing_steps); // Will print 1 +} + +// ----------------------------------------------------------------------------- +// 6. Helper Utility: `service_fn` +// ----------------------------------------------------------------------------- + +// Sometimes you don't want to create a new struct for a simple service. +// `motore/src/service/service_fn.rs` provides `service_fn`, which can directly convert a compliant function into a `Service`. + +use motore::service::service_fn; + +async fn my_handler_func(cx: &mut MyContext, req: String) -> Result { + // --- Demonstrate modifying &mut Cx --- + cx.processing_steps += 10; + + println!("[service_fn] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + Ok(req.to_lowercase()) +} + + +#[tokio::main] async fn main() { + println!("\n--- Example 1: Running `run_builder` ---"); + + run_builder().await; + + println!("\n--- Example 2: Running `service_fn` (standalone) ---"); + + // `service_fn` can convert a function or closure that matches the `async fn(&mut Cx, Req) -> Result` signature + // directly into a `Service`. + let fn_service = service_fn(my_handler_func); + + // Let's run it to prove it works + let mut cx1 = MyContext { request_id: 101, processing_steps: 0 }; + let res1 = fn_service.call(&mut cx1, "HELLO WORLD".to_string()).await; + // Check the modified context + println!("service_fn response: {:?}, context steps: {}", res1, cx1.processing_steps); // prints 10 + + + println!("\n--- Example 3: Running `service_fn` (in a Builder) ---"); + + // You can also use it in a ServiceBuilder: + let service_from_fn = ServiceBuilder::new() + .layer(LogLayer { target: "ServiceFn" }) + .service_fn(my_handler_func); // shorthand for .service(service_fn(my_handler_func)) + + // Run it + let mut cx2 = MyContext { request_id: 202, processing_steps: 0 }; + let res2 = service_from_fn.call(&mut cx2, "ANOTHER EXAMPLE".to_string()).await; + // Check the modified context + println!("service_from_fn response: {:?}, context steps: {}", res2, cx2.processing_steps); // prints 10 +} +``` + +## What you've learned + +- Motore's core design: Service, Layer, and the mutable Cx context +- The use of `#[motore::service]` +- How to assemble Services and Layers using `ServiceBuilder` + +## What's Next? + +Congratulations on getting this far! We have now learned the basic usage of Motore. We hope it will make your journey in the world of Volo much smoother. + +Next, you can check out some features of Motore that were not covered in this tutorial: + +1. The tutorial only mentioned `Service`, but Motore also provides some important `Service` variants: for example, [`UnaryService`](https://deepwiki.com/cloudwego/motore/2.1-service-trait#unaryservice-variant) without a context, and [`BoxService`](https://deepwiki.com/cloudwego/motore/2.1-service-trait#type-erasure-with-boxservice) for type erasure. +2. Motore provides more advanced tools for Layers and ServiceBuilder: for example, [layer_fn](https://deepwiki.com/cloudwego/motore/4.2-layer-combinators#layerfn-implementation) which is very similar to `service_fn`, [option_layer](https://deepwiki.com/cloudwego/motore/2.3-service-builder#conditional-layer-application) which supports `Option>` (and the [`Either` enum](https://deepwiki.com/cloudwego/motore/6-utilities#either-type) that supports this feature), and [map_err](https://deepwiki.com/cloudwego/motore/2.3-service-builder#convenience-methods-for-common-middleware) as a form of Layer. +3. Through the `ServiceExt` Trait, Motore [provides](https://deepwiki.com/cloudwego/motore/4.1-service-combinators#serviceext-trait-and-combinator-overview) `Future`-like methods for `Service`, allowing you to call `.map_err()` and `.map_response()` on a Service. +4. Bidirectional compatibility with the Tower ecosystem: Motore is not only inspired by `Tower`, but also [provides a complete **bidirectional** adaptation layer](https://deepwiki.com/cloudwego/motore/5.1-tower-integration) for it. +5. Motore provides a dedicated [`MakeConnection
` Trait](https://deepwiki.com/cloudwego/motore/6-utilities#makeconnection-trait) to abstract the creation of "connections". +6. The Motore package [enables the `service_send` feature by default](https://deepwiki.com/cloudwego/motore/1.1-project-structure#feature-flag-configuration). It requires that all `Future`s returned by `Service` satisfy the `Send` constraint. `motore-macros` also checks this feature. If you disable it, Motore can be used in a single-threaded environment (e.g., `tokio::main(flavor = "current_thread")`) without the `Send` constraint. diff --git a/content/en/docs/volo/motore/motore_volo_relationship.md b/content/en/docs/volo/motore/motore_volo_relationship.md new file mode 100644 index 00000000000..5362527c687 --- /dev/null +++ b/content/en/docs/volo/motore/motore_volo_relationship.md @@ -0,0 +1,66 @@ +--- +title: "The Relationship Between Motore and Volo" +linkTitle: "The Relationship Between Motore and Volo" +weight: 2 +keywords: ["Motore", "Volo", "Relationship", "Middleware", "RPC"] +description: "Motore provides the core middleware abstraction for Volo. Volo uses Motore as the foundation of its core middleware abstraction layer, on top of which RPC-related functions and implementations are built." +--- + +Understanding the relationship between them is crucial for in-depth use of Volo, framework extension, or developing custom middleware. + +In simple terms: **Motore defines the two general and core asynchronous middleware abstract interfaces, `Service` and `Layer`, while Volo is the main user of these abstract interfaces and the implementer in specific scenarios (RPC)**. + +Motore is more general and can theoretically be used in any place that requires asynchronous service abstraction. Volo is more specific, focusing on the RPC field and utilizing Motore's abstraction to implement RPC-specific functions. + +You can think of Motore as the "skeleton" of the Volo middleware system. Volo itself injects "flesh and blood" into the "skeleton" (framework-level components and logic required to implement RPC), and users ultimately fill in specific business logic on Volo. + +## Motore: The Core Abstraction Layer + +Motore is an independent Rust crate ([cloudwego/motore](https://github.com/cloudwego/motore)) designed to provide a concise, efficient, and ergonomic set of asynchronous middleware abstractions. It is inspired by the widely used [Tower](https://github.com/tower-rs/tower) library in the industry, but its design utilizes Rust's latest **AFIT (async fn in trait)** and **RPITIT (return position impl trait in trait)** features. + +Motore mainly defines two core Traits: + +1. **`Service`**: + * Represents a functional unit that processes requests and asynchronously returns a `Result`. + * This is the most basic component in Motore and can represent a client, a server, or the middleware itself. + * Its core is the `async fn call(&self, cx: &mut Cx, req: Request) -> Result` method. It receives a context `Cx` and a request `Request`, and asynchronously returns the result. + +2. **`Layer`**: + * Represents a "decorator" or "factory" used to wrap and enhance a `Service`. + * It receives an inner `Service` (generic `S`) and returns a new, wrapped `Service` (`Self::Service`). + * `Layer` itself does not directly handle requests but is used to build and compose `Service` chains (i.e., middleware stacks). + * Its core is the `fn layer(self, inner: S) -> Self::Service` method. + +Motore aims to provide a protocol-agnostic, reusable middleware infrastructure. It focuses on the abstraction itself, rather than a framework for a specific application domain like RPC (as Volo is), but Motore can serve as a foundation for building such frameworks. + +If you are familiar with the concepts of Tower, it will be easier to understand Motore, and you will also appreciate the simplification in writing brought by AFIT/RPITIT. + +Compared to Tower, Motore uses AFIT and RPITIT to simplify the writing of asynchronous middleware and reduce the performance overhead and mental burden caused by type erasure (such as `Box`). + +Motore also provides some auxiliary tools, such as `ServiceBuilder` for chaining multiple `Layer`s, or the more low-level `Stack` for combining two `Layer`s. + +## Volo: An RPC Framework Based on Motore + +Volo is a full-featured RPC framework that supports Thrift and gRPC. Volo **directly depends on and is deeply integrated with Motore** as the foundation of its internal middleware system. + +1. **Dependency and Re-export**: + * Volo directly depends on the `motore` crate in its `Cargo.toml`. + * Volo **re-exports** Motore's core Traits at the entry point of its library (`volo/src/lib.rs`): `pub use motore::{Service, layer, Layer, service};`. When you use `volo::Service` or `volo::Layer` in a Volo project, you are **actually using the Traits from Motore**. + +2. **Specific Implementation**: + * The Volo framework extensively uses the abstractions provided by Motore to build its functionality. For example: + * Load balancing (`LoadBalanceLayer`) is a component that implements Motore's `Layer`. + * Features like timeout control, logging, and metrics collection can be integrated by implementing Motore's `Layer`. + * The RPC service handling logic (Handler) written by the end-user, as well as the client-side calling logic generated by the framework, will be wrapped into a form that conforms to the Motore `Service` interface. + * Volo provides many `Layer` **implementations** that are **specific to the RPC scenario**, such as handling Thrift or gRPC protocol details, service discovery integration, etc. These implementations all follow the `Layer` interface defined by Motore. + +3. **User Interaction**: + * Volo users generally configure and add middleware through the APIs provided by `Client::builder()` or `Server::new()`. These APIs internally use `motore::ServiceBuilder` or the low-level `motore::layer::Stack` to apply the `Layer`s provided by the user to the `Service`. + * If users need to write custom middleware, they also need to implement the `motore::Layer` Trait (or directly implement `motore::Service` to wrap another Service). + +## Why is Motore needed? + +Separating the core abstraction (Motore) from the framework implementation (Volo) brings several benefits: + +1. **Modularity and Reusability**: The abstractions and some basic Layers (like timeout) defined by Motore are generic and can be used by projects other than Volo to write middleware and services. +2. **Separation of Concerns**: Motore focuses on providing stable, efficient, and ergonomic core abstractions. Volo, on the other hand, focuses on the business logic and protocol details of the RPC framework. diff --git a/content/zh/docs/volo/motore/_index.md b/content/zh/docs/volo/motore/_index.md index 3999b4fcc44..c7f809f724a 100644 --- a/content/zh/docs/volo/motore/_index.md +++ b/content/zh/docs/volo/motore/_index.md @@ -28,7 +28,7 @@ pub trait Service { } ``` -## 快速上手 +## 特色介绍 使用 AFIT,我们可以以非常简洁易读的方式编写异步代码: diff --git a/content/zh/docs/volo/motore/getting_started.md b/content/zh/docs/volo/motore/getting_started.md new file mode 100644 index 00000000000..06b3ce8a307 --- /dev/null +++ b/content/zh/docs/volo/motore/getting_started.md @@ -0,0 +1,381 @@ +--- +title: "快速开始" +linkTitle: "快速开始" +weight: 1 +keywords: ["Motore", "Volo", "Rust", "中间件", "快速开始"] +description: "通过一个完整的示例介绍 Motore 框架的核心概念,帮助你快速了解 Motore。" +--- + +```rust +/* + * Motore: 一个受 Tower 启发的 Rust 异步中间件抽象库 + * + * 核心理念: + * 1. `Service`: 代表一个异步服务 (Request -> Response)。 + * 2. `Layer`: 代表中间件,它包装一个 `Service` 并返回一个新的 `Service`。 + * 3. `Cx`: 一个可变的上下文,在整个调用链中传递,你可以使用它在整个调用链中传递数据(比如数据库连接、tracing span、用户信息等),这可是 Motore 的一大特色,也是与 Tower 的主要区别之一。 + */ + +// ----------------------------------------------------------------------------- +// 1. 核心抽象:`Service` Trait (推荐方式:使用宏) +// ----------------------------------------------------------------------------- + +// `motore` 的核心是 `Service` trait (定义于 motore/src/service/mod.rs)。 +// 它代表一个接收 `Cx` 上下文和 `Request`,并异步返回 `Response` 的服务。 + +// `motore-macros/src/lib.rs` 提供了 `#[motore::service]` 宏, +// 这是我们推荐的,实现 `Service` trait 最便捷的方式。 +use motore::service; +use motore::service::Service; + +// 我们定义一个上下文(Context) +#[derive(Debug, Clone)] +struct MyContext { + request_id: u32, + processing_steps: u32, // 示例:一个可写的上下文状态 +} + +struct MyMacroService; + +// --- 使用宏 `#[service]` 实现 `Service` --- +#[service] +impl Service for MyMacroService { + async fn call(&self, cx: &mut MyContext, req: String) -> Result { + // --- 演示对 &mut Cx 的修改 --- + cx.processing_steps += 1; + + println!("[MacroService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + let res = Ok(req.to_uppercase()); + println!("[MacroService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); + res + } +} + + +// ----------------------------------------------------------------------------- +// 2. 深入理解:`Service` Trait +// ----------------------------------------------------------------------------- + +// 其实 `#[service]` 宏在背后, +// - 自动从 `Result` 推断出: +// - `type Response = String;` +// - `type Error = Infallible;` +// - 自动将 `async fn call` 转换为 trait 要求的 `fn call(...) -> impl Future` 签名 +// - 自动将函数体包装在 `async move { ... }` 块中 + +// 最后,宏把你刚才实现的 Service 转换成了 `motore/src/service/mod.rs` 中真正的核心 `Service` trait + +/* +pub trait Service { + /// Service 处理成功时返回的响应类型 + type Response; + /// Service 处理失败时返回的错误类型 + type Error; + + /// 核心方法:处理请求并异步返回响应 + /// 注意这个签名:它 *不* 是 `async fn call`。 + /// 它是一个返回 `impl Future` 的普通函数 (RPITIT 风格)。 + fn call( + &self, + cx: &mut Cx, + req: Request, + ) -> impl std::future::Future> + Send; +} +*/ + +// 因为它定义的是 `fn call(...) -> impl Future`, +// 如果不使用宏的话,你就得 *手动* 匹配这个签名: + +use std::convert::Infallible; +use std::future::Future; + +// 这是我们的“业务逻辑”服务 +struct MyManualService; + +// --- 不使用宏,手动实现 `Service` --- +// +// 这非常繁琐,你需要: +// 1. 明确定义 `type Response` +// 2. 明确定义 `type Error` +// 3. 编写正确的 `fn call(...) -> impl Future` 签名 +// 4. 在 `call` 内部返回一个 `async move { ... }` 块 +// +// 这正是 `#[service]` 宏帮你自动完成的工作! +impl Service for MyManualService { + type Response = String; + type Error = Infallible; // Infallible 表示这个服务永远不会失败 + + // 手动实现 `call` + fn call( + &self, + cx: &mut MyContext, + req: String, + ) -> impl Future> + Send { + // 在本例中,我们只读取上下文,不修改 + println!("[ManualService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + + // 你必须返回一个实现了 Future 的东西,通常是一个 async 块 + async move { + let res = Ok(req.to_uppercase()); + println!("[ManualService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); + res + } + } +} + +// 结论:宏极大地简化了 Service 的实现,让你专注于 `async fn` 业务逻辑。而不是 `impl Future` 的 trait 签名模板。 + + +// ----------------------------------------------------------------------------- +// 3. 中间件:`Layer` Trait +// ----------------------------------------------------------------------------- + +use motore::layer::Layer; + +// `Layer` (来自 `motore/src/layer/mod.rs`) 是一个工厂, +// 它接收一个内部服务 `S` (inner),并返回一个包装后的新服务 `Self::Service`。 +/* +pub trait Layer { + /// 包装后返回的新 Service 类型 + type Service; + + /// 将内部服务 S 包装成新服务 Self::Service + fn layer(self, inner: S) -> Self::Service; +} +*/ + +// --- 实现一个 `Layer` (日志中间件) --- + +// 这是 Layer 的标准模式:"两个 Struct" +// 1. `LogLayer`: Layer 本身 (工厂) +#[derive(Clone)] +struct LogLayer { + target: &'static str, +} + +// 2. `LogService`: Layer 返回的新 Service (包装器) +#[derive(Clone)] +struct LogService { + inner: S, // 内部服务 + target: &'static str, +} + +// 实现 `Layer` trait +impl Layer for LogLayer { + type Service = LogService; // 指定返回类型 + + fn layer(self, inner: S) -> Self::Service { + // 返回包装后的新 Service + LogService { + inner, + target: self.target, + } + } +} + +// --- 手动实现 `LogService` 的 `Service` trait --- +// +// 同样,这很繁琐。 +impl Service for LogService +where + // `S` 必须也是一个 Service,并且满足 Send/Sync 等约束 + S: Service + Send + Sync, + S::Response: Send, + S::Error: Send, + Cx: Send, // LogService 是通用的,它不关心 Cx 的具体类型 + Req: Send, +{ + // 响应和错误类型通常与内部服务相同 + type Response = S::Response; + type Error = S::Error; + + fn call( + &self, + cx: &mut Cx, + req: Req, + ) -> impl Future> + Send { + println!("[LogLayer] (Manual) target: {}, enter", self.target); + + // 必须返回 async 块 + async move { + // 在调用内部服务之前执行逻辑 + + // 调用内部服务 + let result = self.inner.call(cx, req).await; + + // 在内部服务返回之后执行逻辑 + match &result { + Ok(_) => println!("[LogLayer] (Manual) target: {}, exit (Ok)", self.target), + Err(_) => println!("[LogLayer] (Manual) target: {}, exit (Err)", self.target), + } + + result + } + } +} + +// ----------------------------------------------------------------------------- +// 4. 使用宏实现 `Layer` 的 `Service` 部分 +// ----------------------------------------------------------------------------- + +// 我们可以对 `LogService` 的 `impl` 块也使用宏 +// (注意:`Layer` trait 的 `impl` 块保持不变,宏只用于 `Service` trait) + +#[derive(Clone)] +struct LogServiceMacro { + inner: S, + target: &'static str, +} + +// (`impl Layer` 部分省略,和上面一样,它返回 `LogServiceMacro`) + +// --- 使用宏实现 `LogService` --- +#[service] +impl Service for LogServiceMacro +where + S: Service + Send + Sync, // 内部服务约束 + Cx: Send + 'static, + Req: Send + 'static, +{ + // 再次,我们只需要写 `async fn` + // 宏会自动推断 `Response = S::Response` 和 `Error = S::Error` + async fn call(&self, cx: &mut Cx, req: Req) -> Result { + println!("[LogLayer] (Macro) target: {}, enter", self.target); + + // 逻辑完全相同,但代码更清晰 + let result = self.inner.call(cx, req).await; + + match &result { + Ok(_) => println!("[LogLayer] (Macro) target: {}, exit (Ok)", self.target), + Err(_) => println!("[LogLayer] (Macro) target: {}, exit (Err)", self.target), + } + + result + } +} + +// ----------------------------------------------------------------------------- +// 5. 组合:`ServiceBuilder` +// ----------------------------------------------------------------------------- + +use motore::builder::ServiceBuilder; +use motore::timeout::TimeoutLayer; // Motore 自带的 Layer (motore/src/timeout.rs) +use std::time::Duration; + +// `ServiceBuilder` (来自 `motore/src/builder.rs`) +// 允许你将多个 Layer 组合到一个 Service 上。 + +async fn run_builder() { + // 1. 创建一个 ServiceBuilder + let builder = ServiceBuilder::new() + // 2. 添加 Layer。 + // 请求的执行顺序:从上到下 + // 响应的执行顺序:从下到上 + .layer(LogLayer { target: "Outer" }) + .layer(TimeoutLayer::new(Some(Duration::from_secs(1)))) // Motore 默认提供的一个 Layer + .layer(LogLayer { target: "Inner" }); + + // 3. 将 Layer 栈应用到一个“最内部”的服务上 + // 这里我们使用 `MyMacroService` 作为最核心的业务服务 + let service = builder.service(MyMacroService); + + // 4. 准备上下文和请求 + // 注意:processing_steps 从 0 开始 + let mut cx = MyContext { request_id: 42, processing_steps: 0 }; + let req = "hello motore".to_string(); + + // 5. 调用! + let res = service.call(&mut cx, req).await; + + println!("\nFinal response: {:?}", res); + + /* + * 预期输出: + * + * [LogLayer] (Manual) target: Outer, enter + * [LogLayer] (Manual) target: Inner, enter + * [MacroService] handling req id: 42, step: 1 <-- step 变为 1 + * [MacroService] responding req id: 42, step: 1 + * [LogLayer] (Manual) target: Inner, exit (Ok) + * [LogLayer] (Manual) target: Outer, exit (Ok) + * + * Final response: Ok("HELLO MOTORE") + */ + + // 最终,原始的 cx 已经被修改 + println!("Final context steps: {}", cx.processing_steps); // 将打印 1 +} + +// ----------------------------------------------------------------------------- +// 6. 辅助工具:`service_fn` +// ----------------------------------------------------------------------------- + +// 有时候你不想为简单的服务创建一个新 struct。 +// `motore/src/service/service_fn.rs` 提供了 `service_fn`,它能把符合要求的函数直接转换成一个 `Service`。 + +use motore::service::service_fn; + +async fn my_handler_func(cx: &mut MyContext, req: String) -> Result { + // --- 演示对 &mut Cx 的修改 --- + cx.processing_steps += 10; + + println!("[service_fn] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + Ok(req.to_lowercase()) +} + + +#[tokio::main] +async fn main() { + println!("\n--- 示例 1: 运行 `run_builder` ---"); + + run_builder().await; + + println!("\n--- 示例 2: 运行 `service_fn` (独立) ---"); + + // `service_fn` 可以将一个符合 `async fn(&mut Cx, Req) -> Result` 签名 + // 的函数或闭包,直接转换成一个 `Service`。 + let fn_service = service_fn(my_handler_func); + + // 我们也运行一下它,来证明它是工作的 + let mut cx1 = MyContext { request_id: 101, processing_steps: 0 }; + let res1 = fn_service.call(&mut cx1, "HELLO WORLD".to_string()).await; + // 检查修改后的上下文 + println!("service_fn 响应: {:?}, context steps: {}", res1, cx1.processing_steps); // 打印 10 + + + println!("\n--- 示例 3: 运行 `service_fn` (在 Builder 中) ---"); + + // 你也可以在 ServiceBuilder 中使用它: + let service_from_fn = ServiceBuilder::new() + .layer(LogLayer { target: "ServiceFn" }) + .service_fn(my_handler_func); // .service(service_fn(my_handler_func)) 的简写 + + // 运行它 + let mut cx2 = MyContext { request_id: 202, processing_steps: 0 }; + let res2 = service_from_fn.call(&mut cx2, "ANOTHER EXAMPLE".to_string()).await; + // 检查修改后的上下文 + println!("service_from_fn 响应: {:?}, context steps: {}", res2, cx2.processing_steps); // 打印 10 +} +``` + +## 学到了什么 + +- Motore 的核心设计:Service、Layer 和可变的 Cx 上下文 +- #[motore::service] 的用处 +- 使用 ServiceBuilder 把 Service、Layer 组装到一起 + +## What's Next? + +恭喜你,阅读到了这里!至此,我们已经学会 Motore 的基本使用了,祝愿它能让你在 Volo 的世界里遨游时更加如鱼得水~ + +接下来可以看看本教程中没有讲到,但实际上 Motore 有的一些功能: + +1. 教程只提到了 `Service`,但 Motore 还提供了一些重要的 `Service` 变体:例如 没有上下文的 [`UnaryService`](https://deepwiki.com/cloudwego/motore/2.1-service-trait#unaryservice-variant)、用于类型擦除的 [`BoxService`](https://deepwiki.com/cloudwego/motore/2.1-service-trait#type-erasure-with-boxservice)。 +1. Motore 针对 Layer 和 ServiceBuilder 提供了更多高级工具:例如 神似 service_fn 的 [layer_fn](https://deepwiki.com/cloudwego/motore/4.2-layer-combinators#layerfn-implementation) 、支持 `Option>` 的 [option_layer](https://deepwiki.com/cloudwego/motore/2.3-service-builder#conditional-layer-application)(以及支撑这个功能的 [`Either` 枚举](https://deepwiki.com/cloudwego/motore/6-utilities#either-type))、作为 Layer 形式的 [map_err](https://deepwiki.com/cloudwego/motore/2.3-service-builder#convenience-methods-for-common-middleware)。 +1. 通过 `ServiceExt` Trait,Motore 为 `Service` [提供了](https://deepwiki.com/cloudwego/motore/4.1-service-combinators#serviceext-trait-and-combinator-overview) 类似 `Future` 的方法,能让你在 Service 上调用 `.map_err()` `.map_response()`。 + +1. 与 Tower 生态双向兼容: Motore 不仅受 `Tower` 启发,还为其[提供了完整的**双向**适配层](https://deepwiki.com/cloudwego/motore/5.1-tower-integration)。 + +1. Motore 提供了一个专门的 [`MakeConnection
` Trait](https://deepwiki.com/cloudwego/motore/6-utilities#makeconnection-trait) 用于抽象“连接”的创建。 + +1. Motore 包 [默认启用了 `service_send` feature](https://deepwiki.com/cloudwego/motore/1.1-project-structure#feature-flag-configuration)。它要求所有 `Service` 返回的 `Future` 都满足 `Send` 约束。`motore-macros` 也会检查这个 feature。如果禁用它,Motore 可以在单线程环境中使用(例如 `tokio::main(flavor = "current_thread")`),而不需要 `Send` 约束。 diff --git a/content/zh/docs/volo/motore/motore_volo_relationship.md b/content/zh/docs/volo/motore/motore_volo_relationship.md new file mode 100644 index 00000000000..97db6b4af7c --- /dev/null +++ b/content/zh/docs/volo/motore/motore_volo_relationship.md @@ -0,0 +1,66 @@ +--- +title: "Motore 与 Volo 的关系" +linkTitle: "Motore 与 Volo 的关系" +weight: 2 +keywords: ["Motore", "Volo", "关系", "中间件", "RPC"] +description: "Motore 为 Volo 提供了核心的中间件抽象,Volo 使用 Motore 作为其核心中间件抽象层的基础,在此之上构建了 RPC 相关的功能和实现。" +--- + +理解它们之间的关系对于深入使用 Volo、进行框架扩展或开发自定义中间件至关重要。 + +简单来说:**Motore 定义了 `Service` 和 `Layer` 这两个通用的、核心的异步中间件抽象接口,而 Volo 是这些抽象接口的主要使用者和特定场景(RPC)下的实现者**。 + +Motore 更通用,理论上可用于任何需要异步服务抽象的地方。Volo 则更具体地专注于 RPC 领域,利用 Motore 的抽象来实现 RPC 特有的功能。 + +可以将 Motore 视为 Volo 中间件系统的"骨架",Volo 本身为 "骨架" 注入了 "血肉"(实现 RPC 所需的框架层面的组件和逻辑),用户最终在 Volo 上去填充具体的业务逻辑。 + +## Motore: 核心抽象层 + +Motore 是一个独立的 Rust crate ([cloudwego/motore](https://github.com/cloudwego/motore)),其设计目标是提供一套简洁、高效且符合人体工程学的异步中间件抽象。它受到了业界广泛使用的 [Tower](https://github.com/tower-rs/tower) 库的启发,但在设计上利用了 Rust 最新的 **AFIT (async fn in trait)** 和 **RPITIT (return position impl trait in trait)** 特性。 + +Motore 主要定义了两个核心 Trait: + +1. **`Service`**: + * 代表一个处理请求并异步返回 `Result` 的功能单元。 + * 这是 Motore 中最基础的构件,可以表示客户端、服务器或中间件本身。 + * 其核心是 `async fn call(&self, cx: &mut Cx, req: Request) -> Result` 方法。它接收一个上下文 `Cx` 和请求 `Request`,并异步返回结果。 + +2. **`Layer`**: + * 代表一个“装饰器”或“工厂”,用于包装和增强 `Service`。 + * 它接收一个内部 `Service` (泛型 `S`),并返回一个新的、经过包装的 `Service` (`Self::Service`)。 + * `Layer` 本身不直接处理请求,而是用于构建和组合 `Service` 链(即中间件栈)。 + * 其核心是 `fn layer(self, inner: S) -> Self::Service` 方法。 + +Motore 旨在提供一套协议无关的、可重用的中间件基础结构。它专注于抽象本身,而非 Volo 那样的特定应用领域(如 RPC)的框架,但 Motore 可以作为构建这种框架的基础。 + +如果你熟悉 Tower 的概念,那么理解 Motore 会更容易,同时也能体会到 AFIT/RPITIT 带来的写法上的简化。 + +和 Tower 相比,Motore 利用 AFIT 和 RPITIT 简化异步中间件的编写,并减少因类型擦除(如 `Box`)带来的性能开销和心智负担。 + +Motore 还提供了一些辅助工具,如 `ServiceBuilder` 用于链式调用添加多个 `Layer`,或更底层的 `Stack` 用于组合两个 `Layer` 等。 + +## Volo: 基于 Motore 的 RPC 框架 + +Volo 是一个功能完备的 RPC 框架,支持 Thrift 和 gRPC。Volo **直接依赖并深度整合了 Motore** 作为其内部中间件系统的基础。 + +1. **依赖与重导出**: + * Volo 在其 `Cargo.toml` 中直接依赖 `motore` crate。 + * Volo 在其库的入口(`volo/src/lib.rs`)**重新导出**了 Motore 的核心 Trait:`pub use motore::{Service, layer, Layer, service};`。当你在 Volo 项目中使用 `volo::Service` 或 `volo::Layer` 时,你**实际上用的就是 Motore 那边的 Trait**。 + +2. **具体实现**: + * Volo 框架内部大量使用了 Motore 提供的抽象来构建其功能。例如: + * 负载均衡 (`LoadBalanceLayer`) 是一个实现了 Motore `Layer` 的组件。 + * 超时控制、日志记录、指标收集等功能都可以通过实现 Motore `Layer` 来集成。 + * 最终用户编写的 RPC 服务处理逻辑(Handler),以及框架生成的客户端调用逻辑,都会被包装成符合 Motore `Service` 接口的形式。 + * Volo 提供了许多**特定于 RPC 场景**的 `Layer` **实现**,例如处理 Thrift 或 gRPC 协议细节、服务发现集成等,这些实现都遵循 Motore 定义的 `Layer` 接口。 + +3. **用户交互**: + * Volo 用户一般通过 `Client::builder()` 或 `Server::new()` 提供的 API 来配置和添加中间件。这些 API 内部就用到了 `motore::ServiceBuilder`、或底层的 `motore::layer::Stack` 来将用户提供的 `Layer` 套在 `Service` 上。 + * 用户如果需要编写自定义中间件,也需要实现 `motore::Layer` Trait(或者直接实现 `motore::Service` 来包装另一个 Service)。 + +## 为什么需要 Motore? + +将核心抽象(Motore)与框架实现(Volo)分离带来了一些好处: + +1. **模块化与可复用性**: Motore 定义的抽象和一些基础 Layer(如超时)是通用的,可以被 Volo 之外的项目拿来写中间件和服务。 +2. **关注点分离**: Motore 专注于提供稳定、高效、符合人体工程学的核心抽象。Volo 则专注于 RPC 框架的业务逻辑和协议细节。 \ No newline at end of file From 395152744cd060d5aa4972ffc6aac86c80fef45a Mon Sep 17 00:00:00 2001 From: voyage200 Date: Wed, 29 Oct 2025 18:44:40 +0800 Subject: [PATCH 2/4] docs(volo/motore): removed content about service macro, slightly modified the tutorial --- .../en/docs/volo/motore/getting_started.md | 261 ++++++------------ .../zh/docs/volo/motore/getting_started.md | 257 ++++++----------- 2 files changed, 175 insertions(+), 343 deletions(-) diff --git a/content/en/docs/volo/motore/getting_started.md b/content/en/docs/volo/motore/getting_started.md index c7de60a9f6e..20041a93ae7 100644 --- a/content/en/docs/volo/motore/getting_started.md +++ b/content/en/docs/volo/motore/getting_started.md @@ -17,121 +17,43 @@ description: "This document introduces the core concepts of the Motore framework */ // ----------------------------------------------------------------------------- -// 1. Core Abstraction: `Service` Trait (Recommended: Use the macro) +// 1. Implement a `Service` // ----------------------------------------------------------------------------- -// The core of `motore` is the `Service` trait (defined in motore/src/service/mod.rs). -// It represents a service that receives a `Cx` context and a `Request`, and asynchronously returns a `Response`. - -// `motore-macros/src/lib.rs` provides the `#[motore::service]` macro, -// which is our recommended and most convenient way to implement the `Service` trait. -use motore::service; -use motore::service::Service; - -// We define a context +// First, let's define a context #[derive(Debug, Clone)] struct MyContext { request_id: u32, processing_steps: u32, // Example: a writable context state } -struct MyMacroService; +use motore::service::Service; +use std::convert::Infallible; + +// This is our "string to uppercase" service +struct ToUppercaseService; + +// --- Implement the Service trait for ToUppercaseService --- +impl Service for ToUppercaseService { + type Response = String; + type Error = Infallible; // Infallible means this service will never fail -// --- Implement `Service` using the `#[service]` macro --- -#[service] -impl Service for MyMacroService { - async fn call(&self, cx: &mut MyContext, req: String) -> Result { + async fn call(&self, cx: &mut MyContext, req: String) -> Result { // --- Demonstrate modifying &mut Cx --- cx.processing_steps += 1; - println!("[MacroService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + println!("[ToUppercaseService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); let res = Ok(req.to_uppercase()); - println!("[MacroService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); + println!("[ToUppercaseService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); res } } // ----------------------------------------------------------------------------- -// 2. Deeper Dive: The `Service` Trait +// 2. Implement a `Layer` // ----------------------------------------------------------------------------- -// In fact, behind the scenes, the `#[service]` macro: -// - Automatically infers from `Result`: -// - `type Response = String;` -// - `type Error = Infallible;` -// - Automatically converts `async fn call` to the `fn call(...) -> impl Future` signature required by the trait -// - Automatically wraps the function body in an `async move { ... }` block - -// Finally, the macro transforms the Service you just implemented into the real core `Service` trait in `motore/src/service/mod.rs` - -/* -pub trait Service { - /// The response type returned when the service processes successfully - type Response; - /// The error type returned when the service fails to process - type Error; - - /// Core method: process the request and return the response asynchronously - /// Note this signature: it is *not* `async fn call`. - /// It is a regular function that returns `impl Future` (RPITIT style). - fn call( - &self, - cx: &mut Cx, - req: Request, - ) -> impl std::future::Future> + Send; -} -*/ - -// Because it defines `fn call(...) -> impl Future`, -// if you don't use the macro, you have to *manually* match this signature: - -use std::convert::Infallible; -use std::future::Future; - -// This is our "business logic" service -struct MyManualService; - -// --- Manually implement `Service` without the macro --- -// -// This is very tedious. You need to: -// 1. Explicitly define `type Response` -// 2. Explicitly define `type Error` -// 3. Write the correct `fn call(...) -> impl Future` signature -// 4. Return an `async move { ... }` block inside `call` -// -// This is exactly what the `#[service]` macro does for you automatically! -impl Service for MyManualService { - type Response = String; - type Error = Infallible; // Infallible means this service will never fail - - // Manually implement `call` - fn call( - &self, - cx: &mut MyContext, - req: String, - ) -> impl Future> + Send { - // In this example, we only read the context, not modify it - println!("[ManualService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); - - // You must return something that implements Future, usually an async block - async move { - let res = Ok(req.to_uppercase()); - println!("[ManualService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); - res - } - } -} - -// Conclusion: The macro greatly simplifies the implementation of Service, allowing you to focus on the `async fn` business logic instead of the `impl Future` trait signature boilerplate. - - -// ----------------------------------------------------------------------------- -// 3. Middleware: The `Layer` Trait -// ----------------------------------------------------------------------------- - -use motore::layer::Layer; - // `Layer` (from `motore/src/layer/mod.rs`) is a factory // that takes an inner service `S` and returns a new, wrapped service `Self::Service`. /* @@ -160,9 +82,10 @@ struct LogService { target: &'static str, } -// Implement the `Layer` trait +use motore::layer::Layer; +// Implement the Layer trait for LogLayer impl Layer for LogLayer { - type Service = LogService; // Specify the return type + type Service = LogService; fn layer(self, inner: S) -> Self::Service { // Return the new, wrapped Service @@ -173,97 +96,90 @@ impl Layer for LogLayer { } } -// --- Manually implement the `Service` trait for `LogService` --- -// -// Again, this is tedious. impl Service for LogService where // `S` must also be a Service and satisfy constraints like Send/Sync S: Service + Send + Sync, - S::Response: Send, - S::Error: Send, - Cx: Send, // LogService is generic, it doesn't care about the concrete type of Cx - Req: Send, + Cx: Send + 'static, + Req: Send + 'static, { // The response and error types are usually the same as the inner service type Response = S::Response; type Error = S::Error; - fn call( - &self, - cx: &mut Cx, - req: Req, - ) -> impl Future> + Send { - println!("[LogLayer] (Manual) target: {}, enter", self.target); + async fn call(&self, cx: &mut Cx, req: Req) -> Result { + // Execute logic before calling the inner service + println!("[LogLayer] target: {}, enter", self.target); - // Must return an async block - async move { - // Execute logic before calling the inner service - - // Call the inner service - let result = self.inner.call(cx, req).await; - - // Execute logic after the inner service returns - match &result { - Ok(_) => println!("[LogLayer] (Manual) target: {}, exit (Ok)", self.target), - Err(_) => println!("[LogLayer] (Manual) target: {}, exit (Err)", self.target), - } - - result + // Call the inner service + let result = self.inner.call(cx, req).await; + + // Execute logic after the inner service returns + match &result { + Ok(_) => println!("[LogLayer] target: {}, exit (Ok)", self.target), + Err(_) => println!("[LogLayer] target: {}, exit (Err)", self.target), } + + result } } // ----------------------------------------------------------------------------- -// 4. Implementing the `Service` part of a `Layer` with a macro +// 3. Extended Knowledge: How `async fn call` works // ----------------------------------------------------------------------------- -// We can also use the macro on the `impl` block for `LogService` -// (Note: the `impl` block for the `Layer` trait remains unchanged, the macro is only for the `Service` trait) +// The core `Service` trait in `motore` (defined in motore/src/service/mod.rs) +// is actually defined like this: +/* +pub trait Service { + /// The response type returned when the service processes successfully + type Response; + /// The error type returned when the service fails to process + type Error; -#[derive(Clone)] -struct LogServiceMacro { - inner: S, - target: &'static str, + /// Core method: process the request and return the response asynchronously + /// + /// Note this signature! It is *not* `async fn`. + /// It is a regular function that returns `impl Future`. + /// This syntax is known as "Return Position `impl Trait` in Trait" (RPITIT). + fn call( + &self, + cx: &mut Cx, + req: Request, + ) -> impl std::future::Future> + Send; } +*/ -// (The `impl Layer` part is omitted, it's the same as above, returning `LogServiceMacro`) +// You might have noticed: +// Why does the `Service` trait require the signature `fn call(...) -> impl Future`, +// but what we wrote (in ToUppercaseService and LogService) was `async fn call`? +// These two signatures are different, so why does it compile? + +// The answer is the `async fn in trait` (AFIT) feature. + +// With the AFIT feature, `async fn` in a trait is actually "syntactic sugar" +// for `fn ... -> impl Future`. + +// When the Rust compiler sees you trying to implement a trait +// that expects `fn call(...) -> impl Future` with `async fn call`, +// it automatically performs this "syntactic sugar" conversion (the process is called desugaring). + +// **In summary:** +// 1. Motore's `Service` trait is defined using RPITIT (`fn ... -> impl Future`). +// 2. Rust's AFIT feature allows us to implement this trait directly using `async fn`. +// 3. When writing services and middleware, we get both the convenience of `async/await` and the zero-cost abstractions of `impl Trait`. -// --- Implement `LogService` using the macro --- -#[service] -impl Service for LogServiceMacro -where - S: Service + Send + Sync, // Inner service constraints - Cx: Send + 'static, - Req: Send + 'static, -{ - // Again, we just need to write `async fn` - // The macro will automatically infer `Response = S::Response` and `Error = S::Error` - async fn call(&self, cx: &mut Cx, req: Req) -> Result { - println!("[LogLayer] (Macro) target: {}, enter", self.target); - - // The logic is identical, but the code is cleaner - let result = self.inner.call(cx, req).await; - - match &result { - Ok(_) => println!("[LogLayer] (Macro) target: {}, exit (Ok)", self.target), - Err(_) => println!("[LogLayer] (Macro) target: {}, exit (Err)", self.target), - } - - result - } -} // ----------------------------------------------------------------------------- -// 5. Composition: `ServiceBuilder` +// 4. Assembling Services and Middleware with `ServiceBuilder` // ----------------------------------------------------------------------------- +// `ServiceBuilder` (from `motore/src/builder.rs`) +// allows you to stack multiple Layers onto a Service. + use motore::builder::ServiceBuilder; -use motore::timeout::TimeoutLayer; // A Layer that comes with Motore (motore/src/timeout.rs) use std::time::Duration; - -// `ServiceBuilder` (from `motore/src/builder.rs`) -// allows you to compose multiple Layers onto a Service. +use motore::timeout::TimeoutLayer; // A Layer that comes with Motore async fn run_builder() { // 1. Create a ServiceBuilder @@ -272,12 +188,12 @@ async fn run_builder() { // Request execution order: top to bottom // Response execution order: bottom to top .layer(LogLayer { target: "Outer" }) - .layer(TimeoutLayer::new(Some(Duration::from_secs(1)))) // A Layer provided by default in Motore + .layer(TimeoutLayer::new(Some(Duration::from_secs(1)))) .layer(LogLayer { target: "Inner" }); // 3. Apply the Layer stack to an "innermost" service - // Here we use `MyMacroService` as the core business service - let service = builder.service(MyMacroService); + // Here we use `ToUppercaseService` as the core business service + let service = builder.service(ToUppercaseService); // 4. Prepare the context and request // Note: processing_steps starts at 0 @@ -292,12 +208,12 @@ async fn run_builder() { /* * Expected output: * - * [LogLayer] (Manual) target: Outer, enter - * [LogLayer] (Manual) target: Inner, enter - * [MacroService] handling req id: 42, step: 1 <-- step becomes 1 - * [MacroService] responding req id: 42, step: 1 - * [LogLayer] (Manual) target: Inner, exit (Ok) - * [LogLayer] (Manual) target: Outer, exit (Ok) + * [LogLayer] target: Outer, enter + * [LogLayer] target: Inner, enter + * [ToUppercaseService] handling req id: 42, step: 1 <-- step becomes 1 + * [ToUppercaseService] responding req id: 42, step: 1 + * [LogLayer] target: Inner, exit (Ok) + * [LogLayer] target: Outer, exit (Ok) * * Final response: Ok("HELLO MOTORE") */ @@ -307,7 +223,7 @@ async fn run_builder() { } // ----------------------------------------------------------------------------- -// 6. Helper Utility: `service_fn` +// 5. Helper Utility: `service_fn` // ----------------------------------------------------------------------------- // Sometimes you don't want to create a new struct for a simple service. @@ -324,7 +240,8 @@ async fn my_handler_func(cx: &mut MyContext, req: String) -> Result Result for ToUppercaseService { + type Response = String; + type Error = Infallible; // Infallible 表示这个服务永远不会失败 -// --- 使用宏 `#[service]` 实现 `Service` --- -#[service] -impl Service for MyMacroService { - async fn call(&self, cx: &mut MyContext, req: String) -> Result { + async fn call(&self, cx: &mut MyContext, req: String) -> Result { // --- 演示对 &mut Cx 的修改 --- cx.processing_steps += 1; - println!("[MacroService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); + println!("[ToUppercaseService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); let res = Ok(req.to_uppercase()); - println!("[MacroService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); + println!("[ToUppercaseService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); res } } // ----------------------------------------------------------------------------- -// 2. 深入理解:`Service` Trait +// 2. 实现一个 `Layer` // ----------------------------------------------------------------------------- -// 其实 `#[service]` 宏在背后, -// - 自动从 `Result` 推断出: -// - `type Response = String;` -// - `type Error = Infallible;` -// - 自动将 `async fn call` 转换为 trait 要求的 `fn call(...) -> impl Future` 签名 -// - 自动将函数体包装在 `async move { ... }` 块中 - -// 最后,宏把你刚才实现的 Service 转换成了 `motore/src/service/mod.rs` 中真正的核心 `Service` trait - -/* -pub trait Service { - /// Service 处理成功时返回的响应类型 - type Response; - /// Service 处理失败时返回的错误类型 - type Error; - - /// 核心方法:处理请求并异步返回响应 - /// 注意这个签名:它 *不* 是 `async fn call`。 - /// 它是一个返回 `impl Future` 的普通函数 (RPITIT 风格)。 - fn call( - &self, - cx: &mut Cx, - req: Request, - ) -> impl std::future::Future> + Send; -} -*/ - -// 因为它定义的是 `fn call(...) -> impl Future`, -// 如果不使用宏的话,你就得 *手动* 匹配这个签名: - -use std::convert::Infallible; -use std::future::Future; - -// 这是我们的“业务逻辑”服务 -struct MyManualService; - -// --- 不使用宏,手动实现 `Service` --- -// -// 这非常繁琐,你需要: -// 1. 明确定义 `type Response` -// 2. 明确定义 `type Error` -// 3. 编写正确的 `fn call(...) -> impl Future` 签名 -// 4. 在 `call` 内部返回一个 `async move { ... }` 块 -// -// 这正是 `#[service]` 宏帮你自动完成的工作! -impl Service for MyManualService { - type Response = String; - type Error = Infallible; // Infallible 表示这个服务永远不会失败 - - // 手动实现 `call` - fn call( - &self, - cx: &mut MyContext, - req: String, - ) -> impl Future> + Send { - // 在本例中,我们只读取上下文,不修改 - println!("[ManualService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps); - - // 你必须返回一个实现了 Future 的东西,通常是一个 async 块 - async move { - let res = Ok(req.to_uppercase()); - println!("[ManualService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps); - res - } - } -} - -// 结论:宏极大地简化了 Service 的实现,让你专注于 `async fn` 业务逻辑。而不是 `impl Future` 的 trait 签名模板。 - - -// ----------------------------------------------------------------------------- -// 3. 中间件:`Layer` Trait -// ----------------------------------------------------------------------------- - -use motore::layer::Layer; - // `Layer` (来自 `motore/src/layer/mod.rs`) 是一个工厂, // 它接收一个内部服务 `S` (inner),并返回一个包装后的新服务 `Self::Service`。 /* @@ -160,9 +82,10 @@ struct LogService { target: &'static str, } -// 实现 `Layer` trait +use motore::layer::Layer; +// 为 LogLayer 实现 Layer trait impl Layer for LogLayer { - type Service = LogService; // 指定返回类型 + type Service = LogService; fn layer(self, inner: S) -> Self::Service { // 返回包装后的新 Service @@ -173,97 +96,89 @@ impl Layer for LogLayer { } } -// --- 手动实现 `LogService` 的 `Service` trait --- -// -// 同样,这很繁琐。 impl Service for LogService where // `S` 必须也是一个 Service,并且满足 Send/Sync 等约束 S: Service + Send + Sync, - S::Response: Send, - S::Error: Send, - Cx: Send, // LogService 是通用的,它不关心 Cx 的具体类型 - Req: Send, + Cx: Send + 'static, + Req: Send + 'static, { // 响应和错误类型通常与内部服务相同 type Response = S::Response; type Error = S::Error; - fn call( - &self, - cx: &mut Cx, - req: Req, - ) -> impl Future> + Send { - println!("[LogLayer] (Manual) target: {}, enter", self.target); + async fn call(&self, cx: &mut Cx, req: Req) -> Result { + // 在调用内部服务之前执行逻辑 + println!("[LogLayer] target: {}, enter", self.target); - // 必须返回 async 块 - async move { - // 在调用内部服务之前执行逻辑 - - // 调用内部服务 - let result = self.inner.call(cx, req).await; - - // 在内部服务返回之后执行逻辑 - match &result { - Ok(_) => println!("[LogLayer] (Manual) target: {}, exit (Ok)", self.target), - Err(_) => println!("[LogLayer] (Manual) target: {}, exit (Err)", self.target), - } - - result + // 调用内部服务 + let result = self.inner.call(cx, req).await; + + // 在内部服务返回之后执行逻辑 + match &result { + Ok(_) => println!("[LogLayer] target: {}, exit (Ok)", self.target), + Err(_) => println!("[LogLayer] target: {}, exit (Err)", self.target), } + + result } } // ----------------------------------------------------------------------------- -// 4. 使用宏实现 `Layer` 的 `Service` 部分 +// 3. 拓展知识:`async fn call` 是如何工作的 // ----------------------------------------------------------------------------- -// 我们可以对 `LogService` 的 `impl` 块也使用宏 -// (注意:`Layer` trait 的 `impl` 块保持不变,宏只用于 `Service` trait) +// `motore` 的核心 `Service` trait (定义于 motore/src/service/mod.rs) +// 实际上是这样定义的: +/* +pub trait Service { + /// Service 处理成功时返回的响应类型 + type Response; + /// Service 处理失败时返回的错误类型 + type Error; -#[derive(Clone)] -struct LogServiceMacro { - inner: S, - target: &'static str, + /// 核心方法:处理请求并异步返回响应 + /// + /// 注意这个签名!它不是 `async fn`。 + /// 它是一个返回 `impl Future` 的普通函数。 + /// 这种语法被称为 "Return Position `impl Trait` in Trait" (RPITIT)。 + fn call( + &self, + cx: &mut Cx, + req: Request, + ) -> impl std::future::Future> + Send; } +*/ -// (`impl Layer` 部分省略,和上面一样,它返回 `LogServiceMacro`) +// 你可能已经注意到了: +// 为什么 `Service` trait 要求的签名是 `fn call(...) -> impl Future`, +// 而我们自己写的(在 ToUppercaseService 和 LogService 中)却是 `async fn call`? +// 这两个不一样的签名,为什么编译却能通过呢? + +// 答案就是 `async fn in trait` (AFIT) 特性。 + +// AFIT 特性下,trait 中的 `async fn` 其实是 `fn ... -> impl Future` 的 “语法糖”。 + +// 当 Rust 编译器看到你试图用 `async fn call` 来实现一个 +// 期望 `fn call(...) -> impl Future` 的 trait 时, +// 它会自动帮你完成这个“语法糖”的转换(转换过程被称作脱糖 -- desugars)。 + +// **总结一下:** +// 1. Motore 的 `Service` trait 使用 RPITIT (`fn ... -> impl Future`) 来定义。 +// 2. Rust 编译器的 AFIT 特性,允许我们直接使用 `async fn` 来实现这个 trait。 +// 3. 我们在编写服务和中间件时,既能享受 `async/await` 的便利,又能获得 `impl Trait` 带来的零成本抽象。 -// --- 使用宏实现 `LogService` --- -#[service] -impl Service for LogServiceMacro -where - S: Service + Send + Sync, // 内部服务约束 - Cx: Send + 'static, - Req: Send + 'static, -{ - // 再次,我们只需要写 `async fn` - // 宏会自动推断 `Response = S::Response` 和 `Error = S::Error` - async fn call(&self, cx: &mut Cx, req: Req) -> Result { - println!("[LogLayer] (Macro) target: {}, enter", self.target); - - // 逻辑完全相同,但代码更清晰 - let result = self.inner.call(cx, req).await; - - match &result { - Ok(_) => println!("[LogLayer] (Macro) target: {}, exit (Ok)", self.target), - Err(_) => println!("[LogLayer] (Macro) target: {}, exit (Err)", self.target), - } - - result - } -} // ----------------------------------------------------------------------------- -// 5. 组合:`ServiceBuilder` +// 4. 通过 `ServiceBuilder` 把服务和中间件拼装到一块儿 // ----------------------------------------------------------------------------- +// `ServiceBuilder` (来自 `motore/src/builder.rs`) +// 允许你将多个 Layer 叠到一个 Service 上。 + use motore::builder::ServiceBuilder; -use motore::timeout::TimeoutLayer; // Motore 自带的 Layer (motore/src/timeout.rs) use std::time::Duration; - -// `ServiceBuilder` (来自 `motore/src/builder.rs`) -// 允许你将多个 Layer 组合到一个 Service 上。 +use motore::timeout::TimeoutLayer; // Motore 自带的 Layer async fn run_builder() { // 1. 创建一个 ServiceBuilder @@ -272,12 +187,12 @@ async fn run_builder() { // 请求的执行顺序:从上到下 // 响应的执行顺序:从下到上 .layer(LogLayer { target: "Outer" }) - .layer(TimeoutLayer::new(Some(Duration::from_secs(1)))) // Motore 默认提供的一个 Layer + .layer(TimeoutLayer::new(Some(Duration::from_secs(1)))) .layer(LogLayer { target: "Inner" }); // 3. 将 Layer 栈应用到一个“最内部”的服务上 - // 这里我们使用 `MyMacroService` 作为最核心的业务服务 - let service = builder.service(MyMacroService); + // 这里我们使用 `ToUppercaseService` 作为最核心的业务服务 + let service = builder.service(ToUppercaseService); // 4. 准备上下文和请求 // 注意:processing_steps 从 0 开始 @@ -292,12 +207,12 @@ async fn run_builder() { /* * 预期输出: * - * [LogLayer] (Manual) target: Outer, enter - * [LogLayer] (Manual) target: Inner, enter - * [MacroService] handling req id: 42, step: 1 <-- step 变为 1 - * [MacroService] responding req id: 42, step: 1 - * [LogLayer] (Manual) target: Inner, exit (Ok) - * [LogLayer] (Manual) target: Outer, exit (Ok) + * [LogLayer] target: Outer, enter + * [LogLayer] target: Inner, enter + * [ToUppercaseService] handling req id: 42, step: 1 <-- step 变为 1 + * [ToUppercaseService] responding req id: 42, step: 1 + * [LogLayer] target: Inner, exit (Ok) + * [LogLayer] target: Outer, exit (Ok) * * Final response: Ok("HELLO MOTORE") */ @@ -307,7 +222,7 @@ async fn run_builder() { } // ----------------------------------------------------------------------------- -// 6. 辅助工具:`service_fn` +// 5. 辅助工具:`service_fn` // ----------------------------------------------------------------------------- // 有时候你不想为简单的服务创建一个新 struct。 @@ -361,7 +276,7 @@ async fn main() { ## 学到了什么 - Motore 的核心设计:Service、Layer 和可变的 Cx 上下文 -- #[motore::service] 的用处 +- Rust 的 AFIT 和 RPITIT 特性,以及他们在 Motore 的应用 - 使用 ServiceBuilder 把 Service、Layer 组装到一起 ## What's Next? From 99d1b2616e002db11b3b0e5dbcfb0066dce42252 Mon Sep 17 00:00:00 2001 From: voyage200 Date: Wed, 29 Oct 2025 19:10:14 +0800 Subject: [PATCH 3/4] docs(volo/motore): add environment setup section to getting started guide --- .../en/docs/volo/motore/getting_started.md | 20 +++++++++++++++++++ .../zh/docs/volo/motore/getting_started.md | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/content/en/docs/volo/motore/getting_started.md b/content/en/docs/volo/motore/getting_started.md index 20041a93ae7..42e0f139fe1 100644 --- a/content/en/docs/volo/motore/getting_started.md +++ b/content/en/docs/volo/motore/getting_started.md @@ -5,6 +5,26 @@ weight: 1 keywords: ["Motore", "Volo", "Rust", "Middleware", "Getting Started"] description: "This document introduces the core concepts of the Motore framework through a complete example to help you quickly understand Motore." --- +## Prepare the Environment + +The tutorial text itself is runnable Rust code. You can read it directly, or you can copy and paste the tutorial text into the `src/main.rs` file of a new cargo package and view it in an IDE. This provides many benefits, such as seeing type hints from rust-analyzer, which enhances the tutorial experience. + +Don't forget to add the dependencies required by the tutorial text to your `Cargo.toml` file: + +```toml +[package] +name = "hello-motore" +version = "0.1.0" +edition = "2024" + +[dependencies] +motore = { version = "0"} + +# Motore depends on tokio, and we also need a tokio runtime to execute the main function +tokio = { version = "1", features = ["full"] } +``` + +## Tutorial Text ```rust /* diff --git a/content/zh/docs/volo/motore/getting_started.md b/content/zh/docs/volo/motore/getting_started.md index 36dee953761..bd73e5db640 100644 --- a/content/zh/docs/volo/motore/getting_started.md +++ b/content/zh/docs/volo/motore/getting_started.md @@ -5,6 +5,25 @@ weight: 1 keywords: ["Motore", "Volo", "Rust", "中间件", "快速开始"] description: "通过一个完整的示例介绍 Motore 框架的核心概念,帮助你快速了解 Motore。" --- +## 准备环境 + +教程正文本身就是能运行的 Rust 代码。你可以直接看,也可以把教程正文复制粘贴到一个新 cargo 包的 `src/main.rs` 中,然后用 IDE 看,这样就有 可以看到 rust-analyzer 的类型提示 等等诸多好处,提升本教程效果。 + +不要忘了在 `Cargo.toml` 文件中加入教程正文所需的依赖: +```toml +[package] +name = "hello-motore" +version = "0.1.0" +edition = "2024" + +[dependencies] +motore = { version = "0"} + +# Motore 依赖 tokio,我们也需要一个 tokio 运行时来执行 main 函数 +tokio = { version = "1", features = ["full"] } +``` + +## 教程正文 ```rust /* From 9ce6efeada7ce065c9329a2593ce662b487257fa Mon Sep 17 00:00:00 2001 From: voyage200 Date: Wed, 29 Oct 2025 23:16:29 +0800 Subject: [PATCH 4/4] docs(volo/motore): add some content and some small fix --- .../en/docs/volo/motore/getting_started.md | 25 ++++++++++++ .../volo/motore/motore_volo_relationship.md | 38 +++++++++---------- .../zh/docs/volo/motore/getting_started.md | 24 ++++++++++++ .../volo/motore/motore_volo_relationship.md | 10 ++--- 4 files changed, 73 insertions(+), 24 deletions(-) diff --git a/content/en/docs/volo/motore/getting_started.md b/content/en/docs/volo/motore/getting_started.md index 42e0f139fe1..117cb86af50 100644 --- a/content/en/docs/volo/motore/getting_started.md +++ b/content/en/docs/volo/motore/getting_started.md @@ -242,6 +242,31 @@ async fn run_builder() { println!("Final context steps: {}", cx.processing_steps); // Will print 1 } +// Fun fact: `ServiceBuilder` also implements the `Layer` trait, which means +// you can nest one `ServiceBuilder` inside another `ServiceBuilder`'s `layer` method: +// --- Suppose we have a bunch of middleware --- +// struct LogLayer; +// struct TimeoutLayer; +// struct AuthLayer; +// struct MetricsLayer; +// struct MyCoreService; +// +// 1. We can create a reusable "authentication" middleware stack +// let auth_stack = ServiceBuilder::new() +// .layer(MetricsLayer) +// .layer(AuthLayer); +// +// 2. Now, `auth_stack` is a `ServiceBuilder<...>` +// Since `ServiceBuilder` implements `Layer`, +// we can use the entire `auth_stack` as a single `Layer`! +// +// 3. Use `auth_stack` in our main `ServiceBuilder` +// let final_service = ServiceBuilder::new() +// .layer(LogLayer) +// .layer(auth_stack) // <-- This step works precisely because `ServiceBuilder` implements `Layer` +// .layer(TimeoutLayer) +// .service(MyCoreService); + // ----------------------------------------------------------------------------- // 5. Helper Utility: `service_fn` // ----------------------------------------------------------------------------- diff --git a/content/en/docs/volo/motore/motore_volo_relationship.md b/content/en/docs/volo/motore/motore_volo_relationship.md index 5362527c687..fc8ba0b0148 100644 --- a/content/en/docs/volo/motore/motore_volo_relationship.md +++ b/content/en/docs/volo/motore/motore_volo_relationship.md @@ -6,9 +6,9 @@ keywords: ["Motore", "Volo", "Relationship", "Middleware", "RPC"] description: "Motore provides the core middleware abstraction for Volo. Volo uses Motore as the foundation of its core middleware abstraction layer, on top of which RPC-related functions and implementations are built." --- -Understanding the relationship between them is crucial for in-depth use of Volo, framework extension, or developing custom middleware. +Understanding the relationship between them is crucial for in-depth use of Volo (such as framework extension, developing custom middleware, etc.). -In simple terms: **Motore defines the two general and core asynchronous middleware abstract interfaces, `Service` and `Layer`, while Volo is the main user of these abstract interfaces and the implementer in specific scenarios (RPC)**. +In simple terms: **Motore defines the two general and core asynchronous middleware abstract interfaces, `Service` and `Layer`, while Volo is the user of these abstract interfaces and the implementer in specific scenarios (RPC)**. Motore is more general and can theoretically be used in any place that requires asynchronous service abstraction. Volo is more specific, focusing on the RPC field and utilizing Motore's abstraction to implement RPC-specific functions. @@ -21,15 +21,15 @@ Motore is an independent Rust crate ([cloudwego/motore](https://github.com/cloud Motore mainly defines two core Traits: 1. **`Service`**: - * Represents a functional unit that processes requests and asynchronously returns a `Result`. - * This is the most basic component in Motore and can represent a client, a server, or the middleware itself. - * Its core is the `async fn call(&self, cx: &mut Cx, req: Request) -> Result` method. It receives a context `Cx` and a request `Request`, and asynchronously returns the result. + * Represents a functional unit that processes requests and asynchronously returns a `Result`. + * This is the most basic component in Motore and can represent a client, a server, or the middleware itself. + * Its core is the `async fn call(&self, cx: &mut Cx, req: Request) -> Result` method. It receives a context `Cx` and a request `Request`, and asynchronously returns the result. 2. **`Layer`**: - * Represents a "decorator" or "factory" used to wrap and enhance a `Service`. - * It receives an inner `Service` (generic `S`) and returns a new, wrapped `Service` (`Self::Service`). - * `Layer` itself does not directly handle requests but is used to build and compose `Service` chains (i.e., middleware stacks). - * Its core is the `fn layer(self, inner: S) -> Self::Service` method. + * Represents a "decorator" or "factory" used to wrap and enhance a `Service`. + * It receives an inner `Service` (generic `S`) and returns a new, wrapped `Service` (`Self::Service`). + * `Layer` itself does not directly handle requests but is used to build and compose `Service` chains (i.e., middleware stacks). + * Its core is the `fn layer(self, inner: S) -> Self::Service` method. Motore aims to provide a protocol-agnostic, reusable middleware infrastructure. It focuses on the abstraction itself, rather than a framework for a specific application domain like RPC (as Volo is), but Motore can serve as a foundation for building such frameworks. @@ -44,23 +44,23 @@ Motore also provides some auxiliary tools, such as `ServiceBuilder` for chaining Volo is a full-featured RPC framework that supports Thrift and gRPC. Volo **directly depends on and is deeply integrated with Motore** as the foundation of its internal middleware system. 1. **Dependency and Re-export**: - * Volo directly depends on the `motore` crate in its `Cargo.toml`. - * Volo **re-exports** Motore's core Traits at the entry point of its library (`volo/src/lib.rs`): `pub use motore::{Service, layer, Layer, service};`. When you use `volo::Service` or `volo::Layer` in a Volo project, you are **actually using the Traits from Motore**. + * Volo directly depends on the `motore` crate in its `Cargo.toml`. + * Volo **re-exports** Motore's core Traits at the entry point of its library (`volo/src/lib.rs`): `pub use motore::{Service, layer, Layer, service};`. When you use `volo::Service` or `volo::Layer` in a Volo project, you are **actually using the Traits from Motore**. 2. **Specific Implementation**: - * The Volo framework extensively uses the abstractions provided by Motore to build its functionality. For example: - * Load balancing (`LoadBalanceLayer`) is a component that implements Motore's `Layer`. - * Features like timeout control, logging, and metrics collection can be integrated by implementing Motore's `Layer`. - * The RPC service handling logic (Handler) written by the end-user, as well as the client-side calling logic generated by the framework, will be wrapped into a form that conforms to the Motore `Service` interface. - * Volo provides many `Layer` **implementations** that are **specific to the RPC scenario**, such as handling Thrift or gRPC protocol details, service discovery integration, etc. These implementations all follow the `Layer` interface defined by Motore. + * The Volo framework extensively uses the abstractions provided by Motore to build its functionality. For example: + * Load balancing (`LoadBalanceLayer`) is a component that implements Motore's `Layer`. + * Features like timeout control, logging, and metrics collection can be integrated by implementing Motore's `Layer`. + * The RPC service handling logic (Handler) written by the end-user, as well as the client-side calling logic generated by the framework, will be wrapped into a form that conforms to the Motore `Service` interface. + * Volo provides many `Layer` **implementations** that are **specific to the RPC scenario**, such as handling Thrift or gRPC protocol details, service discovery integration, etc. These implementations all follow the `Layer` interface defined by Motore. 3. **User Interaction**: - * Volo users generally configure and add middleware through the APIs provided by `Client::builder()` or `Server::new()`. These APIs internally use `motore::ServiceBuilder` or the low-level `motore::layer::Stack` to apply the `Layer`s provided by the user to the `Service`. - * If users need to write custom middleware, they also need to implement the `motore::Layer` Trait (or directly implement `motore::Service` to wrap another Service). + * Volo users generally configure and add middleware through the APIs provided by `Client::builder()` or `Server::new()`. These APIs internally use `motore::ServiceBuilder` or the low-level `motore::layer::Stack` to apply the `Layer`s provided by the user to the `Service`. + * If users need to write custom middleware, they also need to implement the `motore::Layer` Trait (or directly implement `motore::Service` to wrap another Service). ## Why is Motore needed? Separating the core abstraction (Motore) from the framework implementation (Volo) brings several benefits: 1. **Modularity and Reusability**: The abstractions and some basic Layers (like timeout) defined by Motore are generic and can be used by projects other than Volo to write middleware and services. -2. **Separation of Concerns**: Motore focuses on providing stable, efficient, and ergonomic core abstractions. Volo, on the other hand, focuses on the business logic and protocol details of the RPC framework. +2. **Separation of Concerns**: Motore focuses on providing stable, efficient, and ergonomic core abstractions. Volo, on the other hand, focuses on the business logic and protocol details of the RPC framework. \ No newline at end of file diff --git a/content/zh/docs/volo/motore/getting_started.md b/content/zh/docs/volo/motore/getting_started.md index bd73e5db640..a63d33ff96e 100644 --- a/content/zh/docs/volo/motore/getting_started.md +++ b/content/zh/docs/volo/motore/getting_started.md @@ -240,6 +240,30 @@ async fn run_builder() { println!("Final context steps: {}", cx.processing_steps); // 将打印 1 } +// 讲个好玩的,ServiceBuilder 也实现了 Layer trait,所以能把一个 ServiceBuilder 放在另外一个 ServiceBuilder 的 layer 方法里面: +// --- 有一堆中间件 --- +// struct LogLayer; +// struct TimeoutLayer; +// struct AuthLayer; +// struct MetricsLayer; +// struct MyCoreService; +// +// 1. 我们可以创建一个可复用的 "鉴权" 中间件栈 +// let auth_stack = ServiceBuilder::new() +// .layer(MetricsLayer) +// .layer(AuthLayer); +// +// 2. 现在,auth_stack 是一个 ServiceBuilder<...> +// 因为 ServiceBuilder 实现了 Layer, +// 所以我们可以把整个 auth_stack 当作一个 Layer 来使用! +// +// 3. 在我们的主 ServiceBuilder 中使用 auth_stack +// let final_service = ServiceBuilder::new() +// .layer(LogLayer) +// .layer(auth_stack) // <-- 这一步之所以能成功,就是因为 ServiceBuilder 实现了 Layer +// .layer(TimeoutLayer) +// .service(MyCoreService); + // ----------------------------------------------------------------------------- // 5. 辅助工具:`service_fn` // ----------------------------------------------------------------------------- diff --git a/content/zh/docs/volo/motore/motore_volo_relationship.md b/content/zh/docs/volo/motore/motore_volo_relationship.md index 97db6b4af7c..29ca3b4cbf5 100644 --- a/content/zh/docs/volo/motore/motore_volo_relationship.md +++ b/content/zh/docs/volo/motore/motore_volo_relationship.md @@ -6,17 +6,17 @@ keywords: ["Motore", "Volo", "关系", "中间件", "RPC"] description: "Motore 为 Volo 提供了核心的中间件抽象,Volo 使用 Motore 作为其核心中间件抽象层的基础,在此之上构建了 RPC 相关的功能和实现。" --- -理解它们之间的关系对于深入使用 Volo、进行框架扩展或开发自定义中间件至关重要。 +理解它们之间的关系对深入使用 Volo(进行框架扩展、开发自定义中间件 等等)至关重要。 -简单来说:**Motore 定义了 `Service` 和 `Layer` 这两个通用的、核心的异步中间件抽象接口,而 Volo 是这些抽象接口的主要使用者和特定场景(RPC)下的实现者**。 +简单来说:**Motore 定义了 `Service` 和 `Layer` 这两个通用的、核心的异步中间件抽象接口,而 Volo 是这些抽象接口的使用者和特定场景(RPC)下的实现者**。 Motore 更通用,理论上可用于任何需要异步服务抽象的地方。Volo 则更具体地专注于 RPC 领域,利用 Motore 的抽象来实现 RPC 特有的功能。 -可以将 Motore 视为 Volo 中间件系统的"骨架",Volo 本身为 "骨架" 注入了 "血肉"(实现 RPC 所需的框架层面的组件和逻辑),用户最终在 Volo 上去填充具体的业务逻辑。 +可以将 Motore 视为 Volo 中间件系统的 "骨架",Volo 本身为 "骨架" 注入了 "血肉"(实现 RPC 所需的框架层面的组件和逻辑),用户最终在 Volo 上去填充具体的业务逻辑。 ## Motore: 核心抽象层 -Motore 是一个独立的 Rust crate ([cloudwego/motore](https://github.com/cloudwego/motore)),其设计目标是提供一套简洁、高效且符合人体工程学的异步中间件抽象。它受到了业界广泛使用的 [Tower](https://github.com/tower-rs/tower) 库的启发,但在设计上利用了 Rust 最新的 **AFIT (async fn in trait)** 和 **RPITIT (return position impl trait in trait)** 特性。 +Motore 是一个独立的 Rust crate ([cloudwego/motore](https://github.com/cloudwego/motore)),其设计目标是提供一套简洁、高效且符合人体工程学的异步中间件抽象。虽然它受到了业界广泛使用的 [Tower](https://github.com/tower-rs/tower) 库的启发,但在设计上它利用了 Rust 最新的 **AFIT (async fn in trait)** 和 **RPITIT (return position impl trait in trait)** 特性。 Motore 主要定义了两个核心 Trait: @@ -48,7 +48,7 @@ Volo 是一个功能完备的 RPC 框架,支持 Thrift 和 gRPC。Volo **直 * Volo 在其库的入口(`volo/src/lib.rs`)**重新导出**了 Motore 的核心 Trait:`pub use motore::{Service, layer, Layer, service};`。当你在 Volo 项目中使用 `volo::Service` 或 `volo::Layer` 时,你**实际上用的就是 Motore 那边的 Trait**。 2. **具体实现**: - * Volo 框架内部大量使用了 Motore 提供的抽象来构建其功能。例如: + * Volo 框架内部大量使用 Motore 提供的抽象来构建其功能。例如: * 负载均衡 (`LoadBalanceLayer`) 是一个实现了 Motore `Layer` 的组件。 * 超时控制、日志记录、指标收集等功能都可以通过实现 Motore `Layer` 来集成。 * 最终用户编写的 RPC 服务处理逻辑(Handler),以及框架生成的客户端调用逻辑,都会被包装成符合 Motore `Service` 接口的形式。