目录结构

ACE Framework 遵循"高内聚、低耦合"的分层原则。每个目录承担单一职责,可独立演进。以下以 task-api 示例项目为蓝本展示完整结构。

完整目录树

task-api/
├── cjpm.toml                  # 包配置(package、dependencies、sub-packages)
├── src/
│   ├── main.cj                # 程序入口,显式 import 触发自注册
│   ├── controller/            # HTTP 路由层(@Controller + @Get/@Post …)
│   │   ├── task_controller.cj
│   │   └── user_controller.cj
│   ├── service/               # 业务逻辑层(@Service / @Prototype)
│   │   ├── task_service.cj
│   │   └── user_service.cj
│   ├── domain/                # 领域模型 / 实体(@Entity、@Id、@Column)
│   │   ├── task.cj
│   │   └── user.cj
│   ├── dto/                   # 数据传输对象(请求体 / 响应体结构)
│   │   ├── create_task_dto.cj
│   │   └── task_response.cj
│   ├── middleware/            # 自定义中间件(@Middleware[order])
│   │   ├── auth_middleware.cj
│   │   └── logging_middleware.cj
│   └── exception/             # 全局异常处理(@Catch[ExceptionType])
│       ├── not_found_handler.cj
│       └── validation_handler.cj
└── config/
    ├── application.toml       # 基础配置(所有环境共用)
    ├── application-local.toml # 本地开发覆盖
    ├── application-dev.toml   # 开发环境覆盖
    ├── application-test.toml  # 测试环境覆盖
    └── application-prod.toml  # 生产环境覆盖

目录职责说明

目录 职责 典型注解
src/controller/ 接收 HTTP 请求,参数绑定,调用 Service,返回响应 @Controller @Get @Post @PathParam @Body
src/service/ 核心业务逻辑,事务边界,调用 Repository/外部服务 @Service @Transactional @Cacheable
src/domain/ 实体定义,与数据库表一一映射 @Entity @Id @Column
src/dto/ 纯数据结构,用于请求体反序列化和响应体序列化 无注解(普通 struct/class)
src/middleware/ 横切关注点:认证、日志、限流等,按 order 排序挂载 @Middleware[order]
src/exception/ 全局异常拦截,将异常映射为标准 HTTP 响应 @Catch[ExceptionType]
config/ 多环境配置文件,TOML 格式,按环境叠加合并 —

cjpm.toml 结构说明

[package]
name = "task_api"
organization = "aceboot"
version = "1.0.0"
cangjie-version = "1.1.0"

[dependencies]
"aceboot::framework" = { path = "../../ace-framework" }
"aceboot::http"      = { path = "../../ace-http" }

# stdx 预编译路径统一经 CANGJIE_STDX_PATH 环境变量引用(勿删):
# macOS 由 scripts/env.sh 导出;Linux 构建前 export 指向对应平台的 stdx 根目录。
[target.aarch64-apple-darwin.bin-dependencies]
path-option = ["${CANGJIE_STDX_PATH}/static/stdx"]

[target.x86_64-unknown-linux-gnu.bin-dependencies]
path-option = ["${CANGJIE_STDX_PATH}/static/stdx"]

ACE 的各能力模块(ace-web / ace-router / ace-bodyparser / ace-http / ace-framework)均作为独立成员发布在 workspace 下,task-api 只需在 [dependencies] 中声明所需层。cjpm 会自动处理传递依赖,无需手动列出 ace-web。

为什么 main.cj 必须显式 import 子包

ACE 的声明式自注册机制依赖顶层 let 变量的初始化器在程序启动时自动执行。以 @Service 为例,宏展开后会在源文件顶层生成:

let __ace_reg_TaskService = Registry.register("TaskService") { TaskService() }

仓颉编译器只会编译被直接或间接 import 引用的包。若 main.cj 不导入 controller 和 service 包,这些顶层初始化器将永远不会执行,容器中也就没有任何可注入的 Bean。

// src/main.cj
package aceboot::task_api

// 必须显式 import 各子包,否则 @Service/@Controller 的自注册不会触发
import aceboot::task_api.controller.*
import aceboot::task_api.service.*
import aceboot::task_api.middleware.*
import aceboot::task_api.exception.*

import aceboot::framework.*
import aceboot::http.*

main(): Int64 {
    let app = AceApplication.create()
    app.listen(8080)
    return 0
}

::: tip 与 Spring/MidwayJS 的差异 Spring 通过运行时 classpath 扫描(反射)自动发现 Bean;MidwayJS 通过 TypeScript 装饰器元数据在启动时扫描。ACE 选择编译期宏 + 零反射路线——宏生成等价的显式注册代码,import 语句替代运行时扫描,在保持声明式体验的同时消除了反射开销。 :::

::: warning 常见错误 忘记 import 某个 controller 包后,路由注册不生效,但框架不会报错——容器中仅仅没有该 Controller 的 Bean。遇到 404 时,优先检查 main.cj 的 import 列表。 :::

与 MidwayJS 目录对比

MidwayJS ACE Framework 说明
src/controller/ src/controller/ 相同职责
src/service/ src/service/ 相同职责
src/entity/ src/domain/ ACE 用 domain 体现领域驱动意图
src/dto/ src/dto/ 相同职责
src/middleware/ src/middleware/ 相同职责
src/filter/ src/exception/ ACE 用 @Catch 注解替代 Filter 概念
bootstrap.ts 扫描装饰器 main.cj 显式 import ACE 无运行时扫描,import 即注册
configuration.ts config/application.toml ACE 统一用 TOML 配置,无 JS 配置文件