Skip to content

Commit 1915951

Browse files
authored
docs(volo/motore): add some content (#1451)
1 parent 89a4d10 commit 1915951

File tree

6 files changed

+812
-2
lines changed

6 files changed

+812
-2
lines changed

content/en/docs/volo/motore/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub trait Service<Cx, Request> {
2828
}
2929
```
3030

31-
## Getting Started
31+
## Features
3232

3333
Using AFIT, we can write asynchronous code in a very concise and readable way.
3434

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
---
2+
title: "Getting Started"
3+
linkTitle: "Getting Started"
4+
weight: 1
5+
keywords: ["Motore", "Volo", "Rust", "Middleware", "Getting Started"]
6+
description: "This document introduces the core concepts of the Motore framework through a complete example to help you quickly understand Motore."
7+
---
8+
## Prepare the Environment
9+
10+
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.
11+
12+
Don't forget to add the dependencies required by the tutorial text to your `Cargo.toml` file:
13+
14+
```toml
15+
[package]
16+
name = "hello-motore"
17+
version = "0.1.0"
18+
edition = "2024"
19+
20+
[dependencies]
21+
motore = { version = "0"}
22+
23+
# Motore depends on tokio, and we also need a tokio runtime to execute the main function
24+
tokio = { version = "1", features = ["full"] }
25+
```
26+
27+
## Tutorial Text
28+
29+
```rust
30+
/*
31+
* Motore: A Tower-inspired asynchronous middleware abstraction library for Rust
32+
*
33+
* Core Concepts:
34+
* 1. `Service`: Represents an asynchronous service (Request -> Response).
35+
* 2. `Layer`: Represents middleware that wraps a `Service` and returns a new `Service`.
36+
* 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.
37+
*/
38+
39+
// -----------------------------------------------------------------------------
40+
// 1. Implement a `Service`
41+
// -----------------------------------------------------------------------------
42+
43+
// First, let's define a context
44+
#[derive(Debug, Clone)]
45+
struct MyContext {
46+
request_id: u32,
47+
processing_steps: u32, // Example: a writable context state
48+
}
49+
50+
use motore::service::Service;
51+
use std::convert::Infallible;
52+
53+
// This is our "string to uppercase" service
54+
struct ToUppercaseService;
55+
56+
// --- Implement the Service trait for ToUppercaseService ---
57+
impl Service<MyContext, String> for ToUppercaseService {
58+
type Response = String;
59+
type Error = Infallible; // Infallible means this service will never fail
60+
61+
async fn call(&self, cx: &mut MyContext, req: String) -> Result<Self::Response, Self::Error> {
62+
// --- Demonstrate modifying &mut Cx ---
63+
cx.processing_steps += 1;
64+
65+
println!("[ToUppercaseService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps);
66+
let res = Ok(req.to_uppercase());
67+
println!("[ToUppercaseService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps);
68+
res
69+
}
70+
}
71+
72+
73+
// -----------------------------------------------------------------------------
74+
// 2. Implement a `Layer`
75+
// -----------------------------------------------------------------------------
76+
77+
// `Layer` (from `motore/src/layer/mod.rs`) is a factory
78+
// that takes an inner service `S` and returns a new, wrapped service `Self::Service`.
79+
/*
80+
pub trait Layer<S> {
81+
/// The new Service type returned after wrapping
82+
type Service;
83+
84+
/// Wraps the inner service S into a new service Self::Service
85+
fn layer(self, inner: S) -> Self::Service;
86+
}
87+
*/
88+
89+
// --- Implement a `Layer` (logging middleware) ---
90+
91+
// This is the standard pattern for a Layer: "two structs"
92+
// 1. `LogLayer`: The Layer itself (the factory)
93+
#[derive(Clone)]
94+
struct LogLayer {
95+
target: &'static str,
96+
}
97+
98+
// 2. `LogService<S>`: The new Service returned by the Layer (the wrapper)
99+
#[derive(Clone)]
100+
struct LogService<S> {
101+
inner: S, // The inner service
102+
target: &'static str,
103+
}
104+
105+
use motore::layer::Layer;
106+
// Implement the Layer trait for LogLayer
107+
impl<S> Layer<S> for LogLayer {
108+
type Service = LogService<S>;
109+
110+
fn layer(self, inner: S) -> Self::Service {
111+
// Return the new, wrapped Service
112+
LogService {
113+
inner,
114+
target: self.target,
115+
}
116+
}
117+
}
118+
119+
impl<Cx, Req, S> Service<Cx, Req> for LogService<S>
120+
where
121+
// `S` must also be a Service and satisfy constraints like Send/Sync
122+
S: Service<Cx, Req> + Send + Sync,
123+
Cx: Send + 'static,
124+
Req: Send + 'static,
125+
{
126+
// The response and error types are usually the same as the inner service
127+
type Response = S::Response;
128+
type Error = S::Error;
129+
130+
async fn call(&self, cx: &mut Cx, req: Req) -> Result<Self::Response, Self::Error> {
131+
// Execute logic before calling the inner service
132+
println!("[LogLayer] target: {}, enter", self.target);
133+
134+
// Call the inner service
135+
let result = self.inner.call(cx, req).await;
136+
137+
// Execute logic after the inner service returns
138+
match &result {
139+
Ok(_) => println!("[LogLayer] target: {}, exit (Ok)", self.target),
140+
Err(_) => println!("[LogLayer] target: {}, exit (Err)", self.target),
141+
}
142+
143+
result
144+
}
145+
}
146+
147+
// -----------------------------------------------------------------------------
148+
// 3. Extended Knowledge: How `async fn call` works
149+
// -----------------------------------------------------------------------------
150+
151+
// The core `Service` trait in `motore` (defined in motore/src/service/mod.rs)
152+
// is actually defined like this:
153+
/*
154+
pub trait Service<Cx, Request> {
155+
/// The response type returned when the service processes successfully
156+
type Response;
157+
/// The error type returned when the service fails to process
158+
type Error;
159+
160+
/// Core method: process the request and return the response asynchronously
161+
///
162+
/// Note this signature! It is *not* `async fn`.
163+
/// It is a regular function that returns `impl Future`.
164+
/// This syntax is known as "Return Position `impl Trait` in Trait" (RPITIT).
165+
fn call(
166+
&self,
167+
cx: &mut Cx,
168+
req: Request,
169+
) -> impl std::future::Future<Output = Result<Self::Response, Self::Error>> + Send;
170+
}
171+
*/
172+
173+
// You might have noticed:
174+
// Why does the `Service` trait require the signature `fn call(...) -> impl Future`,
175+
// but what we wrote (in ToUppercaseService and LogService) was `async fn call`?
176+
// These two signatures are different, so why does it compile?
177+
178+
// The answer is the `async fn in trait` (AFIT) feature.
179+
180+
// With the AFIT feature, `async fn` in a trait is actually "syntactic sugar"
181+
// for `fn ... -> impl Future`.
182+
183+
// When the Rust compiler sees you trying to implement a trait
184+
// that expects `fn call(...) -> impl Future` with `async fn call`,
185+
// it automatically performs this "syntactic sugar" conversion (the process is called desugaring).
186+
187+
// **In summary:**
188+
// 1. Motore's `Service` trait is defined using RPITIT (`fn ... -> impl Future`).
189+
// 2. Rust's AFIT feature allows us to implement this trait directly using `async fn`.
190+
// 3. When writing services and middleware, we get both the convenience of `async/await` and the zero-cost abstractions of `impl Trait`.
191+
192+
193+
// -----------------------------------------------------------------------------
194+
// 4. Assembling Services and Middleware with `ServiceBuilder`
195+
// -----------------------------------------------------------------------------
196+
197+
// `ServiceBuilder` (from `motore/src/builder.rs`)
198+
// allows you to stack multiple Layers onto a Service.
199+
200+
use motore::builder::ServiceBuilder;
201+
use std::time::Duration;
202+
use motore::timeout::TimeoutLayer; // A Layer that comes with Motore
203+
204+
async fn run_builder() {
205+
// 1. Create a ServiceBuilder
206+
let builder = ServiceBuilder::new()
207+
// 2. Add Layers.
208+
// Request execution order: top to bottom
209+
// Response execution order: bottom to top
210+
.layer(LogLayer { target: "Outer" })
211+
.layer(TimeoutLayer::new(Some(Duration::from_secs(1))))
212+
.layer(LogLayer { target: "Inner" });
213+
214+
// 3. Apply the Layer stack to an "innermost" service
215+
// Here we use `ToUppercaseService` as the core business service
216+
let service = builder.service(ToUppercaseService);
217+
218+
// 4. Prepare the context and request
219+
// Note: processing_steps starts at 0
220+
let mut cx = MyContext { request_id: 42, processing_steps: 0 };
221+
let req = "hello motore".to_string();
222+
223+
// 5. Call it!
224+
let res = service.call(&mut cx, req).await;
225+
226+
println!("\nFinal response: {:?}", res);
227+
228+
/*
229+
* Expected output:
230+
*
231+
* [LogLayer] target: Outer, enter
232+
* [LogLayer] target: Inner, enter
233+
* [ToUppercaseService] handling req id: 42, step: 1 <-- step becomes 1
234+
* [ToUppercaseService] responding req id: 42, step: 1
235+
* [LogLayer] target: Inner, exit (Ok)
236+
* [LogLayer] target: Outer, exit (Ok)
237+
*
238+
* Final response: Ok("HELLO MOTORE")
239+
*/
240+
241+
// Finally, the original cx has been modified
242+
println!("Final context steps: {}", cx.processing_steps); // Will print 1
243+
}
244+
245+
// Fun fact: `ServiceBuilder` also implements the `Layer` trait, which means
246+
// you can nest one `ServiceBuilder` inside another `ServiceBuilder`'s `layer` method:
247+
// --- Suppose we have a bunch of middleware ---
248+
// struct LogLayer;
249+
// struct TimeoutLayer;
250+
// struct AuthLayer;
251+
// struct MetricsLayer;
252+
// struct MyCoreService;
253+
//
254+
// 1. We can create a reusable "authentication" middleware stack
255+
// let auth_stack = ServiceBuilder::new()
256+
// .layer(MetricsLayer)
257+
// .layer(AuthLayer);
258+
//
259+
// 2. Now, `auth_stack` is a `ServiceBuilder<...>`
260+
// Since `ServiceBuilder` implements `Layer`,
261+
// we can use the entire `auth_stack` as a single `Layer`!
262+
//
263+
// 3. Use `auth_stack` in our main `ServiceBuilder`
264+
// let final_service = ServiceBuilder::new()
265+
// .layer(LogLayer)
266+
// .layer(auth_stack) // <-- This step works precisely because `ServiceBuilder` implements `Layer`
267+
// .layer(TimeoutLayer)
268+
// .service(MyCoreService);
269+
270+
// -----------------------------------------------------------------------------
271+
// 5. Helper Utility: `service_fn`
272+
// -----------------------------------------------------------------------------
273+
274+
// Sometimes you don't want to create a new struct for a simple service.
275+
// `motore/src/service/service_fn.rs` provides `service_fn`, which can directly convert a compliant function into a `Service`.
276+
277+
use motore::service::service_fn;
278+
279+
async fn my_handler_func(cx: &mut MyContext, req: String) -> Result<String, Infallible> {
280+
// --- Demonstrate modifying &mut Cx ---
281+
cx.processing_steps += 10;
282+
283+
println!("[service_fn] handling req id: {}, step: {}", cx.request_id, cx.processing_steps);
284+
Ok(req.to_lowercase())
285+
}
286+
287+
288+
#[tokio::main]
289+
async fn main() {
290+
println!("\n--- Example 1: Running `run_builder` ---");
291+
292+
run_builder().await;
293+
294+
println!("\n--- Example 2: Running `service_fn` (standalone) ---");
295+
296+
// `service_fn` can convert a function or closure that matches the `async fn(&mut Cx, Req) -> Result<Res, Err>` signature
297+
// directly into a `Service`.
298+
let fn_service = service_fn(my_handler_func);
299+
300+
// Let's run it to prove it works
301+
let mut cx1 = MyContext { request_id: 101, processing_steps: 0 };
302+
let res1 = fn_service.call(&mut cx1, "HELLO WORLD".to_string()).await;
303+
// Check the modified context
304+
println!("service_fn response: {:?}, context steps: {}", res1, cx1.processing_steps); // prints 10
305+
306+
307+
println!("\n--- Example 3: Running `service_fn` (in a Builder) ---");
308+
309+
// You can also use it in a ServiceBuilder:
310+
let service_from_fn = ServiceBuilder::new()
311+
.layer(LogLayer { target: "ServiceFn" })
312+
.service_fn(my_handler_func); // shorthand for .service(service_fn(my_handler_func))
313+
314+
// Run it
315+
let mut cx2 = MyContext { request_id: 202, processing_steps: 0 };
316+
let res2 = service_from_fn.call(&mut cx2, "ANOTHER EXAMPLE".to_string()).await;
317+
// Check the modified context
318+
println!("service_from_fn response: {:?}, context steps: {}", res2, cx2.processing_steps); // prints 10
319+
}
320+
```
321+
322+
## What you've learned
323+
324+
- Motore's core design: Service, Layer, and the mutable Cx context
325+
- Rust's AFIT and RPITIT features, and their application in Motore
326+
- How to assemble Services and Layers using `ServiceBuilder`
327+
328+
## What's Next?
329+
330+
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.
331+
332+
Next, you can check out some features of Motore that were not covered in this tutorial:
333+
334+
1. The tutorial only mentioned `Service<Cx, Request>`, but Motore also provides some important `Service` variants: for example, [`UnaryService<Request>`](https://deepwiki.com/cloudwego/motore/2.1-service-trait#unaryservice-variant) without a context, and [`BoxService<Cx, T, U, E>`](https://deepwiki.com/cloudwego/motore/2.1-service-trait#type-erasure-with-boxservice) for type erasure.
335+
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<Layer<...>>` (and the [`Either<A, B>` 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.
336+
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.
337+
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.
338+
5. Motore provides a dedicated [`MakeConnection<Address>` Trait](https://deepwiki.com/cloudwego/motore/6-utilities#makeconnection-trait) to abstract the creation of "connections".
339+
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.

0 commit comments

Comments
 (0)