Files
raisfast/docs/springboot-inspiration.md
T
2026-05-10 03:00:33 +08:00

19 KiB
Raw Blame History

Spring Boot 设计借鉴路线图

从 Spring Boot 生态中提取适合 Rust/Axum 场景的设计模式,作为项目演进参考。 已有对应:AppError@ControllerAdviceEventBusApplicationEventPublisherWorker@Async + @Scheduled。 与 laravel-inspiration.md 互补——本文聚焦 Spring Boot 独有的设计理念。


现有架构 ↔ Spring Boot 对照

Spring Boot 项目现状 差距
IoC Container / @Autowired build_app_state() 手动 Arc::new() 60+ 组件手写构造
@ControllerAdvice AppError + IntoResponse 已完善
@Validated / Bean Validation validation::validate(&req)? 已完善
ApplicationEventPublisher EventBus (tokio broadcast) 已完善
@Scheduled CronScheduler + DB cron 表 更灵活
@Transactional 手动 pool.begin() 仅 4 处使用,多数多步操作无事务
@Profile / 多环境配置 平铺 .env 无环境分层
Actuator 健康监控 /api/v1/health 基础版 缺少详细探针
Spring Security FilterChain Axum layer() + AuthUser extractor 部分覆盖
AOP @Around AspectEngine + aop_http.rs 有基础,不够通用
@ConditionalOnProperty #[cfg(feature = "...")] 编译期方案

P0 — 高价值(解决实际痛点)

1. Service Locator / 模块化 App State

现状build_app_state() 是一个 130 行的巨型函数(src/lib.rs:117-231),60+ 个 Arc::new() 手动排列。新增一个 service 要改 3 处(struct 定义 + 构造 + 传参)。

Spring 借鉴Spring 的 ApplicationContext 容器自动管理 Bean 生命周期和依赖。Rust 不需要反射式 IoC,但可以借鉴 Service Locator 模式 简化组件注册。

方案Builder + trait-object registry,编译期检查依赖类型:

// src/app/mod.rs

pub struct AppBuilder {
    pool: Pool,
    config: Arc<AppConfig>,
    services: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}

impl AppBuilder {
    pub fn new(config: AppConfig) -> Self { ... }

    pub fn register<T: Send + Sync + 'static>(mut self, service: Arc<T>) -> Self {
        self.services.insert(TypeId::of::<T>(), service);
        self
    }

    pub fn resolve<T: 'static>(&self) -> Arc<T> {
        self.services
            .get(&TypeId::of::<T>())
            .and_then(|s| s.downcast_ref::<Arc<T>>())
            .cloned()
            .expect("service not registered")
    }

    pub fn build(self) -> AppState {
        AppState {
            inner: Arc::new(AppStateInner {
                pool: self.pool,
                config: self.config,
                services: self.services,
            }),
        }
    }
}

// 注册时链式调用
let state = AppBuilder::new(config)
    .register::<dyn PostRepository>(post_repo)
    .register::<dyn UserRepository>(user_repo)
    .register::<PostService>(post_service)
    .register::<EventBus>(bus)
    .build();

设计要点:

  • 不引入反射:用 TypeId + Any 实现类型安全的 service locator
  • 可选编译期检查resolve() 可以用 macro 包装为 resolve!<T>(state) 在 debug 模式 panicrelease 模式 unwrap
  • 渐进迁移AppState 保留现有字段,新组件走 registry;逐步替换老字段
  • 测试友好:测试时只需 register mock 实现即可

与 Spring IoC 的区别:

Spring IoC Rust Service Locator
发现机制 反射 + @ComponentScan 显式 register::<T>()
依赖注入 自动 @Autowired 手动 resolve::<T>()
生命周期 singleton/prototype/request Arc 引用计数
循环依赖 自动检测 编译期 Arc 可循环,需人工避免

收益:新增 service 只需 1 行 register + 1 行 resolvebuild_app_state() 从过程式变为声明式。

工作量2-3 天


2. #[transactional] 过程宏

现状:只有 4 处使用 pool.begin() 手动事务(sqlx_post.rsauth.rssqlite_queue.rsscheduler.rs)。架构审计发现 media 删除、options 批量更新、content_type 字段写入等多步操作缺少事务保护。

Spring 借鉴Spring 的 @Transactional 注解是声明式事务的典范——一个注解自动包裹 begin / commit / rollback

方案:过程宏自动注入事务边界,service 层函数标注即可:

// proc-macro crate: raisfast-macros

