refactor(supply-api): split runtime store bundle builders

This commit is contained in:
Your Name
2026-04-15 23:27:11 +08:00
parent 22575bdd82
commit 39c4a11ff9
3 changed files with 212 additions and 24 deletions

View File

@@ -0,0 +1,124 @@
# Supply API Store Bundle Split Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:**`supply-api/internal/app/runtime.go``buildStoreBundle` 的 DB/in-memory 双分支拆成两个更小的 helper降低单函数分支复杂度并保持现有语义不变。
**Architecture:** 保留 `buildStoreBundle` 作为总入口,只负责根据 `db` 是否存在分发到 `buildDBStoreBundle``buildMemoryStoreBundle`。先补 helper 级失败测试,再复用现有 `buildStoreBundle` 回归测试验证委派后的行为不变。
**Tech Stack:** Go, Go test
---
### Task 1: 提取 DB-backed store helper
**Files:**
- Modify: `supply-api/internal/app/runtime.go`
- Modify: `supply-api/internal/app/runtime_test.go`
**Step 1: Write the failing test**
```go
func TestBuildDBStoreBundle_InitializesDatabaseOnlyDependencies(t *testing.T) {
bundle := buildDBStoreBundle(&repository.DB{})
if bundle.tokenStatusRepo == nil {
t.Fatal("expected token status repo")
}
}
```
**Step 2: Run test to verify it fails**
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildDBStoreBundle_InitializesDatabaseOnlyDependencies' -v`
Expected: FAIL因为 helper 尚不存在
**Step 3: Write minimal implementation**
```go
func buildDBStoreBundle(db *repository.DB) runtimeStoreBundle { ... }
```
**Step 4: Run test to verify it passes**
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildDBStoreBundle_InitializesDatabaseOnlyDependencies' -v`
Expected: PASS
**Step 5: Commit**
```bash
git add supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go
git commit -m "refactor(supply-api): extract db store bundle builder"
```
### Task 2: 提取内存 store helper
**Files:**
- Modify: `supply-api/internal/app/runtime.go`
- Modify: `supply-api/internal/app/runtime_test.go`
**Step 1: Write the failing test**
```go
func TestBuildMemoryStoreBundle_DisablesDatabaseOnlyDependencies(t *testing.T) {
bundle := buildMemoryStoreBundle()
if bundle.idempotencyRepo != nil {
t.Fatal("expected nil idempotency repo")
}
}
```
**Step 2: Run test to verify it fails**
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildMemoryStoreBundle_DisablesDatabaseOnlyDependencies' -v`
Expected: FAIL因为 helper 尚不存在
**Step 3: Write minimal implementation**
```go
func buildMemoryStoreBundle() runtimeStoreBundle { ... }
```
**Step 4: Run test to verify it passes**
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildMemoryStoreBundle_DisablesDatabaseOnlyDependencies' -v`
Expected: PASS
**Step 5: Commit**
```bash
git add supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go
git commit -m "refactor(supply-api): extract memory store bundle builder"
```
### Task 3: 回归验证与收尾
**Files:**
- Modify: `supply-api/internal/app/runtime.go`
- Verify: `supply-api/internal/app/runtime_test.go`
**Step 1: Run focused tests**
Run: `cd "supply-api" && go test ./internal/app -run 'Test(Build(DB|Memory)StoreBundle_.*|BuildStoreBundle_.*)' -v`
Expected: PASS
**Step 2: Run package regression**
Run: `cd "supply-api" && go test ./internal/app ./cmd/supply-api ./internal/httpapi`
Expected: PASS
**Step 3: Run repo exit verification**
Run: `bash "scripts/ci/repo_integrity_check.sh"`
Expected: PASS
**Step 4: Check formatting**
Run: `git diff --check`
Expected: no output
**Step 5: Commit**
```bash
git add docs/plans/2026-04-15-supply-api-store-bundle-split-plan.md supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go
git commit -m "refactor(supply-api): split runtime store bundle builders"
```

View File

