Skip to content

Commit b5c2716

Browse files
committed
docs(volo/motore): add some content
- Change "Getting Started" to "Features" in English documentation - Change "快速上手" to "特色介绍" in Chinese documentation - Add new pages
1 parent e21606a commit b5c2716

File tree

6 files changed

+892
-2
lines changed

6 files changed

+892
-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: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
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+
9+
```rust
10+
/*
11+
* Motore: A Tower-inspired asynchronous middleware abstraction library for Rust
12+
*
13+
* Core Concepts:
14+
* 1. `Service`: Represents an asynchronous service (Request -> Response).
15+
* 2. `Layer`: Represents middleware that wraps a `Service` and returns a new `Service`.
16+
* 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.
17+
*/
18+
19+
// -----------------------------------------------------------------------------
20+
// 1. Core Abstraction: `Service` Trait (Recommended: Use the macro)
21+
// -----------------------------------------------------------------------------
22+
23+
// The core of `motore` is the `Service` trait (defined in motore/src/service/mod.rs).
24+
// It represents a service that receives a `Cx` context and a `Request`, and asynchronously returns a `Response`.
25+
26+
// `motore-macros/src/lib.rs` provides the `#[motore::service]` macro,
27+
// which is our recommended and most convenient way to implement the `Service` trait.
28+
use motore::service;
29+
use motore::service::Service;
30+
31+
// We define a context
32+
#[derive(Debug, Clone)]
33+
struct MyContext {
34+
request_id: u32,
35+
processing_steps: u32, // Example: a writable context state
36+
}
37+
38+
struct MyMacroService;
39+
40+
// --- Implement `Service` using the `#[service]` macro ---
41+
#[service]
42+
impl Service<MyContext, String> for MyMacroService {
43+
async fn call(&self, cx: &mut MyContext, req: String) -> Result<String, Infallible> {
44+
// --- Demonstrate modifying &mut Cx ---
45+
cx.processing_steps += 1;
46+
47+
println!("[MacroService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps);
48+
let res = Ok(req.to_uppercase());
49+
println!("[MacroService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps);
50+
res
51+
}
52+
}
53+
54+
55+
// -----------------------------------------------------------------------------
56+
// 2. Deeper Dive: The `Service` Trait
57+
// -----------------------------------------------------------------------------
58+
59+
// In fact, behind the scenes, the `#[service]` macro:
60+
// - Automatically infers from `Result<String, Infallible>`:
61+
// - `type Response = String;`
62+
// - `type Error = Infallible;`
63+
// - Automatically converts `async fn call` to the `fn call(...) -> impl Future` signature required by the trait
64+
// - Automatically wraps the function body in an `async move { ... }` block
65+
66+
// Finally, the macro transforms the Service you just implemented into the real core `Service` trait in `motore/src/service/mod.rs`
67+
68+
/*
69+
pub trait Service<Cx, Request> {
70+
/// The response type returned when the service processes successfully
71+
type Response;
72+
/// The error type returned when the service fails to process
73+
type Error;
74+
75+
/// Core method: process the request and return the response asynchronously
76+
/// Note this signature: it is *not* `async fn call`.
77+
/// It is a regular function that returns `impl Future` (RPITIT style).
78+
fn call(
79+
&self,
80+
cx: &mut Cx,
81+
req: Request,
82+
) -> impl std::future::Future<Output = Result<Self::Response, Self::Error>> + Send;
83+
}
84+
*/
85+
86+
// Because it defines `fn call(...) -> impl Future`,
87+
// if you don't use the macro, you have to *manually* match this signature:
88+
89+
use std::convert::Infallible;
90+
use std::future::Future;
91+
92+
// This is our "business logic" service
93+
struct MyManualService;
94+
95+
// --- Manually implement `Service` without the macro ---
96+
//
97+
// This is very tedious. You need to:
98+
// 1. Explicitly define `type Response`
99+
// 2. Explicitly define `type Error`
100+
// 3. Write the correct `fn call(...) -> impl Future` signature
101+
// 4. Return an `async move { ... }` block inside `call`
102+
//
103+
// This is exactly what the `#[service]` macro does for you automatically!
104+
impl Service<MyContext, String> for MyManualService {
105+
type Response = String;
106+
type Error = Infallible; // Infallible means this service will never fail
107+
108+
// Manually implement `call`
109+
fn call(
110+
&self,
111+
cx: &mut MyContext,
112+
req: String,
113+
) -> impl Future<Output = Result<Self::Response, Self::Error>> + Send {
114+
// In this example, we only read the context, not modify it
115+
println!("[ManualService] handling req id: {}, step: {}", cx.request_id, cx.processing_steps);
116+
117+
// You must return something that implements Future, usually an async block
118+
async move {
119+
let res = Ok(req.to_uppercase());
120+
println!("[ManualService] responding req id: {}, step: {}", cx.request_id, cx.processing_steps);
121+
res
122+
}
123+
}
124+
}
125+
126+
// 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.
127+
128+
129+
// -----------------------------------------------------------------------------
130+
// 3. Middleware: The `Layer` Trait
131+
// -----------------------------------------------------------------------------
132+
133+
use motore::layer::Layer;
134+
135+
// `Layer` (from `motore/src/layer/mod.rs`) is a factory
136+
// that takes an inner service `S` and returns a new, wrapped service `Self::Service`.
137+
/*
138+
pub trait Layer<S> {
139+
/// The new Service type returned after wrapping
140+
type Service;
141+
142+
/// Wraps the inner service S into a new service Self::Service
143+
fn layer(self, inner: S) -> Self::Service;
144+
}
145+
*/
146+
147+
// --- Implement a `Layer` (logging middleware) ---
148+
149+
// This is the standard pattern for a Layer: "two structs"
150+
// 1. `LogLayer`: The Layer itself (the factory)
151+
#[derive(Clone)]
152+
struct LogLayer {
153+
target: &'static str,
154+
}
155+
156+
// 2. `LogService<S>`: The new Service returned by the Layer (the wrapper)
157+
#[derive(Clone)]
158+
struct LogService<S> {
159+
inner: S, // The inner service
160+
target: &'static str,
161+
}
162+
163+
// Implement the `Layer` trait
164+
impl<S> Layer<S> for LogLayer {
165+
type Service = LogService<S>; // Specify the return type
166+
167+
fn layer(self, inner: S) -> Self::Service {
168+
// Return the new, wrapped Service
169+
LogService {
170+
inner,
171+
target: self.target,
172+
}
173+
}
174+
}
175+
176+
// --- Manually implement the `Service` trait for `LogService` ---
177+
//
178+
// Again, this is tedious.
179+
impl<Cx, Req, S> Service<Cx, Req> for LogService<S>
180+
where
181+
// `S` must also be a Service and satisfy constraints like Send/Sync
182+
S: Service<Cx, Req> + Send + Sync,
183+
S::Response: Send,
184+
S::Error: Send,
185+
Cx: Send, // LogService is generic, it doesn't care about the concrete type of Cx
186+
Req: Send,
187+
{
188+
// The response and error types are usually the same as the inner service
189+
type Response = S::Response;
190+
type Error = S::Error;
191+
192+
fn call(
193+
&self,
194+
cx: &mut Cx,
195+
req: Req,
196+
) -> impl Future<Output = Result<Self::Response, Self::Error>> + Send {
197+
println!("[LogLayer] (Manual) target: {}, enter", self.target);
198+
199+
// Must return an async block
200+
async move {
201+
// Execute logic before calling the inner service
202+
203+
// Call the inner service
204+
let result = self.inner.call(cx, req).await;
205+
206+
// Execute logic after the inner service returns
207+
match &result {
208+
Ok(_) => println!("[LogLayer] (Manual) target: {}, exit (Ok)", self.target),
209+
Err(_) => println!("[LogLayer] (Manual) target: {}, exit (Err)", self.target),
210+
}
211+
212+
result
213+
}
214+
}
215+
}
216+
217+
// -----------------------------------------------------------------------------
218+
// 4. Implementing the `Service` part of a `Layer` with a macro
219+
// -----------------------------------------------------------------------------
220+
221+
// We can also use the macro on the `impl` block for `LogService<S>`
222+
// (Note: the `impl` block for the `Layer` trait remains unchanged, the macro is only for the `Service` trait)
223+
224+
#[derive(Clone)]
225+
struct LogServiceMacro<S> {
226+
inner: S,
227+
target: &'static str,
228+
}
229+
230+
// (The `impl Layer` part is omitted, it's the same as above, returning `LogServiceMacro<S>`)
231+
232+
// --- Implement `LogService` using the macro ---
233+
#[service]
234+
impl<Cx, Req, S> Service<Cx, Req> for LogServiceMacro<S>
235+
where
236+
S: Service<Cx, Req> + Send + Sync, // Inner service constraints
237+
Cx: Send + 'static,
238+
Req: Send + 'static,
239+
{
240+
// Again, we just need to write `async fn`
241+
// The macro will automatically infer `Response = S::Response` and `Error = S::Error`
242+
async fn call(&self, cx: &mut Cx, req: Req) -> Result<S::Response, S::Error> {
243+
println!("[LogLayer] (Macro) target: {}, enter", self.target);
244+
245+
// The logic is identical, but the code is cleaner
246+
let result = self.inner.call(cx, req).await;
247+
248+
match &result {
249+
Ok(_) => println!("[LogLayer] (Macro) target: {}, exit (Ok)", self.target),
250+
Err(_) => println!("[LogLayer] (Macro) target: {}, exit (Err)", self.target),
251+
}
252+
253+
result
254+
}
255+
}
256+
257+
// -----------------------------------------------------------------------------
258+
// 5. Composition: `ServiceBuilder`
259+
// -----------------------------------------------------------------------------
260+
261+
use motore::builder::ServiceBuilder;
262+
use motore::timeout::TimeoutLayer; // A Layer that comes with Motore (motore/src/timeout.rs)
263+
use std::time::Duration;
264+
265+
// `ServiceBuilder` (from `motore/src/builder.rs`)
266+
// allows you to compose multiple Layers onto a Service.
267+
268+
async fn run_builder() {
269+
// 1. Create a ServiceBuilder
270+
let builder = ServiceBuilder::new()
271+
// 2. Add Layers.
272+
// Request execution order: top to bottom
273+
// Response execution order: bottom to top
274+
.layer(LogLayer { target: "Outer" })
275+
.layer(TimeoutLayer::new(Some(Duration::from_secs(1)))) // A Layer provided by default in Motore
276+
.layer(LogLayer { target: "Inner" });
277+
278+
// 3. Apply the Layer stack to an "innermost" service
279+
// Here we use `MyMacroService` as the core business service
280+
let service = builder.service(MyMacroService);
281+
282+
// 4. Prepare the context and request
283+
// Note: processing_steps starts at 0
284+
let mut cx = MyContext { request_id: 42, processing_steps: 0 };
285+
let req = "hello motore".to_string();
286+
287+
// 5. Call it!
288+
let res = service.call(&mut cx, req).await;
289+
290+
println!("\nFinal response: {:?}", res);
291+
292+
/*
293+
* Expected output:
294+
*
295+
* [LogLayer] (Manual) target: Outer, enter
296+
* [LogLayer] (Manual) target: Inner, enter
297+
* [MacroService] handling req id: 42, step: 1 <-- step becomes 1
298+
* [MacroService] responding req id: 42, step: 1
299+
* [LogLayer] (Manual) target: Inner, exit (Ok)
300+
* [LogLayer] (Manual) target: Outer, exit (Ok)
301+
*
302+
* Final response: Ok("HELLO MOTORE")
303+
*/
304+
305+
// Finally, the original cx has been modified
306+
println!("Final context steps: {}", cx.processing_steps); // Will print 1
307+
}
308+
309+
// -----------------------------------------------------------------------------
310+
// 6. Helper Utility: `service_fn`
311+
// -----------------------------------------------------------------------------
312+
313+
// Sometimes you don't want to create a new struct for a simple service.
314+
// `motore/src/service/service_fn.rs` provides `service_fn`, which can directly convert a compliant function into a `Service`.
315+
316+
use motore::service::service_fn;
317+
318+
async fn my_handler_func(cx: &mut MyContext, req: String) -> Result<String, Infallible> {
319+
// --- Demonstrate modifying &mut Cx ---
320+
cx.processing_steps += 10;
321+
322+
println!("[service_fn] handling req id: {}, step: {}", cx.request_id, cx.processing_steps);
323+
Ok(req.to_lowercase())
324+
}
325+
326+
327+
#[tokio::main] async fn main() {
328+
println!("\n--- Example 1: Running `run_builder` ---");
329+
330+
run_builder().await;
331+
332+
println!("\n--- Example 2: Running `service_fn` (standalone) ---");
333+
334+
// `service_fn` can convert a function or closure that matches the `async fn(&mut Cx, Req) -> Result<Res, Err>` signature
335+
// directly into a `Service`.
336+
let fn_service = service_fn(my_handler_func);
337+
338+
// Let's run it to prove it works
339+
let mut cx1 = MyContext { request_id: 101, processing_steps: 0 };
340+
let res1 = fn_service.call(&mut cx1, "HELLO WORLD".to_string()).await;
341+
// Check the modified context
342+
println!("service_fn response: {:?}, context steps: {}", res1, cx1.processing_steps); // prints 10
343+
344+
345+
println!("\n--- Example 3: Running `service_fn` (in a Builder) ---");
346+
347+
// You can also use it in a ServiceBuilder:
348+
let service_from_fn = ServiceBuilder::new()
349+
.layer(LogLayer { target: "ServiceFn" })
350+
.service_fn(my_handler_func); // shorthand for .service(service_fn(my_handler_func))
351+
352+
// Run it
353+
let mut cx2 = MyContext { request_id: 202, processing_steps: 0 };
354+
let res2 = service_from_fn.call(&mut cx2, "ANOTHER EXAMPLE".to_string()).await;
355+
// Check the modified context
356+
println!("service_from_fn response: {:?}, context steps: {}", res2, cx2.processing_steps); // prints 10
357+
}
358+
```
359+
360+
## What you've learned
361+
362+
- Motore's core design: Service, Layer, and the mutable Cx context
363+
- The use of `#[motore::service]`
364+
- How to assemble Services and Layers using `ServiceBuilder`
365+
366+
## What's Next?
367+
368+
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.
369+
370+
Next, you can check out some features of Motore that were not covered in this tutorial:
371+
372+
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.
373+
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.
374+
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.
375+
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.
376+
5. Motore provides a dedicated [`MakeConnection<Address>` Trait](https://deepwiki.com/cloudwego/motore/6-utilities#makeconnection-trait) to abstract the creation of "connections".
377+
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)