#[transactional]
async fn create_post(
    pool: &Pool,
    cmd: CreatePostCmd,
    tenant_id: Option<&str>,
) -> AppResult<Post> {
    // 宏展开后自动变为:
    // let mut tx = pool.begin().await?;
    // let result = { ... original body with &mut tx ... }.await;
    // tx.commit().await?;
    // result

    sqlx::query("INSERT INTO posts ...")
        .bind(&cmd.title)
        .execute(pool)  // ← 宏会将 pool 替换为 &mut tx
        .await?;

    for tag_id in &cmd.tag_ids {
        sqlx::query("INSERT INTO post_tags ...")
            .execute(pool)  // ← 同上
            .await?;
    }

    Ok(post)
}

实现策略:

  • 宏解析函数签名,找到 pool: &Pool 参数
  • 在函数体前插入 let mut tx = pool.begin().await?;
  • 将函数体中所有 pool 引用替换为 &tx
  • 函数体后插入 tx.commit().await?;
  • 错误自动 rollbacktx drop 时)

备选方案(如果过程宏改 pool 引用太侵入):

// 更简单:wrapper function + 闭包
#[transactional(pool_arg = "pool")]
async fn create_post(pool: &Pool, cmd: CreatePostCmd) -> AppResult<Post> {
    // 宏生成:
    // pub async fn create_post(pool: &Pool, cmd: CreatePostCmd) -> AppResult<Post> {
    //     crate::db::transaction(pool, move |tx| Box::pin(async move {
    //         create_post_inner(tx, cmd).await
    //     })).await
    // }
    // async fn create_post_inner(tx: &Transaction, cmd: CreatePostCmd) -> AppResult<Post> { ... }
}

收益:一行注解代替 5 行事务样板代码,降低遗漏事务的风险。

工作量2-3 天


3. Actuator 风格健康检查

现状/api/v1/health 返回固定 { "status": "ok" },不检查 DB 连接、磁盘空间、Worker 状态等。生产环境无法通过 health endpoint 发现问题。

Spring 借鉴Spring Boot Actuator 的 /actuator/health 汇总所有组件状态,返回 UP / DOWN / DEGRADED

方案

// src/handlers/health.rs — 扩展现有 health endpoint

#[derive(Serialize)]
pub struct HealthResponse {
    pub status: HealthStatus,
    pub components: HashMap<String, ComponentHealth>,
    pub uptime_seconds: u64,
    pub version: String,
}

#[derive(Serialize)]
pub struct ComponentHealth {
    pub status: HealthStatus,
    pub details: Option<Value>,
}

#[derive(Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum HealthStatus {
    Up,
    Down,
    Degraded,
}

// 各组件实现 HealthIndicator trait
pub trait HealthIndicator: Send + Sync {
    fn name(&self) -> &str;
    async fn check(&self) -> ComponentHealth;
}

// 注册多个 indicator
struct DatabaseHealth { pool: Pool }
struct WorkerHealth { queue: Arc<dyn JobQueue> }
struct SearchHealth { engine: Arc<dyn SearchEngine> }
struct StorageHealth { config: Arc<AppConfig> }

// GET /api/v1/health
// → { "status": "UP", "components": { "database": { "status": "UP", ... }, ... } }
// → 任何 DOWN 则整体 status = DOWN,返回 503

关键检查项:

  • DatabaseSELECT 1 测试连接
  • Workerdead jobs 数量 / 队列深度
  • Search:索引是否可用
  • Storage:磁盘剩余空间 / 可写
  • Migrations:是否有 pending migration

