refactor(supply-api): split runtime store bundle builders
This commit is contained in:
124
docs/plans/2026-04-15-supply-api-store-bundle-split-plan.md
Normal file
124
docs/plans/2026-04-15-supply-api-store-bundle-split-plan.md
Normal 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"
|
||||
```
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user