本文从请求进入服务器开始,串起 aceboot 的模块边界、声明式 API、组件开发方式和一个任务管理项目案例。

1. 总体架构

aceboot 是一个用仓颉编写的服务端框架,定位接近 Spring Boot / MidwayJS,技术路线更接近 Micronaut / Quarkus:声明由编译期宏处理,运行时不依赖反射。

在这里插入图片描述

图 1:aceboot 的编译期声明层、运行时内核与适配扩展层。

应用代码
  ├─ @Controller / @Service / @Dto / @Middleware
  ├─ Component、ORM、Security、Agent 等可选能力
  ↓
ace-framework
  ├─ macros:读取声明并生成代码
  ├─ runtime:IoC、配置、生命周期、路由注册、AOP运行时
  ├─ boot:加载配置、装配组件、构建应用
  └─ facade:面向应用的统一入口
  ↓
ace-web / ace-router / ace-bodyparser
  ├─ Context、App、compose 洋葱中间件

  ├─ 路由匹配
  └─ 请求体、查询参数、校验
  ↓
ace-http → stdx.net.http

在这里插入图片描述

图 2:一次请求从 HTTP 适配到响应回写的执行路径。

外围模块按需接入:ace-security 提供 JWT、CSRF、安全头和授权 guard;ace-orm 提供实体宏、仓储和数据库方言;ace-observability 提供日志、指标和 trace context;ace-agentace-rpcace-autopilot 面向智能体、微服务和控制面。

核心层保持小而独立:ace-webace-routerace-bodyparser 不依赖 stdx,可单独测试;HTTP、JSON、TLS、加密等适配能力放在上层模块。

2. 一次请求如何流动

aceboot 的中间件模型是 Koa 风格洋葱:前置逻辑在进入下一层前执行,后置逻辑在 next() 返回后执行。

public func requestTimer(): Middleware {
    return {ctx, next =>
        let start = nowMs()
        next()
        ctx.setHeader("X-Elapsed-Ms", "${nowMs() - start}")
    }
}

实际处理路径可概括为:

stdx HttpRequest
  → ace-http 转为 Context
  → 错误处理 / 请求日志 / CORS / 健康检查
  → 用户中间件(按 order 升序)
  → 路由匹配
  → 控制器 Bean 解析
  → 路径参数、查询参数、Body 绑定
  → DTO 校验
  → 业务方法
  → DTO/文本/流式响应
  → ace-http 写回 HttpResponse

响应体在核心层用 ResponseBody 枚举表达,避免文本、二进制和流式响应同时存在造成优先级歧义;HTTP 适配层只需 match 后选择写回方式。

3. 应用开发:控制器、服务和 DTO

最小控制器:

@Service
public class Greeter {
    public func hi(name: String): String { "Hello, ${name}!" }
}

@Controller["/api"]
public class HelloController {
    @Inject var greeter: Greeter

    @Get["/hello/:name"]
    public func hello(name: String): String {
        greeter.hi(name)
    }
}

main() { AceApplication.run("0.0.0.0", 8080) }

@Get 宏根据方法参数和路径决定绑定来源:路径包含 :name 时读取路径参数,否则读取查询参数;Int64Float64Bool 会生成类型转换和错误处理代码。

请求体使用 DTO:

@Dto
public class CreateTaskRequest {
    @NotEmpty
    public var title: String = ""
}

@Controller["/tasks"]
public class TaskController {
    @Inject var service: TaskService

    @Post[""]
    public func create(body: CreateTaskRequest): TaskView {
        service.create(body)
    }
}

Stringbody 参数会触发 JSON 反序列化和 validate();校验失败直接转换为 HTTP 400。返回 DTO 时,框架生成对应的序列化路径,不需要控制器手写 JSON 拼接。

4. 组件的使用:从能力到装配

组件适合封装需要配置、依赖编排、生命周期或 Bean 注入的能力。简单横切能力用 Middleware 工厂;有配置和装配顺序要求时使用 @Component

在这里插入图片描述

图 3:组件从配置加载到服务就绪,以及停机逆序销毁的生命周期。

@Component
public class GreetingComponent {
    public func name(): String { "greeting" }

    public func defaults(): Array<(String, String)> {
        [("greeting.enabled", "true"), ("greeting.text", "hi")]
    }

