Files
lab/go/projects/lyra/docs/plans/2026-10-16-lyra-architecture.md
labmem 3280c8a7bc docs(lyra): 记录技术选型与分层架构
- 记录 Gin、PostgreSQL、Squirrel、sqlx 和 Goose 选型
- 明确 handler、service、model 调用链及 API 职责边界
- 整理目标目录结构、待定事项与后续验证要求
2026-10-16 14:52:32 +08:00

124 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lyra 技术选型与架构讨论记录
日期:2026-10-16
状态:HTTP 路由、数据访问、数据库迁移工具与目标目录结构已确认(结构见 [architecture.md](../architecture.md));本文档方案已获文档层面批准,代码实施尚未批准。
## 已确认
- Lyra 是个人 Go 后端,先承接 Polaris,未来承接其他项目接口。
- Polaris 采用一人管理、匿名公开浏览的模式。
- 已有服务器和 PostgreSQL;具体部署与连接配置尚未核实。
- 数据访问选择 Squirrel + sqlx,PostgreSQL 驱动使用 pgx stdlib。
- HTTP 路由选择 Gin;用户无明显接口风格偏好,结合文档与开发便利性作出选择。
- 数据库迁移工具选择 Goose。
- 本次确认仅涉及选型,不代表批准搭建代码或接入真实数据库。
## 数据访问约定
- 固定查询直接写 SQL,动态查询使用 Squirrel,统一由 sqlx 执行。
- Squirrel 使用 Dollar 占位符,普通值使用参数绑定,动态标识符采用服务端白名单。
- 使用带 Context 的执行方法;需要原子性的业务操作使用事务。
- 显式声明查询字段,处理可空值和 JOIN 列名冲突。
- 不同时引入 sqlc 或 golang-migrate。
- 已讨论的取舍:无代码生成步骤,但查询与结果映射主要在运行时校验;上游维护低频。
## 当前实现状态
- Lyra 仅有 go.mod 与 docs/ 下的文档,尚无接口、数据库或业务代码。
- Polaris 的 services 仍同步读取本地 JSON,未接入 Lyra。
- 未进行数据库连接或数据库结构修改。
## 架构建议(尚未定案)
- 一个 Go 服务;根据用户不希望按 Polaris 建立独立项目目录的偏好,调整为按技术职责组织代码,项目在 HTTP 路由上使用不同前缀。
- 各项目使用独立 HTTP 前缀,共享配置、日志、连接池等基础设施。
- PostgreSQL 可按项目划分 schema;这是组织边界,不等于权限隔离。
- 不提前建设微服务、统一用户中心或通用 CRUD 框架。
- 日志候选为标准库 slog,具体中间件与日志集成方式另行确认。
## 目录结构与边界(已确认)
2026-10-16 用户确认以下目标组织方式;同日确认将数据访问职责并入 model,不再设独立 internal/store 目录。完整结构记录见 [architecture.md](../architecture.md)。确认的是结构与职责划分,不代表立即创建所有文件或占位目录,代码实施仍需另行批准。
```text
lyra/
├── cmd/lyra/main.go
├── internal/
│ ├── api/
│ │ ├── links.go
│ │ ├── categories.go
│ │ └── settings.go
│ ├── config/
│ │ └── config.go
│ ├── postgres/
│ │ └── postgres.go
│ ├── router/
│ │ └── router.go
│ ├── handler/
│ │ └── links.go
│ ├── service/
│ │ ├── links.go
│ │ └── links_test.go
│ ├── model/
│ │ ├── link.go
│ │ ├── link_model.go
│ │ └── link_model_test.go
│ └── httpx/
│ └── response.go
├── migrations/
│ └── 00001_polaris_create_tables.sql
├── docs/plans/
├── README.md
├── go.mod
└── go.sum
```
- cmd/lyra 负责启动、依赖组装、调用路由注册和退出清理,不放业务规则。
- 用户提出统一管理接口请求与响应;推荐 internal/api,只放 HTTP 请求与响应 DTO 及其字段标签,按资源组织文件,不放 handler、业务逻辑或数据库查询。
- internal/router 注册路由与项目路径前缀;handler 处理参数绑定、DTO 转换与响应输出;service 管理业务规则;model 承载数据结构定义与数据访问(Squirrel/SQL 构造、sqlx 执行与结果映射),驱动为 pgx stdlib。测试与被测文件同目录。
- 树中 handler/service/model 仅列出链接资源示例;model/link.go 定义 Link 数据结构,model/link_model.go 定义数据访问与 SQL,model/link_model_test.go 测试查询,均为示例,实际文件随实现新增,不创建空占位目录或文件。
- handler 引用 API DTO 并转换为业务输入或响应;service/model 不依赖 API DTO 包,API DTO 包不引用 handler/service/model,避免传输契约和实现相互耦合或循环引用。
- 请求、响应与数据库模型职责分开,即使初期字段相近,也不默认直接把数据库模型暴露为响应;接受必要的显式转换代码。
- 主调用链为 handler→service→model→PostgreSQL;service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。
- 上述文件是职责划分建议,不要求简单接口机械地走完整三层,也不按每张表或每种类型机械复制一套层级与文件。
- HTTP 层可以依赖 Gin;service 和 model 使用标准 context.Context,不接收 gin.Context。
- 移除 platform 和 modules 分类层;配置、连接和 HTTP 辅助直接位于 internal/config、internal/postgres、internal/httpx,减少无必要的路径深度。httpx 只在需要统一响应输出时实现,不提前抽象用户、设置、通用 CRUD 等业务。
- go.sum 在引入依赖后由 Go 工具生成;测试文件随实际功能编写,不创建空占位文件。
- 新项目可以在路由上增加独立前缀并复用上述技术层目录,不强制增加项目目录。不同领域的同名类型需要明确命名;文件名本身不提供 Go 包级符号隔离。
- 路由前缀只区分 HTTP 入口,不自动隔离代码依赖、数据库或权限;业务所有权与鉴权仍需显式设计,不能因路径不同就假定隔离成立。
- PostgreSQL 按项目划分 schema 仍为建议,尚未确认;schema 不自动提供权限隔离。
### 外部示例参考
- 参考 https://github.com/vespeng/gin-pathway ,读取 main 分支 commit 67582d1。
- 借鉴其薄启动入口、显式依赖组装与统一错误处理思路;曾考虑其按业务分组,后按用户偏好改为技术分层,不直接复制具体代码或全部依赖。
- 该示例没有独立 API DTO 目录,部分数据库模型直接作为响应,本方案不沿用这一点。
- 不沿用其启动时同步表结构的机制;Lyra 仍使用已确认的 Goose 管理迁移,具体执行方式待确认。
- 不预建空 pkg、scripts、tests 等目录,不引入当前无需求的 Redis、ORM 或全局数据库单例。
- internal/api 与根 api 都是可行布局,小写 api 是命名习惯;推荐 internal/api 是因为当前 DTO 仅供 Lyra 内部使用,而非 Go 语言强制要求。
- 另参考 https://github.com/llmons/go-dianping ,读取 main 分支 commit cc7fbcf9:借鉴其独立 API 响应定义、技术分层与构造函数注入依赖;不照搬 ORM 生成层及缓存设施。
- go-dianping 的部分请求与响应复用数据库模型,Lyra 的独立请求/响应 DTO 方案不沿用这一做法。
## 推荐迁移执行方式(待用户确认)
- 起步采用 Goose CLI,在独立部署步骤中显式执行 SQL 迁移;Web 服务启动时不自动迁移。
- 迁移成功后再启动或更新服务;迁移失败则停止后续发布,检查实际状态。
- 同一个 Lyra 数据库初期使用一个 migrations 目录和统一版本序列,文件名标注所属模块;不让不同模块共用版本表却各自维护重复版本号。
- 部署流程保证同一数据库的迁移串行执行;如以后存在多个迁移入口或并发发布,再选择带数据库锁的迁移执行入口,不假定事务本身能防止并发迁移。
- 优先 SQL 迁移,非事务 DDL 单独设计;生产回退需要评估数据影响,不自动执行 Down。
- 表结构迁移与 Polaris JSON 初次导入分开设计,不能把 Goose 等同于业务数据导入工具。
## 后续讨论顺序
1. 确认 Goose 的执行入口、串行执行或数据库锁方案(技术分层与路由前缀组织已确认,见 [architecture.md](../architecture.md))。
2. 确认首阶段范围:建议先做 JSON 导入、只读 API 和 Polaris 接入;管理写入另行确定认证方式。
3. 编写实施与验证方案,获得明确批准后再实施。
## 后续实施方案必须覆盖的验证
- 使用隔离的 PostgreSQL 测试环境验证迁移、核心查询与事务,不默认操作现有真实数据。
- 核对导入前后的分类、链接、排序和置顶语义。
- 接入 Polaris 时验证 service 异步化、动态分类路由、缓存与后端故障处理。
- 管理写接口上线前验证鉴权,不能以隐藏入口代替权限控制。