IRequest 与 IRequestHandler
核心契约
rust-webx 用两个 trait 定义一个完整端点:
// 声明:这是一个请求,响应类型为 T
pub trait IRequest<TResponse>: Send + 'static
where
TResponse: serde::Serialize + Send + 'static,
{}
// 实现:如何处理这个请求
#[async_trait]
pub trait IRequestHandler<T, R>: Send + Sync
where
T: IRequest<R> + Send + 'static,
R: serde::Serialize + Send + 'static,
{
async fn handle(&self, req: T) -> Result<R>;
}
类型绑定规则(铁律)
**`IRequest<T>` 的泛型参数 T 必须等于 `IRequestHandler<T, R>` 的 R。**
// ✅ 正确
#[get("/users/{id}")]
impl IRequest<UserDto> for GetUserRequest {}
impl IRequestHandler<GetUserRequest, UserDto> for GetUserHandler { ... }
// ❌ 错误:响应类型不一致
impl IRequest<String> for GetUserRequest {}
impl IRequestHandler<GetUserRequest, UserDto> for GetUserHandler { ... }
违反此规则会导致编译失败或运行时类型不匹配。
IRequest 的职责
`IRequest<T>` 是一个**标记 trait**(marker trait),本身无方法。它的价值在于:
1. **承载响应类型信息** — 框架据此决定序列化什么、返回什么状态码 2. **关联路由元数据** — 通过 `#[get]` 等宏在 impl 块上附加 3. **作为类型参数** — `IMediator::send()` 的泛型约束
响应类型语义
| 响应类型 | HTTP 行为 | |---------|----------| | `String`, `UserDto`, `Vec<T>` 等 | 200 + JSON body | | `()` | 204 No Content,空 body |
IRequestHandler 的职责
Handler 是**唯一执行业务逻辑**的地方:
#[async_trait]
impl IRequestHandler<CreateUserRequest, UserDto> for CreateUserHandler {
async fn handle(&self, req: CreateUserRequest) -> Result<UserDto> {
// 1. 校验(或用 PipelineBehavior)
// 2. 调用领域服务
// 3. 返回结果或 Error
let user = self.service.create(&req.name, &req.email).await?;
Ok(user)
}
}
注册为 dyn trait object
框架通过 `dyn IRequestHandler<T, R>` 做类型擦除和 DI 解析:
svc.singleton::<dyn IRequestHandler<CreateUserRequest, UserDto>>(
|_| Arc::new(CreateUserHandler::default())
)
**必须注册为 dyn trait**,不能注册具体类型。
与 IMediator 的协作
// 在另一个 Handler 或 Service 中
let user = self.mediator.send(GetUserRequest { id: "123".into() }).await?;
`send()` 根据 `GetUserRequest` 的类型自动找到对应 Handler,无需手动指定。
设计优势
| 优势 | 说明 | |------|------| | 一个文件一个端点 | Request + Handler 可放在同一文件 | | 编译期类型检查 | 响应类型不匹配 = 编译错误 | | 可测试 | 直接实例化 Handler 调用 `handle()` | | OpenAPI 友好 | 类型信息可自动提取 |
小结
`IRequest<T>` 声明「要什么」,`IRequestHandler<T,R>` 实现「怎么做」。掌握这对 trait 是使用框架的全部钥匙。
下一节:[路由宏详解](route-macros.md)