    public func enabled(config: Config): Bool {
        config.getBool("greeting.enabled", true)
    }

    public func setup(ctx: ComponentContext): Unit {
        let cfg = ctx.namespace("greeting")
        let text = cfg.getOr("text", "hi")
        ctx.useMiddleware(50, {c, next =>
            c.setHeader("X-Greeting", text)
            next()
        })
        ctx.provide("GreetingService", {=> GreetingService(text) as Any})
    }
}

应用依赖组件模块,并在入口导入其包即可触发自注册;配置放在 application.toml

[greeting]
enabled = true
text = "hello from ace"

启动装配顺序是:加载 profile → 合并组件默认配置 → enabled 过滤 → 按 dependsOn 拓扑排序 → 执行 setupbuildApponReady。停机时按逆序执行 onStop

开发独立组件的步骤:

  1. 建立独立 module,在 workspace 登记;
  2. 实现中间件、Bean 或业务能力;
  3. @Component 暴露配置和生命周期;
  4. ctx.namespace 隔离配置;
  5. ctx.useMiddlewarectx.provide 向应用贡献能力;
  6. 在应用依赖中引入模块,并导入组件包。

5. 项目案例:任务管理 API

仓库中的 examples/task-api 展示了一个更完整的业务切片。它按职责拆分为:

controller/  HTTP 入口、路由、认证、流式下载
service/     业务编排
repository/  数据访问(由 ORM 场景接入)
 domain/     Task 等业务对象
 dto/        创建请求、列表视图、登录和 Token
 middleware/ 特性中间件与安全中间件
 component/  SeedComponent、PoweredByComponent
 exception/  异常过滤器

一次“创建任务”请求的路径如下:

POST /tasks
  → 安全中间件解析 JWT
  → Controller 宏绑定 JSON 到 CreateTaskRequest
  → @NotEmpty 等约束执行
  → TaskService 校验当前用户和业务规则
  → 持久化 Task
  → TaskView 序列化为 JSON

错误不会散落在每个控制器分支中。领域异常由 @Exception 声明,@Catch 过滤器把异常映射为稳定的 HTTP 状态和错误 JSON;未匹配异常再交给默认错误处理中间件。

项目还体现了组件之间的依赖关系:数据库组件先建立数据源和表,种子数据组件通过 dependsOn(["orm"]) 确保数据库准备完成后再执行。这样业务入口不需要手写初始化顺序。

6. 安全、AOP 和可观测性如何接入

安全能力按复杂度选择接入方式:

@Middleware[20]
public func auth(): Middleware { jwtAuth() }

JWT 认证把主体写入请求上下文;authenticated()requireRoles(["admin"]) 作为 guard 控制访问。需要配置和 Bean 编排的能力则包装成 Component。

AOP 注解复用同一个编译期织入内核:

  • @Timed:记录方法耗时;
  • @Cacheable:生成带 TTL 的缓存路径;
  • @Async:返回 Future<T>
  • @Scheduled:登记周期或 cron 任务。

可观测性同样是可组合中间件:请求 ID、结构化请求日志、指标和 W3C Trace Context 均可按应用需要装配,而不是强制塞入核心。

7. 开发、测试与部署

项目级开发命令:

source scripts/env.sh
cjpm build
cjpm test

开发时建议先运行最小示例确认入口,再运行 examples/task-api 验证真实 HTTP 链路。宏相关问题使用:

cjpm build --debug-macro

它能直接观察路由注册、Bean 工厂、参数绑定和组件注册的展开结果。框架的测试重点不是注解字符串,而是可观察契约:路由是否命中、校验是否返回 400、单例是否复用、循环依赖是否报错、组件依赖是否按拓扑顺序执行。

结语

aceboot 的架构核心不是把所有能力放进一个“大框架”,而是保持明确层次:内核只处理请求和中间件;声明层把样板前移到编译期;组件层负责可插拔能力;适配层连接 HTTP、数据库和外部服务。开发者因此可以从一个控制器开始,逐步加入 IoC、校验、安全、ORM、可观测性和智能体能力,而不必改变业务代码的基本结构。

仓库地址:https://atomgit.com/clowder/ACE

Logo

一站式 AI 云服务平台

更多推荐