feat(lyra)!: 接入分类查询并集中组装依赖
- 使用 Squirrel 与 sqlx 实现分类查询 - 集中组装依赖并向 handler 注入 model 接口 - 显式转换分类响应 DTO,统一空列表输出 - 补充路由与 handler 测试,同步架构文档 BREAKING CHANGE: 将 GET /hello 替换为 GET /test,响应由问候字符串改为分类列表
This commit is contained in:
@@ -2,68 +2,74 @@
|
||||
|
||||
日期:2026-10-17
|
||||
|
||||
本文记录已确认的目标目录结构与职责边界,并与当前真实实现同步。目录树是**目标结构**,并非全部已存在:标注“待新增”的文件或目录尚未创建,当前已有 Go 代码的是 `cmd/lyra`、`internal/{api,config,handler,httpx,router}`;`internal/{model,postgres,service}` 仅含 `AGENTS.md`(记录职责与依赖约束),暂不构成 Go 包。选型与讨论过程见 [plans/2026-10-16-lyra-architecture.md](plans/2026-10-16-lyra-architecture.md);handler/service 业务分包的调整记录见 [plans/2026-10-17-business-package-layout.md](plans/2026-10-17-business-package-layout.md)。
|
||||
本文记录已确认的目标目录结构与职责边界,并与当前真实实现同步。目录树是**目标结构**,并非全部已存在:标注“待新增”的文件尚未创建;标注“占位”的目录(`internal/postgres`、`internal/service`)仅含 `AGENTS.md`(记录职责与依赖约束),暂不构成 Go 包。当前已有 Go 代码的是 `cmd/lyra` 与 `internal/{api,config,handler,httpx,model,router}`。仓库内另有本地 `.env.local`(已被 gitignore、不入库)等文件,不属于目标结构,不在此展开。选型与讨论过程见 [plans/2026-10-16-lyra-architecture.md](plans/2026-10-16-lyra-architecture.md);handler/service 业务分包的调整记录见 [plans/2026-10-17-business-package-layout.md](plans/2026-10-17-business-package-layout.md)。
|
||||
|
||||
## 已确认技术栈
|
||||
|
||||
- 已引入:Gin v1.12.0(HTTP 路由)、Viper v1.21.0(配置读取,TOML)。`go.sum` 已由 Go 工具生成。
|
||||
- 目标选型(选型已确认,尚未作为代码依赖引入):PostgreSQL、Squirrel + sqlx(pgx stdlib 驱动)、Goose(数据库迁移;迁移 SQL 已就位,以独立 goose CLI v3.29.0 执行,Go 依赖未引入)。
|
||||
- 已引入并使用:Gin v1.12.0(HTTP 路由)、Viper v1.21.0(配置读取,TOML)、godotenv v1.5.1(启动时加载本地 `.env.local`)、sqlx v1.4.0 + Squirrel v1.5.4(查询构造与执行)、pgx v5.11.0 stdlib(PostgreSQL 驱动,由 sqlx 以 "pgx" 驱动名使用)。`go.sum` 已由 Go 工具生成。
|
||||
- Goose:迁移 SQL 已就位,以独立 goose CLI(v3.29.0)执行,Go 依赖尚未引入。
|
||||
- PostgreSQL 已在启动时连接;连接代码当前在入口 `main`,收拢到 `internal/postgres` 是目标结构。
|
||||
|
||||
## 当前进度
|
||||
|
||||
- 程序入口在 module 根目录以 `go run ./cmd/lyra` 启动:按相对当前工作目录的 `etc/lyra.toml` 加载配置(Viper,`mapstructure` 标签),已启动 HTTP 服务,监听地址与端口来自配置文件;换目录执行会找不到配置。
|
||||
- `app.env` 当前直接传给 `gin.SetMode`,取值仅 `debug`/`release`/`test`(Gin 的合法值);没有 dev/prod 等环境映射。
|
||||
- 已注册 `GET /hello`:`internal/router/router.go` 的 `RegisterRoutes` 将其指向 `handler.Hello`,经 `httpx.OK` 输出 `api.Response`;路由注册配有 httptest 回归测试,不监听实际端口、不连接数据库。
|
||||
- 已定义公共响应契约:`api.Response`(success/data/error)、`api.ErrorInfo`(code/message/debug)与错误码类型 `api.ErrorCode` 及其默认文案 `Message()`。
|
||||
- 已实现响应输出辅助 `httpx.OK`、`httpx.Fail`。
|
||||
- 程序入口在 module 根目录以 `go run ./cmd/lyra` 启动,流程为:`godotenv.Load` 读取本地 `.env.local`(不覆盖已存在的进程环境变量)→ `config.Load` 读取相对当前工作目录的 `etc/lyra.toml`(Viper,`mapstructure` 标签)→ `gin.SetMode` → 以 `gin.Default()` 创建引擎并 `SetTrustedProxies(nil)` 关闭代理信任 → `sqlx.ConnectContext` 建立 PostgreSQL 连接(失败即退出,`defer db.Close()`)→ `buildDependencies`/`buildHandlers` 组装依赖 → `RegisterRoutes` → 监听配置的地址与端口。`.env.local` 与 `etc/lyra.toml` 均按当前工作目录解析,换目录执行会找不到。连接与查询当前未设置超时。
|
||||
- 已注册 `GET /test`:`internal/router/router.go` 的 `RegisterRoutes` 将其指向入口注入的 `router.Handlers.TestHandler`(`gin.HandlerFunc`,由 `handler.NewTestHandler(...).Hello()` 构造)。处理器调用 `model.CategoryModel.FindAll` 查询 `public.categories`——当前固定 `sort >= 1`、按 `sort`/`id` 排序、`limit 10`——经显式转换为本轮新增的 `api.CategoryResponse` 后由 `httpx.OK` 输出;查询出错经 `httpx.Fail` 返回 500 与 `api.CodeInternalError`。该接口用于验证 handler→model→数据库链路,不是完整业务 API。
|
||||
- `internal/model` 已落地:`Category` 结构(id/slug/title/sort)、`CategoryModel` 接口与 `defaultCategoryModel` 实现——Squirrel 构造 SQL(Dollar 占位符),sqlx `SelectContext` 执行与映射。
|
||||
- 已定义公共响应契约:`api.Response`(success/data/error)、`api.ErrorInfo`(code/message/debug)与错误码类型 `api.ErrorCode` 及其默认文案 `Message()`;响应输出辅助 `httpx.OK`、`httpx.Fail` 已实现并被 handler 使用。
|
||||
- 配置:`database.password` 绑定了环境变量 `PG_PASSWORD`(可经 `.env.local` 或进程环境提供),其余配置项暂不支持环境变量覆盖;没有校验与默认值。`Redis` 配置项已定义但暂无消费方。
|
||||
- 测试:路由注册有 httptest 回归测试,覆盖 `GET /test` 注入替身处理器的调用,以及未知路径/不支持方法返回 404;handler 有基于假 `CategoryModel` 的替身测试,覆盖 DTO 转换、空结果输出 `data:[]`、模型出错返回 500 且不泄漏内部错误信息、请求 context 透传。两类测试均不监听实际端口、不连接数据库,因此不证明真实 SQL 可用——连接真实数据库的端到端验证本轮未做。
|
||||
- 数据库迁移已就位:`migrations/00001_init.sql`(`set_updated_at()` 函数、users/categories/links 三张表、约束、注释、索引与触发器,建库内容与 [demo.sql](demo.sql) 一致),由手工 goose CLI(v3.29.0)执行,见 [../migrations/README.md](../migrations/README.md)。
|
||||
- 尚未实现:真实业务逻辑(`/hello` 只是 HTTP 通路示例)、Go 应用侧数据库访问与 Goose Go 依赖;配置校验、环境变量覆盖与默认值;`ErrorInfo.Debug` 的填充与按环境开关。日志方案未定。
|
||||
- 尚未实现:业务 service 层与完整业务 API;连接收拢到 `internal/postgres`(当前连接代码在入口 `main`);Goose Go 依赖;配置校验与默认值;`ErrorInfo.Debug` 的填充与按环境开关;连接/查询超时。日志方案未定。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
lyra/
|
||||
├── cmd/lyra/main.go # 启动、依赖组装、退出清理
|
||||
├── cmd/lyra/
|
||||
│ ├── main.go # 启动流程:加载 .env.local 与配置、连接数据库、组装依赖
|
||||
│ └── dependency.go # Dependencies 与 handler 组装
|
||||
├── internal/
|
||||
│ ├── api/ # HTTP 请求/响应 DTO、错误码与响应契约(平铺,按资源分文件)
|
||||
│ │ ├── response.go # Response/ErrorInfo 结构
|
||||
│ │ ├── code.go # ErrorCode 与错误码常量、默认文案
|
||||
│ │ ├── links.go # links 资源 DTO 示例(待新增)
|
||||
│ │ ├── categories.go # 待新增
|
||||
│ │ └── settings.go # 待新增
|
||||
│ ├── api/ # HTTP 请求/响应 DTO、错误码与响应契约(平铺,按资源分文件)
|
||||
│ │ ├── response.go # Response/ErrorInfo 结构
|
||||
│ │ ├── code.go # ErrorCode 与错误码常量、默认文案
|
||||
│ │ ├── category.go # CategoryResponse 分类响应 DTO
|
||||
│ │ ├── links.go # links 资源 DTO 示例(待新增)
|
||||
│ │ └── settings.go # 待新增
|
||||
│ ├── config/
|
||||
│ │ └── config.go # 配置定义与加载
|
||||
│ ├── postgres/
|
||||
│ │ └── postgres.go # 数据库连接(待新增)
|
||||
│ │ └── config.go # 配置定义与加载
|
||||
│ ├── postgres/ # 数据库连接的目标位置(占位:仅 AGENTS.md,连接暂在 cmd/lyra/main.go)
|
||||
│ ├── router/
|
||||
│ │ └── router.go # 路由注册、项目路径前缀
|
||||
│ │ ├── router.go # 路由注册、项目路径前缀
|
||||
│ │ └── router_test.go # 路由 httptest 回归测试
|
||||
│ ├── handler/
|
||||
│ │ ├── hello.go # GET /hello 处理(服务级简单接口,平铺在根包)
|
||||
│ │ ├── links.go # 参数绑定、DTO 转换、响应输出(待新增)
|
||||
│ │ └── polaris/ # 按明确业务分包的示例(待新增,不预建空包)
|
||||
│ ├── service/
|
||||
│ │ ├── links.go # 业务规则、事务协调(待新增)
|
||||
│ │ ├── links_test.go # 测试与源文件同目录(待新增)
|
||||
│ │ └── polaris/ # 按明确业务分包的示例(待新增,不预建空包)
|
||||
│ │ ├── test.go # GET /test 处理:调用 model、显式 DTO 转换、响应输出
|
||||
│ │ ├── test_test.go # handler 替身测试
|
||||
│ │ ├── links.go # 参数绑定、DTO 转换、响应输出(待新增)
|
||||
│ │ └── polaris/ # 按明确业务分包的示例(待新增,不预建空包)
|
||||
│ ├── service/ # 业务规则、事务协调(占位:仅 AGENTS.md)
|
||||
│ │ ├── links.go # 业务规则、事务协调(待新增)
|
||||
│ │ ├── links_test.go # 测试与源文件同目录(待新增)
|
||||
│ │ └── polaris/ # 按明确业务分包的示例(待新增,不预建空包)
|
||||
│ ├── model/
|
||||
│ │ ├── link.go # Link 数据结构定义(待新增)
|
||||
│ │ ├── link_model.go # 数据访问对象:CRUD 与 SQL(待新增)
|
||||
│ │ └── link_model_test.go # 查询测试(待新增)
|
||||
│ │ ├── category.go # Category 结构、CategoryModel 接口与 Squirrel+sqlx 实现
|
||||
│ │ ├── link.go # Link 数据结构定义(待新增)
|
||||
│ │ ├── link_model.go # 数据访问对象:CRUD 与 SQL(待新增)
|
||||
│ │ └── link_model_test.go # 查询测试(待新增)
|
||||
│ └── httpx/
|
||||
│ └── response.go # 公共响应输出辅助(OK/Fail)
|
||||
│ └── response.go # 公共响应输出辅助(OK/Fail)
|
||||
├── etc/
|
||||
│ └── lyra.toml # 运行配置(App/HTTP)
|
||||
├── migrations/ # Goose SQL 迁移文件(00001_init.sql 初始迁移 + README)
|
||||
│ └── lyra.toml # 运行配置(App/HTTP/Database/Redis)
|
||||
├── migrations/ # Goose SQL 迁移文件(00001_init.sql 初始迁移 + README)
|
||||
├── docs/
|
||||
│ ├── plans/
|
||||
│ └── architecture.md # 本文
|
||||
│ └── architecture.md # 本文
|
||||
├── README.md
|
||||
├── go.mod
|
||||
└── go.sum # 已生成
|
||||
└── go.sum # 已生成
|
||||
```
|
||||
|
||||
- 标注“待新增”的文件只是职责分布示例,随实现按需新增,不预建空文件或空目录;`internal/` 各目录均以 `AGENTS.md` 记录职责与依赖约束。
|
||||
- model 下的 `link*.go` 展示一个资源在各层的文件分布;分类、设置等资源按同样方式在 api/handler/service/model 新增对应文件。首个迁移已落地为 `migrations/00001_init.sql`。
|
||||
- 标注“待新增”的文件只是职责分布示例,随实现按需新增,不预建空文件或空目录;`internal/` 各目录均以 `AGENTS.md` 记录职责与依赖约束(树中未逐一画出)。`internal/postgres`、`internal/service` 目前仅有 `AGENTS.md`,为占位目录,不构成 Go 包。
|
||||
- model 下的 `link*.go` 展示一个资源在各层的文件分布;分类资源已按此方式在 api/model 落地(`api/category.go`、`model/category.go`,handler 为链路验证用的 `test.go`),设置等资源按同样方式新增。首个迁移已落地为 `migrations/00001_init.sql`。
|
||||
- `handler/polaris/`、`service/polaris/` 仅示意业务分包的形态:实际分包与否随业务边界明确时决定,路由、api、model 不照搬同样的分包。
|
||||
|
||||
## 组织原则
|
||||
@@ -76,21 +82,22 @@ lyra/
|
||||
|
||||
## 层间边界
|
||||
|
||||
- 真实业务的主调用链为 handler→service→model→PostgreSQL;服务级简单接口可以不经过 service/model(当前 `/hello` 即是)。service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。
|
||||
- 请求/响应 DTO 集中在 internal/api,与内部模型分开;即使初期字段相近,也不默认把数据库模型直接暴露为响应,接受必要的显式转换代码。
|
||||
- 真实业务的主调用链为 handler→service→model→PostgreSQL;简单接口可以不经过 service——当前 `GET /test` 即由 handler 直接调用 model(service 层尚未实现)。service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。
|
||||
- 请求/响应 DTO 集中在 internal/api,与内部模型分开;即使初期字段相近,也不默认把数据库模型直接暴露为响应,接受必要的显式转换代码(分类列表已经 handler 的 `toCategoryResponses` 转为 `api.CategoryResponse`)。
|
||||
- handler 引用 API DTO 完成绑定与转换;service/model 不依赖 Gin,也不依赖 internal/api DTO;api 包不反向引用 handler/service/model,避免传输契约与实现耦合或循环引用。
|
||||
- internal/api 拥有响应契约与业务错误码(`Response`/`ErrorInfo`/`ErrorCode`);不依赖 Gin。
|
||||
- internal/httpx 负责响应输出:`OK` 固定 HTTP 200;`Fail` 接收 HTTP 状态码与业务错误码,`message` 为空时回退错误码默认文案。HTTP 状态码表示传输/结果类别,`ErrorCode` 表示自定义业务错误,两者分开。
|
||||
- internal/router 负责把 handler 挂到路由上:当前注册 `GET /hello` 指向 `handler.Hello`,后续按业务项目路径前缀(如 `/api/polaris`)扩展。
|
||||
- 配置由入口 `main` 加载后组装并传递:`internal/config` 只负责配置定义与加载,运行配置来自 `etc/lyra.toml`;当前 `app.env` 直接传给 `gin.SetMode`(仅 `debug`/`release`/`test`),HTTP 地址端口供启动使用,数据库相关配置尚未有消费方。
|
||||
- model 承载数据结构定义与数据访问:Squirrel/SQL 构造、sqlx 执行与结果映射,驱动为 pgx stdlib;不是 ORM 自动持久化,也不要求每种类型机械建立一套结构。
|
||||
- internal/router 负责把 handler 挂到路由上:当前注册 `GET /test`,指向入口组装注入的 `Handlers.TestHandler`,后续按业务项目路径前缀(如 `/api/polaris`)扩展。
|
||||
- 配置由入口 `main` 加载后组装并传递:`internal/config` 只负责配置定义与加载,运行配置来自 `etc/lyra.toml`;当前 `app.env` 直接传给 `gin.SetMode`(仅 `debug`/`release`/`test`),HTTP 地址端口供启动使用,数据库相关配置由入口 `main` 组装 DSN 并用于连接(`database.password` 可来自 `PG_PASSWORD`),`Redis` 配置暂无消费方。
|
||||
- model 承载数据结构定义与数据访问:Squirrel/SQL 构造、sqlx 执行与结果映射,驱动为 pgx stdlib(已按此落地 `internal/model/category.go`);不是 ORM 自动持久化,也不要求每种类型机械建立一套结构。
|
||||
- service/model 使用标准 context.Context,不接收 gin.Context。
|
||||
|
||||
## 尚未确定 / 待实现
|
||||
|
||||
以下均为计划中的推荐或待确认项,尚未实现,也未定为已决:
|
||||
|
||||
- 配置校验、环境变量覆盖与默认值:尚未实现(当前无任何校验、覆盖或默认值)。
|
||||
- 配置校验与默认值:尚未实现。环境变量覆盖当前仅 `database.password` 绑定 `PG_PASSWORD`,其余配置项不支持。
|
||||
- 连接与查询超时:尚未设置,也未确定超时策略。
|
||||
- `ErrorInfo.Debug` 的填充与按环境(dev)开关:尚未实现,目前仅有字段。
|
||||
- 分页响应约定(如 data/meta 结构)与统一错误处理中间件:尚未引入,仅为历史讨论。
|
||||
- Goose 迁移的部署执行入口已确定为手工 CLI(见 [../migrations/README.md](../migrations/README.md)),并发控制以“单操作者串行执行”为约定,数据库锁未引入,服务启动不自动迁移。
|
||||
|
||||
Reference in New Issue
Block a user