Resolved 48 identified issues across 5 remediation batches: Critical Fixes (2/2 = 100%): - Removed duplicate "System Architec" directory with 4 archived files - Fixed broken PARA Notes wikilinks in 2 Outline.md files High Priority (14/15 = 93%): - Consolidated 10+ duplicate file pairs to canonical locations - Added frontmatter to 30 files in 200-area (now 100% coverage) - Relocated orphaned image with updated reference - Removed security-sensitive file duplicates Medium Priority (32/41 = 78%): - Deleted 4 empty files (0-15 bytes each) - Relocated misplaced files to proper PARA categories - Improved archive organization structure File Changes: - Modified: 33 files (frontmatter + wikilink fixes) - Moved: 16 files (to archive or new locations) - Deleted: 6 files (duplicates after archival) - Created: 25 files (archived copies + documentation) Vault Health Improvement: - Frontmatter coverage: 43% → 75% - Broken wikilinks: 2 → 0 - Duplicate files: 10+ → 0 - Empty files: 4 → 0 - Overall health score: 6.5/10 → 8.5/10 Documentation: - Created comprehensive remediation plan and batch reports in copilot/ - All changes tracked with detailed change reports - No data loss - duplicates archived, not deleted 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
11 KiB
Executable File
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:
/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/infradirectories. -
Move domain-level structs (telegram, metadata) into
/internal/domain. -
Move parsing logic into
/internal/adapter/parser. -
Add
/pkg/difor Wire. -
Update
go.modand 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
Configstruct (NATS, Postgres, logging, etc.). -
Remove global singleton config; pass
*Configexplicitly via DI. -
Add config validation logic (e.g. non-empty URLs, timeouts > 0).
Example (参考实现思路)
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.goloads config via Koanf correctly。 -
No global config singletons remain。
-
Unit tests can construct
Configdirectly,方便单测。
Phase 3 — Wire Dependency Injection
Goal: Remove manual wiring logic, centralize dependency creation.
Tasks
-
Create
/pkg/di/wire.gowith 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.goto use Wire-generatedInitialize()(或类似函数名)。
Example Wire skeleton
//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 消费逻辑骨架
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 骨架
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 }
-
-
核心流程:
-
收到 NATS 消息(由 consumer 调用
HandleMessage或类似接口) -
调用
Parser.Parse得到domain.Telegram -
调用
Repository.Insert...写入数据库 -
返回成功/失败,由 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 完成的标志:
-
启动链路:
-
使用 Koanf 加载配置。
-
使用 Wire 完成依赖注入。
-
使用 nats.go JetStream 消费消息。
-
使用 pgx 将数据写入 PostgreSQL。
-
-
架构层次清晰:
-
internal/domain无外部依赖。 -
internal/app只依赖 domain + 抽象接口。 -
internal/infra只负责技术细节。 -
cmd只启动,不包含业务逻辑。
-
-
旧技术栈完全移除:
- Watermill、Hasura、GraphQL、Viper、全局单例全部删除。
-
数据流全链路可工作:
- NATS → Parser → Domain Model → Repository → PostgreSQL 全流程可验证。