Files
my-vault/01_Projects/AI-Development/Cursor-IDE/go-caatsm-refactor-plan.md
T

477 lines
11 KiB
Markdown
Raw 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.
go-casstm
---
# go-caatsm Refactor Plan
## Objective
Refactor the project to adopt a modern, maintainable, and scalable architecture using:
- Clean Architecture (app / domain / adapter / infra)
- nats.go JetStream (replace Watermill)
- PostgreSQL pgx (replace Hasura GraphQL)
- Koanf configuration system (replace Viper)
- Google Wire for dependency injection
- Structured logging (+ optional metrics/tracing)
Goal: improve reliability, performance, extensibility, and professional engineering quality.
---
## High-Level Architecture
Refactor into the following structure:
```text
/cmd/receiver/main.go # entrypoint using wire-generated injector
/config/config.toml
/internal
/app # Orchestrates flows
processor.go
listener.go
/domain
telegram.go
/adapter
parser/
mapper/
/infra
config/ # koanf loader
nats/ # jetstream consumer/publisher
postgres/ # pgx repository
log/ # zap logger
/pkg/di/wire.go # wire DI root
```
Principles:
- Domain is pure Go types (no external imports).
- App orchestrates: NATS msg → parser → domain → repository.
- Infra handles external concerns (NATS, PostgreSQL, config, logging).
- Adapter performs mapping between infra/domain.
- `cmd` 只负责启动,不包含业务逻辑。
---
## Phase 1 — Project Structure Migration
**Goal:** Introduce new directories without breaking existing code.
### Tasks
- Create new `/internal/app`, `/internal/domain`, `/internal/adapter`, `/internal/infra` directories.
- Move domain-level structs (telegram, metadata) into `/internal/domain`.
- Move parsing logic into `/internal/adapter/parser`.
- Add `/pkg/di` for Wire.
- Update `go.mod` and imports accordingly.
### Acceptance Criteria
- Project builds successfully.
- Existing behavior unchanged(只是结构调整,不改逻辑).
---
## Phase 2 — Replace Viper → Koanf
**Goal:** Introduce reliable & explicit config loading.
### Tasks
- Add Koanf loader at `/internal/infra/config/koanf.go`.
- Load from file (`config/config.toml`) then environment (`CAATSM_` prefix).
- Define a strongly typed `Config` struct (NATS, Postgres, logging, etc.).
- Remove global singleton config; pass `*Config` explicitly via DI.
- Add config validation logic (e.g. non-empty URLs, timeouts > 0).
### Example (参考实现思路)
```go
func LoadConfig() (*Config, error) {
k := koanf.New(".")
if err := k.Load(file.Provider("config/config.toml"), toml.Parser()); err != nil {
return nil, err
}
if err := k.Load(env.Provider("CAATSM_", ".", func(s string) string {
return strings.ToLower(strings.TrimPrefix(s, "CAATSM_"))
}), nil); err != nil {
return nil, err
}
var cfg Config
if err := k.Unmarshal("", &cfg); err != nil {
return nil, err
}
return &cfg, cfg.Validate()
}
```
### Acceptance Criteria
- Running `go run cmd/receiver/main.go` loads config via Koanf correctly。
- No global config singletons remain。
- Unit tests can construct `Config` directly,方便单测。
---
## Phase 3 — Wire Dependency Injection
**Goal:** Remove manual wiring logic, centralize dependency creation.
### Tasks
- Create `/pkg/di/wire.go` with injectors.
- Provide constructors:
- `ProvideConfig` (Koanf)
- `ProvideLogger` (Zap)
- `ProvideJetStream` (NATS)
- `ProvideDB` (pgxpool)
- `ProvideRepository` (Postgres repo)
- `NewMessageProcessor` (app layer)
- Generate `wire_gen.go`.
- Modify `cmd/receiver/main.go` to use Wire-generated `Initialize()` (或类似函数名)。
### Example Wire skeleton
```go
//go:build wireinject
package di
import (
"github.com/google/wire"
"go-caatsm/internal/app"
"go-caatsm/internal/infra/config"
"go-caatsm/internal/infra/log"
"go-caatsm/internal/infra/nats"
"go-caatsm/internal/infra/postgres"
)
func InitializeProcessor() (*app.MessageProcessor, error) {
wire.Build(
config.ProvideConfig,
log.ProvideLogger,
nats.ProvideJetStream,
postgres.ProvideDB,
postgres.ProvideRepository,
app.NewMessageProcessor,
)
return &app.MessageProcessor{}, nil
}
```
### Acceptance Criteria
- Project builds with Wire DI。
- main.go 只负责调用 `InitializeProcessor()` 和启动 processor。
- 新增依赖时只需修改 Wire graph,不用手动改 main.go。
---
## Phase 4 — Replace Watermill → nats.go JetStream
**Goal:** Gain full control over message flow, retries, DLQ.
### Tasks
- 引入 `/internal/infra/nats/jetstream.go`,实现:
- 连接创建(`nats.Connect``js, _ := nc.JetStream()`
- Stream + Consumer 自动创建(如不存在则创建)
- 使用 Pull Subscribe 模式(`PullSubscribe`
- 手动 ACK / NAK
- 简单 Retry 策略(MaxDeliveries + NAK
- 死信队列(DLQ stream/subject
- 实现批量抓取(例如 `Fetch(50, MaxWait(...))`)。
- 实现 `Consumer.Start(ctx)`,内部循环读取消息并调用 `app.MessageProcessor.Handle()`
### Example 消费逻辑骨架
```go
func (c *Consumer) Start(ctx context.Context) error {
sub, err := c.js.PullSubscribe(c.subject, c.consumerName)
if err != nil {
return err
}
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
msgs, err := sub.Fetch(50, nats.MaxWait(2*time.Second))
if err != nil {
if errors.Is(err, nats.ErrTimeout) {
continue
}
c.logger.Error("fetch failed", zap.Error(err))
continue
}
for _, msg := range msgs {
if err := c.handler.Handle(ctx, msg.Data); err != nil {
_ = msg.Nak()
continue
}
_ = msg.Ack()
}
}
}
```
### Acceptance Criteria
- 消费逻辑完全基于 nats.go,不再依赖 Watermill。
- ACK / NAK 正常工作,可通过 JetStream 管理界面/CLI 查看重试与 DLQ。
- 可通过配置控制批量大小、等待时间、MaxDeliveries 等。
---
## Phase 5 — Replace Hasura GraphQL → PostgreSQL (pgx)
**Goal:** High-performance & reliable write pipeline.
### Tasks
- 添加 `/internal/infra/postgres/db.go`,使用 `pgxpool.Pool` 管理连接。
- 添加 `/internal/infra/postgres/repository.go`
- `InsertOne(ctx, telegram domain.Telegram) error`
- `InsertBatch(ctx, []domain.Telegram) error`(使用 `CopyFrom`
- 定义 telegram 表结构(如已存在则对齐 struct 和列)。
- 增加必要索引(如 `uuid`、时间戳、业务 key 等)。
- 删除 Hasura GraphQL client、genqlient 相关代码。
### Example CopyFrom 骨架
```go
func (r *Repository) InsertBatch(ctx context.Context, msgs []domain.Telegram) error {
rows := make([][]any, len(msgs))
for i, m := range msgs {
rows[i] = []any{
m.UUID,
m.Raw,
m.ParsedJSON,
m.CreatedAt,
}
}
_, err := r.pool.CopyFrom(
ctx,
pgx.Identifier{"aviation_telegrams"},
[]string{"uuid", "raw", "parsed", "created_at"},
pgx.CopyFromRows(rows),
)
return err
}
```
### Acceptance Criteria
- 消息数据成功写入 PostgreSQL。
- 批量写入时使用 CopyFrom,性能明显优于单条 INSERT。
- Hasura / GraphQL 相关依赖从代码和 go.mod 中移除。
---
## Phase 6 — Application Layer (Processor)
**Goal:** Create clean orchestrator for the message lifecycle.
### Tasks
-`/internal/app/processor.go` 实现 `MessageProcessor`
- 接口定义:
- `type Parser interface { Parse(raw []byte) (domain.Telegram, error) }`
- `type Repository interface { InsertOne / InsertBatch }`
- 核心流程:
1. 收到 NATS 消息(由 consumer 调用 `HandleMessage` 或类似接口)
2. 调用 `Parser.Parse` 得到 `domain.Telegram`
3. 调用 `Repository.Insert...` 写入数据库
4. 返回成功/失败,由 caller 决定 ACK/NAK
-`/internal/adapter/parser` 中处理具体报文解析逻辑,保持 domain 纯净。
### Acceptance Criteria
- Processor 不依赖具体的 NATS / pgx 类型,只依赖接口。
- Parser / Repository 可以在测试中替换为 mock。
- 业务流程清晰、单一职责。
---
## Phase 7 — Logging & Observability
**Goal:** Unify logging and enable production-ready debugging.
### Tasks
-`/internal/infra/log/logger.go` 实现 Zap 初始化(支持 dev/prod 模式)。
- 将 main、consumer、processor、repository 中的 `fmt.Println` 替换为结构化日志。
- 每条关键日志附加必要 context 字段:
- `message_id`
- `subject`
- `stream`
- `attempt`
- (可选)添加 Prometheus metrics(处理量、错误数、重试次数)。
### Acceptance Criteria
- 日志输出统一,方便在 Loki / ELK 中检索。
- 出错时能通过日志快速定位是哪个环节(NATS 消费 / 解析 / DB 写入)出了问题。
---
## Phase 8 — Remove Dead Code & Cleanup
**Goal:** Remove legacy patterns and unused modules.
### Tasks
- 移除 Watermill 相关代码与依赖。
- 移除 Hasura / genqlient 相关代码与依赖。
- 移除 Viper 配置加载器与全局单例。
- 删除不再使用的 handler / repository 实现。
- 运行 `go mod tidy` 清理依赖。
- 检查 Taskfile / Makefile,更新为新的启动、测试命令。
### Acceptance Criteria
- `go test ./...``go build ./...` 均成功。
- go.mod 中不再包含 Watermill / Hasura / genqlient / Viper。
- 代码中不再有全局 Config/Logger 单例。
---
## Final Acceptance Criteria
Refactor 完成的标志:
1. **启动链路:**
- 使用 Koanf 加载配置。
- 使用 Wire 完成依赖注入。
- 使用 nats.go JetStream 消费消息。
- 使用 pgx 将数据写入 PostgreSQL。
2. **架构层次清晰:**
- `internal/domain` 无外部依赖。
- `internal/app` 只依赖 domain + 抽象接口。
- `internal/infra` 只负责技术细节。
- `cmd` 只启动,不包含业务逻辑。
3. **旧技术栈完全移除:**
- Watermill、Hasura、GraphQL、Viper、全局单例全部删除。
4. **数据流全链路可工作:**
- NATS → Parser → Domain Model → Repository → PostgreSQL 全流程可验证。
---