跳转到内容

Store Primer

当你已经通过 稳定根 确认改动仍属于稳定表面,但问题进一步收窄到持久化原语与存储 contract 时,就打开这一页。

store 拥有基础持久化抽象。只有当工作应继续停留在拓扑较重的数据特性、tenant-aware 实现,以及 HTTP-facing 文件或缓存行为之下时,它才是正确归属。

var repo store.Repository[User] = newUserRepo(db)
user, err := repo.Get(ctx, userID)
if err != nil {
_ = contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeNotFound).
Message("user not found").
Build())
return
}
_ = contract.WriteResponse(w, r, http.StatusOK, user, nil)
  • 你正在定义稳定存储 contract 或基础持久化 helper
  • 工作仍然停留在 provider-specific topology 决策之下
  • 改动属于 file、KV、cache、idempotency 或 DB helper primitive
  • 工作已经变成 tenant-aware storage policy 或 tenant-aware adapter
  • 任务引入 distributed engine 行为、signed URL 或 metadata-manager ownership
  • 真正的问题是 upload/download HTTP transport,或由 gateway 拥有的 response caching
  1. store/module.yaml
  2. store/* 下的目标包
  3. specs/repo.yaml
这些工作适合留在 store一旦变成这些问题就应移出
store/file 接口、共享文件类型与错误x/data/file 中的 tenant-aware file backend 与 metadata persistence,或 x/fileapi 中的 HTTP upload/download endpoint
store/kv 的 file-backed KV CRUD、TTL-aware helper、key scan 与 basic statsx/data/kvengine 中的 WAL、snapshot、serializer selection、compression 与 shard tuning
store/idempotency 的 record、status、error 与最小 Store 接口x/data/idempotency 中的 SQL/KV provider、SQL dialect policy 与 duplicate-key handling
store/cache 的基础 cache primitivex/data/cache 中的 distributed cache engine、x/gateway/cache 中的 HTTP response caching,以及 x/tenant/store/cache 中的 tenant-aware cache adapter
store/db 中依赖调用方 context.Context 的 helper 执行x/observability/dbinsights 中的 DB analytics 与 slow-query inspection
import kvstore "github.com/spcent/plumego/store/kv"
kv, err := kvstore.NewKVStore(kvstore.Options{
DataDir: "/var/lib/myapp/kv",
})
if err != nil { ... }
defer kv.Close()
kv.Set("session:abc", []byte(`{"user":"alice"}`), 30*time.Minute)
data, err := kv.Get("session:abc")
kv.Delete("session:abc")
exists := kv.Exists("session:abc")

KVStore 并发安全。适合短期应用状态,如 JWT 密钥、会话和限流计数器。高吞吐或分布式场景请用 x/data/cache

import "github.com/spcent/plumego/store/cache"
c, err := cache.NewMemoryCacheWithConfig(cache.DefaultConfig())
if err != nil { ... }
c.Set(ctx, "user:42", jsonBytes, 5*time.Minute)
data, err := c.Get(ctx, "user:42")

Cache 接口允许替换实现:

type Cache interface {
Get(ctx context.Context, key string) ([]byte, error)
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
Delete(ctx context.Context, key string) error
Exists(ctx context.Context, key string) (bool, error)
Close() error
}
import "github.com/spcent/plumego/store/db"
sqlDB, err := db.Open(db.DefaultConfig("postgres", dsn))
if err != nil { ... }
rows, err := db.QueryContext(ctx, sqlDB, "SELECT id, name FROM users WHERE active = $1", true)
result, err := db.ExecContext(ctx, sqlDB, "UPDATE users SET name = $1 WHERE id = $2", name, id)
err = db.WithTransaction(ctx, sqlDB, nil, func(tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, "INSERT INTO orders ...", ...)
return err
})

store/idempotency — 精确一次操作保障

Section titled “store/idempotency — 精确一次操作保障”
import "github.com/spcent/plumego/store/idempotency"
// 实现 Store 接口(或使用 x/data/idempotency 获取持久化 provider)
var idem idempotency.Store
rec, err := idem.Get(ctx, idempotencyKey)
if err == nil && rec.Status == idempotency.StatusCompleted {
// 重放已存储的响应
return rec.Response, nil
}
result, err := doExpensiveOperation(ctx, req)
if err != nil { return nil, err }
idem.Set(ctx, idempotencyKey, idempotency.Record{
Status: idempotency.StatusCompleted,
Response: result,
})

store 看起来容易变成一个很宽的目录,因为持久化会碰到很多能力层。当前仓库的真实规则,是刻意把它压窄:稳定持久化 contract 留在这里,而 provider-heavy、topology-heavy、tenant-aware、HTTP-facing 行为则向外移动。