@@ -13,36 +13,112 @@ import (
1313var (
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+
2841type 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
37108type 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
41117type CacheableEntity [INT UnsignedInt ] interface {
42118 ID () INT
43119}
44120
45- // DelCachedEntity 删除指定键的缓存数据
121+ // DelCachedEntity deletes cached data for specified key
46122func 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
61140func 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
115198func 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)
174260type 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
184273func 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}
0 commit comments