管理端点(需 admin 权限):

  • GET /admin/info — 构建信息、Git SHA、运行时间
  • GET /admin/metrics — 现有 Prometheus 指标(已有 middleware/metrics.rs

收益:生产部署可对接 K8s liveness/readiness probe,故障快速定位。

工作量2-3 天


4. Profile 风格多环境配置

现状AppConfig::from_env() 读取 .envAppConfig::test_defaults() 硬编码测试配置。没有开发/测试/预发布/生产环境分层。

Spring 借鉴Spring 的 application-{profile}.yml 支持多环境配置覆盖:基础配置 + profile 特定覆盖。

方案:分层 .env 加载,后者覆盖前者:

.env                        # 基础默认(所有环境共享)
.env.local                  # 个人本地覆盖(gitignore
.env.development            # 开发环境
.env.test                   # 测试环境
.env.production             # 生产环境
// src/config/app.rs — AppConfig::init() 改造

impl AppConfig {
    pub fn init() -> Self {
        let profile = env::var("APP_PROFILE")
            .unwrap_or_else(|_| "development".into());

        // 加载顺序:后者覆盖前者
        dotenvy::from_path(".env").ok();
        dotenvy::from_path(&format!(".env.{profile}")).ok();
        dotenvy::from_path(".env.local").ok();

        let config = Self::from_env();
        Self::validate(&config, &profile);
        config
    }
}

设计要点:

  • 不引入 YAML/TOML 配置文件:保持 .env 扁平结构(与 12-Factor App 一致)
  • APP_PROFILE 环境变量选择当前环境(development / test / staging / production
  • .env.local 用于开发者个人覆盖(如本地 DB 路径),永远不入 git
  • 生产安全profile == "production" 时强制校验 JWT_SECRET / CORS / HOST 等

与 Spring Profile 的区别:

Spring Profile Rust Profile
格式 YAML 层叠 .env 层叠
激活 spring.profiles.active APP_PROFILE
覆盖 深度合并 简单 key 覆盖(dotenv 语义)
类型安全 @Value + @ConfigurationProperties from_env() + 类型转换

收益:开发/测试/生产配置分离,不再手动改 .env 切换环境。

工作量1-2 天


P1 — 中价值(提升工程质量)

5. @ConditionalOnProperty 风格的 Feature Flag 运行时切换

现状#[cfg(feature = "db-sqlite")] 是编译期特性开关,改一个 feature 要重新编译。BuiltinsConfigBUILTIN_BLOG 等)是运行时开关但只在 server.rs 路由注册时检查,不能动态启停。

Spring 借鉴@ConditionalOnProperty 允许 Bean 根据配置值有条件注册。

方案:在 AppState 中引入 Feature Flag 注册表,支持运行时读取和热更新:

// src/features.rs

pub struct FeatureFlags {
    flags: ArcSwap<HashMap<String, bool>>,
}

impl FeatureFlags {
    pub fn from_config(config: &AppConfig) -> Self { ... }
    pub fn is_enabled(&self, flag: &str) -> bool { ... }
    pub fn reload(&self, config: &AppConfig) { ... }
}

// handler 中使用
pub async fn list_posts(
    State(state): State<AppState>,
) -> AppResult<ApiResponse<...>> {
    if !state.features.is_enabled("blog") {
        return Err(AppError::NotFound("module disabled"));
    }
    // ...
}

设计要点:

  • 使用 ArcSwap(项目已广泛使用)支持无锁热更新
  • BuiltinsConfig 合并,统一入口
  • 可通过 admin API 动态开关(不重启)
  • 编译期 featuredb-sqlite)保持不变——那是代码级开关,不应运行时切换

工作量1-2 天


6. Handler Interceptor(统一请求前后处理)

现状AOP HTTP layeraop_http.rs+ 各种 middleware。但请求计时、请求日志、响应 header 注入散落在多处 middleware 中,且无法按路由选择性地拦截。

Spring 借鉴HandlerInterceptor.preHandle() / postHandle() / afterCompletion() 提供精细的请求生命周期拦截。

方案:在现有 AspectEngine 基础上扩展为结构化的 Interceptor 注册:

// src/middleware/interceptor.rs

pub trait HandlerInterceptor: Send + Sync {
    fn pre_handle(&self, req: &mut RequestParts, ctx: &mut InterceptorContext) -> Result<(), AppError> {
        Ok(())
    }
    fn post_handle(&self, req: &RequestParts, resp: &mut Response, ctx: &InterceptorContext) {
        // default no-op
    }
    fn after_completion(&self, req: &RequestParts, result: &Result<(), AppError>, ctx: &InterceptorContext) {
        // default no-op — for cleanup/logging
    }
}

pub struct InterceptorContext {
    pub start_time: Instant,
    pub extras: HashMap<String, Box<dyn Any + Send>>,
}

// 注册
let mut chain = InterceptorChain::new();
chain.add(LoggingInterceptor);           // 所有请求
chain.add(AuthLoggingInterceptor);       // 仅 auth 路由
chain.add(AuditInterceptor);             // 仅写操作

与现有 AOP 的关系:

  • 现有 AspectEngine 侧重于插件系统的 before_request / after_request hook
  • HandlerInterceptor 侧重于框架级的横切关注点(日志、审计、计时)
  • 两者共存:Interceptor 先执行,AOP hook 后执行

工作量2-3 天


7. 统一 @RequestMapping 风格路由注册

现状src/server.rs 1700+ 行,所有路由集中在巨型 build_router() 函数中。路由注册、中间件应用、权限检查混在一起。

Spring 借鉴@RestController + @RequestMapping("/api/v1/posts") 让路由声明和 handler 代码在一起,自动发现注册。

方案:用宏或约定将路由注册从 server.rs 中拆散到各 handler 模块:

// src/handlers/post.rs — 路由声明与 handler 在同一文件

pub fn routes() -> Router<AppState> {
    Router::new()
        .route("/", get(list).post(create))
        .route("/{slug}", get(get_by_slug).put(update).delete(delete))
        .route("/search", get(search))
        .layer(login_rate_limit())  // 路由级中间件
}

// src/server.rs — 只做组装
fn build_router(state: AppState) -> Router {
    let api = Router::new()
        .nest("/posts", post::routes())
        .nest("/users", user::routes())
        .nest("/media", media::routes())
        .nest("/workflow", workflow::routes())
        .nest("/content-type", content_type::routes())
        // ... 每个 handler 模块一行
        ;
    Router::new()
        .nest("/api/v1", api)
        .layer(/* global middleware */)
}

设计要点:

  • 每个 handler 模块导出 pub fn routes() -> Router<AppState>
  • server.rs 从 1700 行降到 ~100 行路由组装
  • 路由级中间件(rate limit、auth guard)在模块内声明
  • 全局中间件(CORS、security headers、tracing)仍在 build_router

收益server.rs 可维护性大幅提升,新增模块只需加一行 .nest()

工作量1-2 天(纯重构,不改行为)


P2 — 值得但优先级低

8. Spring Security 风格 FilterChain

现状AuthUser 是 extractor(不拒绝请求),权限检查在 handler 中手动调用 auth.ensure_admin() 等。中间件 permission.rs 也是 extractor 模式。

Spring 借鉴Spring Security 的 SecurityFilterChain 在请求到达 handler 之前完成所有认证/授权决策,handler 无需关心安全。

方案(远期):

// src/middleware/security_chain.rs

pub struct SecurityChain {
    rules: Vec<SecurityRule>,
}

struct SecurityRule {
    path_pattern: Matcher,        // "/api/v1/admin/**"
    methods: Vec<Method>,
    required_role: Option<Role>,
    required_permission: Option<String>,
    public: bool,
}

// 在 router 层用 layer 应用
Router::new()
    .nest("/api/v1/admin", admin_routes())
    .layer(SecurityChain::new()
        .rule("/api/v1/admin/**", Require::Role("admin"))
        .rule("/api/v1/posts", Require::Any)       // 公开读
        .rule("/api/v1/posts/**", Require::Role("author"), Method::POST | Method::PUT)
    )

注意:当前 extractor 模式在 Rust/Axum 生态中更惯用,此方案为远期考虑。当前优先用 Laravel 路线图中的 PolicyP1-5)解决。


