Skip to content

Commit da6791f

Browse files
committed
refactor(ecache): revise code comments
1 parent 65cd313 commit da6791f

2 files changed

Lines changed: 159 additions & 43 deletions

File tree

ecache/entity_cache.go

Lines changed: 130 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,112 @@ import (
1313
var (
1414
singleFlightGroup singleflight.Group
1515

16-
// Marshal 默认序列化方案
16+
// Marshal specifies the default serialization function for cache values
1717
Marshal func(v any) ([]byte, error) = json.Marshal
18-
// Unmarshal 默认反序列化方案
18+
19+
// Unmarshal specifies the default deserialization function for cached data
1920
Unmarshal func(data []byte, v any) error = json.Unmarshal
20-
// DefaultExpiration 缓存默认过期时间
21-
DefaultExpiration time.Duration = time.Hour * 24
22-
// Disabled 缓存开关
21+
22+
// DefaultExpiration defines the default time-to-live for cache entries
23+
DefaultExpiration time.Duration = 0 // no expiration
24+
25+
// Disabled globally turns off caching when true
2326
Disabled bool
24-
// 缓存键名分隔符
27+
28+
// Delimiter separates components in cache key strings
2529
Delimiter = ':'
2630
)
2731

32+
// Cache defines the contract for cache implementations.
33+
// Implementations must handle:
34+
// - Serialization/deserialization using Marshal/Unmarshal functions
35+
// - Error handling with proper error wrapping
36+
// - Concurrency control for thread-safe operations
37+
//
38+
// All methods should return errors compatible with IsKeyNotFound check
39+
// for consistent cache miss detection.
40+
2841
type Cache interface {
42+
// Del removes multiple entries from cache.
43+
//
44+
// Args:
45+
// ctx: Context for request cancellation/timeout
46+
// keys: Cache keys to delete
47+
//
48+
// Returns:
49+
// affected: Number of successfully deleted entries
50+
// err: Storage errors (e.g. connection issues), nil on success
2951
Del(ctx context.Context, keys ...string) (affected int64, err error)
52+
53+
// Get retrieves raw byte slice from cache.
54+
//
55+
// Args:
56+
// ctx: Context for request cancellation/timeout
57+
// key: Cache entry identifier
58+
//
59+
// Returns:
60+
// val: Cached byte slice (nil on cache miss)
61+
// err: Storage errors or nil. Use IsKeyNotFound() to detect cache misses
3062
Get(ctx context.Context, key string) (val []byte, err error)
63+
64+
// Set stores byte slice in cache with expiration.
65+
//
66+
// Args:
67+
// ctx: Context for request cancellation/timeout
68+
// key: Cache entry identifier
69+
// val: Data to store (nil allowed for cache deletion)
70+
// expire: TTL duration (<=0 means no expiration)
71+
//
72+
// Returns:
73+
// error: Storage errors, nil on success
3174
Set(ctx context.Context, key string, val []byte, expire time.Duration) error
75+
// GetAsUint64 retrieves and converts cached value to unsigned integer.
76+
//
77+
// Performs strict conversion:
78+
// - Returns error if value is not 8-byte little-endian format
79+
// - Returns error if value exceeds uint64 range
80+
//
81+
// Args:
82+
// key: Cache entry identifier
83+
//
84+
// Returns:
85+
// val: Converted unsigned integer value
86+
// err: Conversion errors or storage errors
3287
GetAsUint64(ctx context.Context, key string) (val uint64, err error)
88+
// SetUint64 stores unsigned integer in 8-byte little-endian format.
89+
//
90+
// Args:
91+
// key: Cache entry identifier
92+
// val: Unsigned integer to store
93+
// expire: TTL duration (<=0 uses default expiration)
94+
//
95+
// Returns:
96+
// error: Storage errors, nil on success
3397
SetUint64(ctx context.Context, key string, val uint64, expire time.Duration) error
98+
// IsKeyNotFound checks if error represents cache miss (key not exists).
99+
//
100+
// Implementation should return true for:
101+
// - Cache storage-specific "not found" errors
102+
// - Wrapped versions of these errors
103+
//
104+
// This enables reliable cache miss detection across implementations
34105
IsKeyNotFound(err error) bool
35106
}
36107

37108
type UnsignedInt interface {
38109
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
39110
}
40111

112+
// CacheableEntity defines the contract for cacheable domain entities
113+
// Used to enforce ID-based caching constraints
114+
// Implementations should:
115+
// - Use unsigned integer types for identifiers
116+
// - Maintain immutable ID properties
41117
type CacheableEntity[INT UnsignedInt] interface {
42118
ID() INT
43119
}
44120

45-
// DelCachedEntity 删除指定键的缓存数据
121+
// DelCachedEntity deletes cached data for specified key
46122
func DelCachedEntity(ctx context.Context, cache Cache, key string) (affected int64, err error) {
47123
if Disabled {
48124
return 0, nil
@@ -54,18 +130,21 @@ func DelCachedEntity(ctx context.Context, cache Cache, key string) (affected int
54130
return affected, nil
55131
}
56132

57-
// GetEntityByID 返回主键ID对应的实体对象。
58-
// 查找顺序:主键ID --> 缓存中实体对象 --> 数据库中实体对象。
59-
// 局限:
60-
// 1、缓存 key格式无法自定义且不够内聚
133+
// GetEntityByID implements a dual-check mechanism:
134+
// 1. Check cache first with singleflight deduplication
135+
// 2. Fallback to database on cache miss
136+
// 3. Repopulate cache with database result
137+
//
138+
// The cache-aside pattern prevents stale cache returns while
139+
// singleflight prevents cache stampede
61140
func GetEntityByID[T CacheableEntity[INT], INT UnsignedInt](
62141
ctx context.Context,
63142
cache Cache,
64143
entityKeyPrefix string,
65144
id INT,
66145
getEntityByID func(context.Context, INT) (*T, error),
67146
) (*T, error) {
68-
// 0、若未启用缓存,则直接查询数据库。
147+
// 0. When cache is disabled, directly query database
69148
if Disabled {
70149
one, err := getEntityByID(ctx, id)
71150
if err != nil {
@@ -74,7 +153,7 @@ func GetEntityByID[T CacheableEntity[INT], INT UnsignedInt](
74153
return one, nil
75154
}
76155

77-
// 1、优先尝试从缓存中获取对象
156+
// 1. First attempt to retrieve object from cache
78157
key := fmt.Sprintf("%s%c%d", entityKeyPrefix, Delimiter, id)
79158

80159
data, err := cache.Get(ctx, key)
@@ -83,35 +162,39 @@ func GetEntityByID[T CacheableEntity[INT], INT UnsignedInt](
83162
}
84163

85164
if err == nil {
86-
// 2、若获取到键对应的值,尝试反序列化,并返回对象。
165+
// 2. If value exists, attempt deserialization and return object
87166
var one T
88167
if err = Unmarshal(data, &one); err == nil {
89168
return &one, nil
90169
}
91-
// 若反序列化失败,不返回,允许继续向下执行。
170+
// If deserialization fails, continue execution flow
92171
}
93172

94-
// 2、从数据库查询指定ID对象
95-
entity, err, _ := singleFlightGroup.Do(key, func() (any, error) { // 防止缓存击穿
173+
// 2. Query database for target ID
174+
entity, err, _ := singleFlightGroup.Do(key, func() (any, error) { // Prevent cache breakdown
96175
return getEntityByID(ctx, id)
97176
})
98177
if err != nil {
99178
return nil, errors.WithStack(err)
100179
}
101180

102-
// 3、序列化对象并存入Redis
181+
// 3. Serialize object and store in cache
103182
if data, err = Marshal(entity); err != nil {
104183
return nil, errors.WithStack(err)
105184
}
106185
if err = cache.Set(ctx, key, data, DefaultExpiration); err != nil {
107186
return nil, errors.WithStack(err)
108187
}
109188

110-
// 4、返回查询到的对象
189+
// 4. Return fetched object
111190
return entity.(*T), nil
112191
}
113192

114-
// GetEntitiesByID 从Redis缓存中读取指定实体列表。若缓存中不存在,则从指定方法中读取并存入Redis缓存。
193+
// GetEntitiesByID retrieves entity list with cache-aside pattern
194+
// Implements:
195+
// 1. Batch cache lookup with singleflight deduplication
196+
// 2. Database fallback on cache miss
197+
// 3. Cache population with serialized results
115198
func GetEntitiesByID[T CacheableEntity[INT], INT UnsignedInt](
116199
ctx context.Context,
117200
cache Cache,
@@ -122,15 +205,15 @@ func GetEntitiesByID[T CacheableEntity[INT], INT UnsignedInt](
122205
var err error
123206
var items []*T
124207

125-
// 0、若未启用缓存,则直接查询数据库。
208+
// 0. When cache is disabled, directly query database
126209
if Disabled {
127210
if items, err = getEntitiesByID(ctx, id); err != nil {
128211
return nil, errors.WithStack(err)
129212
}
130213
return items, nil
131214
}
132215

133-
// 1、优先尝试从Redis中获取对象列表
216+
// 1. First attempt to retrieve object from cache列表
134217
key := fmt.Sprintf("%s%citems%c%d", entityKeyPrefix, Delimiter, Delimiter, id)
135218

136219
data, err := cache.Get(ctx, key)
@@ -139,14 +222,14 @@ func GetEntitiesByID[T CacheableEntity[INT], INT UnsignedInt](
139222
}
140223

141224
if err == nil {
142-
// 2、若获取到键对应的值,尝试反序列化,并返回对象列表。
225+
// 2. If cached data exists, deserialize and return the entity list
143226
if err = Unmarshal(data, &items); err == nil {
144227
return items, nil
145228
}
146-
// 若反序列化失败,不返回,允许继续向下执行。
229+
// If deserialization fails, continue execution flow
147230
}
148231

149-
// 2、从数据库查询指定ID对象列表
232+
// 2. Query database for target ID列表
150233
entities, err, _ := singleFlightGroup.Do(key, func() (any, error) {
151234
return getEntitiesByID(ctx, id)
152235
})
@@ -159,7 +242,7 @@ func GetEntitiesByID[T CacheableEntity[INT], INT UnsignedInt](
159242
return items, nil
160243
}
161244

162-
// 3、序列化对象列表并存入Redis
245+
// 3、序列化对象列表并存入缓存
163246
if data, err = Marshal(&items); err != nil {
164247
return nil, errors.WithStack(err)
165248
}
@@ -170,17 +253,23 @@ func GetEntitiesByID[T CacheableEntity[INT], INT UnsignedInt](
170253
return items, nil
171254
}
172255

173-
// UniqueKey 唯一索引约束
256+
// UniqueKey defines constraints for database unique keys
257+
// Used in GetEntityByUniqueKey to enforce:
258+
// - String or unsigned integer types
259+
// - Single-field uniqueness (composite keys not supported)
174260
type UniqueKey interface {
175261
~string | UnsignedInt
176262
}
177263

178-
// GetEntityByUniqueKey 返回唯一索引值对应的实体对象。
179-
// 查找顺序:唯一索引值 --> Redis中主键ID --> 数据库中主键ID --> Redis中实体对象 --> 数据库中实体对象。
180-
// 局限:
181-
// 1、Redis key格式无法自定义
182-
// 2、不支持联合唯一索引
183-
// 3、多一次查询
264+
// GetEntityByUniqueKey implements two-level cache resolution:
265+
// 1. UK -> PK lookup cache
266+
// 2. PK -> Entity cache
267+
// Features:
268+
// - Prevents cache stampede with singleflight
269+
// - Automatic cache population for both UK-PK and PK-Entity mappings
270+
// Limitations:
271+
// - Composite unique keys not supported
272+
// - Fixed cache key format
184273
func GetEntityByUniqueKey[T CacheableEntity[INT], INT UnsignedInt, UK UniqueKey](
185274
ctx context.Context,
186275
cache Cache,
@@ -190,7 +279,7 @@ func GetEntityByUniqueKey[T CacheableEntity[INT], INT UnsignedInt, UK UniqueKey]
190279
getIDByUK func(context.Context, UK) (INT, error),
191280
getEntityByID func(context.Context, INT) (*T, error),
192281
) (*T, error) {
193-
// 0、若未启用缓存,则直接查询数据库。
282+
// 0. When cache is disabled, directly query database
194283
if Disabled {
195284
id, err := getIDByUK(ctx, ukVal)
196285
if err != nil {
@@ -203,28 +292,27 @@ func GetEntityByUniqueKey[T CacheableEntity[INT], INT UnsignedInt, UK UniqueKey]
203292
return one, nil
204293
}
205294

206-
// 键:唯一索引值
207-
// 值:主键ID
295+
// Key represents unique index value
296+
// Value stores corresponding primary key ID
208297
ukKey := fmt.Sprintf("%s%c%v", ukKeyPrefix, Delimiter, ukVal)
209298

210-
// 1、根据数据库表唯一索引key找到数据库表主键ID
299+
// 1. Resolve primary key ID using unique index key
211300
u64ID, err := cache.GetAsUint64(ctx, ukKey)
212301
if err == nil {
213-
// 2、若找到数据库表主键ID,则根据主键ID尝试返回实体对象。
214302
return GetEntityByID(ctx, cache, entityKeyPrefix, INT(u64ID), getEntityByID)
215303
}
216-
if !cache.IsKeyNotFound(err) { // 发生了除「Redis key不存在」之外的其他未知错误
304+
if !cache.IsKeyNotFound(err) { // Handle unexpected errors beyond cache miss
217305
return nil, errors.WithStack(err)
218306
}
219-
// 2、若未找到数据库表主键ID,则调用查询函数获取主键ID值。
307+
// 2. If the primary key ID of the database table is not found, call the query function to obtain the primary key ID value.
220308
id, err := getIDByUK(ctx, ukVal)
221309
if err != nil {
222310
return nil, errors.WithStack(err)
223311
}
224-
// 3、保存唯一索引值(键)与主键ID(值)关系
312+
// 3. Persist unique index to primary key mapping
225313
if err = cache.SetUint64(ctx, ukKey, uint64(id), -1); err != nil {
226314
return nil, errors.WithStack(err)
227315
}
228-
// 4、根据主键ID尝试返回实体对象
316+
// 4. Retrieve entity using resolved primary key
229317
return GetEntityByID(ctx, cache, entityKeyPrefix, id, getEntityByID)
230318
}

ecache/redis/redis_cache.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,72 @@ import (
88
"github.com/redis/go-redis/v9"
99
)
1010

11+
// cache implements Redis-based caching solution.
12+
// It wraps go-redis client to provide standard cache interface.
1113
type cache struct {
1214
rdb *redis.Client
1315
}
1416

17+
// NewCache creates a Redis cache instance.
18+
// client: Configured go-redis client connection
1519
func NewCache(client *redis.Client) *cache {
1620
return &cache{
1721
rdb: client,
1822
}
1923
}
2024

25+
// Del removes multiple keys from cache.
26+
// Returns number of deleted keys and any error encountered.
27+
// Wraps redis DEL command errors with stack trace.
2128
func (c *cache) Del(ctx context.Context, keys ...string) (affected int64, err error) {
2229
if affected, err = c.rdb.Del(ctx, keys...).Result(); err != nil {
2330
return affected, errors.WithStack(err)
2431
}
2532
return affected, nil
2633
}
2734

35+
// Get retrieves byte slice value for given key.
36+
// Returns redis.Nil error when key does not exist.
37+
// Wraps underlying redis GET command errors.
2838
func (c *cache) Get(ctx context.Context, key string) (val []byte, err error) {
2939
if val, err = c.rdb.Get(ctx, key).Bytes(); err != nil {
3040
return nil, errors.WithStack(err)
3141
}
3242
return val, nil
3343
}
3444

45+
// Set stores value with TTL expiration.
46+
// expire: Time-to-live duration (<=0 means no expiration)
47+
// Wraps redis SET command errors with stack trace.
48+
func (c *cache) Set(ctx context.Context, key string, val []byte, expire time.Duration) (err error) {
49+
if err = c.rdb.Set(ctx, key, val, expire).Err(); err != nil {
50+
return errors.WithStack(err)
51+
}
52+
return nil
53+
}
54+
55+
// GetAsUint64 retrieves uint64 value for given key.
56+
// Returns 0 and error if value cannot be converted.
57+
// Wraps underlying redis GET command errors.
3558
func (c *cache) GetAsUint64(ctx context.Context, key string) (val uint64, err error) {
3659
if val, err = c.rdb.Get(ctx, key).Uint64(); err != nil {
3760
return 0, errors.WithStack(err)
3861
}
3962
return val, nil
4063
}
4164

42-
func (c *cache) Set(ctx context.Context, key string, val []byte, expire time.Duration) (err error) {
65+
// SetUint64 stores uint64 value with TTL expiration.
66+
// expire: Time-to-live duration (<=0 means no expiration)
67+
// Wraps redis SET command errors with stack trace.
68+
func (c *cache) SetUint64(ctx context.Context, key string, val uint64, expire time.Duration) (err error) {
4369
if err = c.rdb.Set(ctx, key, val, expire).Err(); err != nil {
4470
return errors.WithStack(err)
4571
}
4672
return nil
4773
}
4874

75+
// IsKeyNotFound checks if error represents missing key.
76+
// Returns true if error is redis.Nil.
4977
func (c *cache) IsKeyNotFound(err error) bool {
5078
return errors.Is(err, redis.Nil)
5179
}

0 commit comments

Comments
 (0)