@@ -173,43 +173,51 @@ func buildRuntimeWithFactory(opts RuntimeOptions, factory runtimeFactory) (*Runt
}
func buildStoreBundle(db *repository.DB, logger logging.Logger) runtimeStoreBundle {
var bundle runtimeStoreBundle
if db != nil {
accountRepo := repository.NewAccountRepository(db.Pool)
packageRepo := repository.NewPackageRepository(db.Pool)
settlementRepo := repository.NewSettlementRepository(db.Pool)
usageRepo := repository.NewUsageRepository(db.Pool)
bundle.idempotencyRepo = repository.NewIdempotencyRepository(db.Pool)
bundle.tokenStatusRepo = repository.NewTokenStatusRepository(db.Pool)
bundle.accountStore = adapter.NewDBAccountStore(accountRepo)
bundle.packageStore = adapter.NewDBPackageStore(packageRepo)
bundle.settlementStore = adapter.NewDBSettlementStore(settlementRepo, accountRepo, db.Pool)
bundle.earningStore = adapter.NewDBEarningStore(usageRepo)
bundle.auditStore = audit.NewPostgresAuditStore(auditrepo.NewPostgresAuditRepository(db.Pool))
bundle.alertService = auditservice.NewAlertService(auditrepo.NewPostgresAlertRepository(db.Pool))
bundle.fkValidator = repository.NewForeignKeyValidator(db.Pool)
bundle := buildDBStoreBundle(db)
logger.Info("审计存储: 使用PostgreSQL (DB-backed)", nil)
logger.Info("告警存储: 使用PostgreSQL (DB-backed)", nil)
logger.Info("外键校验器: 已初始化 (PostgreSQL-backed)", nil)
return bundle
}
bundle.accountStore = adapter.NewInMemoryAccountStoreAdapter()
bundle.packageStore = adapter.NewInMemoryPackageStoreAdapter()
bundle.settlementStore = adapter.NewInMemorySettlementStoreAdapter()
bundle.earningStore = adapter.NewInMemoryEarningStoreAdapter()
bundle.auditStore = audit.NewMemoryAuditStore()
bundle.alertService = auditservice.NewAlertService(auditservice.NewInMemoryAlertStore())
bundle := buildMemoryStoreBundle()
logger.Warn("审计存储使用内存实现 (生产环境不应使用)", nil)
logger.Warn("告警存储使用内存实现 (仅开发环境允许)", nil)
logger.Warn("外键校验器未启用 (db不可用)", nil)
return bundle
}
func buildDBStoreBundle(db *repository.DB) runtimeStoreBundle {
accountRepo := repository.NewAccountRepository(db.Pool)
packageRepo := repository.NewPackageRepository(db.Pool)
settlementRepo := repository.NewSettlementRepository(db.Pool)
usageRepo := repository.NewUsageRepository(db.Pool)
return runtimeStoreBundle{
accountStore: adapter.NewDBAccountStore(accountRepo),
packageStore: adapter.NewDBPackageStore(packageRepo),
settlementStore: adapter.NewDBSettlementStore(settlementRepo, accountRepo, db.Pool),
earningStore: adapter.NewDBEarningStore(usageRepo),
auditStore: audit.NewPostgresAuditStore(auditrepo.NewPostgresAuditRepository(db.Pool)),
alertService: auditservice.NewAlertService(auditrepo.NewPostgresAlertRepository(db.Pool)),
fkValidator: repository.NewForeignKeyValidator(db.Pool),
tokenStatusRepo: repository.NewTokenStatusRepository(db.Pool),
idempotencyRepo: repository.NewIdempotencyRepository(db.Pool),
}
}
func buildMemoryStoreBundle() runtimeStoreBundle {
return runtimeStoreBundle{
accountStore: adapter.NewInMemoryAccountStoreAdapter(),
packageStore: adapter.NewInMemoryPackageStoreAdapter(),
settlementStore: adapter.NewInMemorySettlementStoreAdapter(),
earningStore: adapter.NewInMemoryEarningStoreAdapter(),
auditStore: audit.NewMemoryAuditStore(),
alertService: auditservice.NewAlertService(auditservice.NewInMemoryAlertStore()),
}
}
func buildSecurityBundle(
env string,
cfg *config.Config,

View File

@@ -93,6 +93,34 @@ func TestBuildStoreBundle_UsesInMemoryStoresWithoutDatabase(t *testing.T) {
}
}
func TestBuildMemoryStoreBundle_DisablesDatabaseOnlyDependencies(t *testing.T) {
bundle := buildMemoryStoreBundle()
if bundle.accountStore == nil {
t.Fatal("expected account store")
}
if bundle.packageStore == nil {
t.Fatal("expected package store")
}
if bundle.settlementStore == nil {
t.Fatal("expected settlement store")
}
if bundle.earningStore == nil {
t.Fatal("expected earning store")
}
if bundle.auditStore == nil {
t.Fatal("expected audit store")
}
if bundle.alertService == nil {
t.Fatal("expected alert service")
}
if bundle.tokenStatusRepo != nil {
t.Fatal("expected nil token status repo")
}
if bundle.idempotencyRepo != nil {
t.Fatal("expected nil idempotency repo")
}
}
func TestBuildStoreBundle_UsesDatabaseBackedStoresWithDatabase(t *testing.T) {
bundle := buildStoreBundle(&repository.DB{}, testLogger{})
if bundle.accountStore == nil {
@@ -121,6 +149,34 @@ func TestBuildStoreBundle_UsesDatabaseBackedStoresWithDatabase(t *testing.T) {
}
}
func TestBuildDBStoreBundle_InitializesDatabaseOnlyDependencies(t *testing.T) {
bundle := buildDBStoreBundle(&repository.DB{})
if bundle.accountStore == nil {
t.Fatal("expected account store")
}
if bundle.packageStore == nil {
t.Fatal("expected package store")
}
if bundle.settlementStore == nil {
t.Fatal("expected settlement store")
}
if bundle.earningStore == nil {
t.Fatal("expected earning store")
}
if bundle.auditStore == nil {
t.Fatal("expected audit store")
}
if bundle.alertService == nil {
t.Fatal("expected alert service")
}
if bundle.tokenStatusRepo == nil {
t.Fatal("expected token status repo")
}
if bundle.idempotencyRepo == nil {
t.Fatal("expected idempotency repo")
}
}
func TestBuildSecurityBundle_UsesMemoryTokenBackendWithoutRepository(t *testing.T) {
security := buildSecurityBundle("dev", testRuntimeConfig(), testLogger{}, audit.NewMemoryAuditStore(), nil, nil)
if security.authMiddleware == nil {