9. 其他可借鉴特性

特性 说明 工作量
RetryTemplate 声明式重试策略(指数退避 + 可配置重试条件),用于插件调用、webhook 投递 2-3 天
RestTemplate 风格 HTTP Client 封装 reqwest 为类型安全的 HTTP client,插件 HTTP 调用统一走此 client 1-2 天
Event Listener 条件过滤 @EventListener(condition = "#event.type == 'post'") 风格,EventBus subscribe 支持条件过滤 1 天
ApplicationRunner 启动完成后执行一次性初始化任务(数据迁移检查、默认角色种子等) 0.5 天
Graceful Shutdown Spring Boot 的 ShutdownHook,当前已有 SIGTERM 处理,可增强为等待所有 in-flight 请求完成 1 天

实施优先级

Service Locator / App State 重构 (P0-1)
    ↓
#[transactional] 过程宏 (P0-2)
    ↓
Actuator 健康检查 (P0-3)
    ↓
Profile 多环境配置 (P0-4)
    ↓
Feature Flag 运行时切换 (P1-5)
    ↓
Handler Interceptor (P1-6)
    ↓
路由注册拆分 (P1-7)
    ↓
其余 P2 项按需排期

前两项(Service Locator + Transactional)是架构级改进,中间两项是运维和开发体验提升,后两项是工程规范。


Spring Boot → Rust 适配原则

Spring Boot 模式 Rust 适配 原因
IoC Container Service LocatorTypeId + Arc<dyn Any> Rust 无反射,显式注册 + 类型安全解析
@Transactional 过程宏注入 tx Rust 没有运行时代理,编译期代码生成是唯一选择
@Profile .env.{profile} 层叠 保持 12-Factor App 风格,不引入 YAML
Actuator HealthIndicator trait + 注册表 Rust 不需要 JMXHTTP endpoint 足够
@ConditionalOnProperty ArcSwap<HashMap> 运行时 flag 编译期 feature 和运行时 flag 各有用途
@RequestMapping fn routes() -> Router Axum 原生支持 Router 组合,无需注解发现
Security FilterChain 暂不实施 Extractor 模式在 Axum 更惯用,Policy 层解决授权
AOP 编译期过程宏 / middleware Rust 没有运行时字节码增强,所有"切面"在编译期展开

与 Laravel 路线图的关系

维度 Laravel 借鉴 Spring Boot 借鉴
数据层 Factory、Migration 回滚、Query Scope @Transactional、Service Locator
API 层 API Resource 转换 Handler Interceptor、路由拆分
权限 Policy Security Chain(远期)
运维 维护模式 Actuator、Profile、Feature Flag
架构 Lifecycle HookEventBus App State 重构
两者共有 Factory ≈ @ComponentEventBus ≈ ApplicationEventPublisher

建议实施顺序:先完成 Laravel 路线图 P0Migration 回滚、Factory、API Resource),再启动 Spring Boot 路线图 P0Service Locator、Transactional、Actuator)。