mirror of
https://github.com/Sosokker/go-chi-oapi-codegen-todolist.git
synced 2025-12-19 05:54:07 +01:00
feat: apply cache to tag
This commit is contained in:
parent
1ddffbc026
commit
0ef57400ba
@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Sosokker/todolist-backend/internal/api"
|
||||
"github.com/Sosokker/todolist-backend/internal/cache"
|
||||
"github.com/Sosokker/todolist-backend/internal/config"
|
||||
"github.com/Sosokker/todolist-backend/internal/repository"
|
||||
"github.com/Sosokker/todolist-backend/internal/service"
|
||||
@ -53,7 +54,10 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
repoRegistry := repository.NewRepositoryRegistry(pool)
|
||||
// --- Cache Setup ---
|
||||
appCache := cache.NewMemoryCache(cfg.Cache, logger)
|
||||
|
||||
repoRegistry := repository.NewRepositoryRegistry(pool, appCache, logger)
|
||||
|
||||
var storageService service.FileStorageService
|
||||
storageService, err = service.NewGCStorageService(cfg.Storage.GCS, logger)
|
||||
|
||||
27
backend/internal/cache/cache.go
vendored
27
backend/internal/cache/cache.go
vendored
@ -20,17 +20,28 @@ type Cache interface {
|
||||
type memoryCache struct {
|
||||
client *gocache.Cache
|
||||
logger *slog.Logger
|
||||
defaultExpiration time.Duration
|
||||
}
|
||||
|
||||
// NewMemoryCache creates a new in-memory cache
|
||||
func NewMemoryCache(cfg config.CacheConfig, logger *slog.Logger) Cache {
|
||||
c := gocache.New(cfg.DefaultExpiration, cfg.CleanupInterval)
|
||||
defaultExp := cfg.DefaultExpiration
|
||||
if defaultExp <= 0 {
|
||||
defaultExp = 5 * time.Minute
|
||||
}
|
||||
cleanupInterval := cfg.CleanupInterval
|
||||
if cleanupInterval <= 0 {
|
||||
cleanupInterval = 10 * time.Minute
|
||||
}
|
||||
|
||||
c := gocache.New(defaultExp, cleanupInterval)
|
||||
logger.Info("In-memory cache initialized",
|
||||
"defaultExpiration", cfg.DefaultExpiration,
|
||||
"cleanupInterval", cfg.CleanupInterval)
|
||||
"defaultExpiration", defaultExp,
|
||||
"cleanupInterval", cleanupInterval)
|
||||
return &memoryCache{
|
||||
client: c,
|
||||
logger: logger,
|
||||
logger: logger.With("component", "memoryCache"),
|
||||
defaultExpiration: defaultExp,
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,8 +56,12 @@ func (m *memoryCache) Get(ctx context.Context, key string) (interface{}, bool) {
|
||||
}
|
||||
|
||||
func (m *memoryCache) Set(ctx context.Context, key string, value interface{}, duration time.Duration) {
|
||||
m.logger.DebugContext(ctx, "Setting cache", "key", key, "duration", duration)
|
||||
m.client.Set(key, value, duration) // duration=0 means use default, -1 means never expire (DefaultExpiration)
|
||||
exp := duration
|
||||
if exp <= 0 {
|
||||
exp = m.defaultExpiration
|
||||
}
|
||||
m.logger.DebugContext(ctx, "Setting cache", "key", key, "duration", exp)
|
||||
m.client.Set(key, value, exp)
|
||||
}
|
||||
|
||||
func (m *memoryCache) Delete(ctx context.Context, key string) {
|
||||
|
||||
@ -2,8 +2,10 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/Sosokker/todolist-backend/internal/cache"
|
||||
"github.com/Sosokker/todolist-backend/internal/domain"
|
||||
db "github.com/Sosokker/todolist-backend/internal/repository/sqlc/generated"
|
||||
"github.com/google/uuid"
|
||||
@ -82,14 +84,22 @@ type RepositoryRegistry struct {
|
||||
Pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
// NewRepositoryRegistry creates a new registry
|
||||
func NewRepositoryRegistry(pool *pgxpool.Pool) *RepositoryRegistry {
|
||||
// NewRepositoryRegistry creates a new registry, now with caching decorators
|
||||
func NewRepositoryRegistry(pool *pgxpool.Pool, cache cache.Cache, logger *slog.Logger) *RepositoryRegistry {
|
||||
queries := db.New(pool)
|
||||
|
||||
pgxUserRepo := NewPgxUserRepository(queries)
|
||||
pgxTagRepo := NewPgxTagRepository(queries)
|
||||
pgxTodoRepo := NewPgxTodoRepository(queries, pool)
|
||||
pgxSubtaskRepo := NewPgxSubtaskRepository(queries)
|
||||
|
||||
cachingTagRepo := NewCachingTagRepository(pgxTagRepo, cache, logger)
|
||||
|
||||
return &RepositoryRegistry{
|
||||
UserRepo: NewPgxUserRepository(queries),
|
||||
TagRepo: NewPgxTagRepository(queries),
|
||||
TodoRepo: NewPgxTodoRepository(queries, pool),
|
||||
SubtaskRepo: NewPgxSubtaskRepository(queries),
|
||||
UserRepo: pgxUserRepo, // Not cached yet in this example
|
||||
TagRepo: cachingTagRepo, // Use the caching decorator
|
||||
TodoRepo: pgxTodoRepo, // Not cached yet in this example
|
||||
SubtaskRepo: pgxSubtaskRepo, // Not cached yet in this example
|
||||
Queries: queries,
|
||||
Pool: pool,
|
||||
}
|
||||
|
||||
95
backend/internal/repository/tag_repo_cache.go
Normal file
95
backend/internal/repository/tag_repo_cache.go
Normal file
@ -0,0 +1,95 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/Sosokker/todolist-backend/internal/cache"
|
||||
"github.com/Sosokker/todolist-backend/internal/domain"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type cachingTagRepository struct {
|
||||
next TagRepository
|
||||
cache cache.Cache
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewCachingTagRepository(next TagRepository, cache cache.Cache, logger *slog.Logger) TagRepository {
|
||||
return &cachingTagRepository{
|
||||
next: next,
|
||||
cache: cache,
|
||||
logger: logger.With("repository", "tag_cache_decorator"),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Cache Key Generation ---
|
||||
func tagCacheKey(userID, tagID uuid.UUID) string {
|
||||
return fmt.Sprintf("user:%s:tag:%s", userID, tagID)
|
||||
}
|
||||
|
||||
// --- TagRepository Interface Implementation ---
|
||||
|
||||
func (r *cachingTagRepository) Create(ctx context.Context, tag *domain.Tag) (*domain.Tag, error) {
|
||||
createdTag, err := r.next.Create(ctx, tag)
|
||||
// Invalidate list caches if/when implemented
|
||||
return createdTag, err
|
||||
}
|
||||
|
||||
func (r *cachingTagRepository) GetByID(ctx context.Context, id, userID uuid.UUID) (*domain.Tag, error) {
|
||||
cacheKey := tagCacheKey(userID, id)
|
||||
if cached, found := r.cache.Get(ctx, cacheKey); found {
|
||||
if tag, ok := cached.(*domain.Tag); ok {
|
||||
r.logger.DebugContext(ctx, "GetTagByID cache hit", "key", cacheKey)
|
||||
return tag, nil
|
||||
}
|
||||
// Invalid type in cache, treat as miss and delete
|
||||
r.logger.WarnContext(ctx, "Invalid type found in tag cache", "key", cacheKey, "type", fmt.Sprintf("%T", cached))
|
||||
r.cache.Delete(ctx, cacheKey)
|
||||
}
|
||||
|
||||
r.logger.DebugContext(ctx, "GetTagByID cache miss", "key", cacheKey)
|
||||
tag, err := r.next.GetByID(ctx, id, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
r.cache.Set(ctx, cacheKey, tag, 0)
|
||||
r.logger.DebugContext(ctx, "Set tag in cache", "key", cacheKey)
|
||||
}
|
||||
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
func (r *cachingTagRepository) Update(ctx context.Context, id, userID uuid.UUID, updateData *domain.Tag) (*domain.Tag, error) {
|
||||
updatedTag, err := r.next.Update(ctx, id, userID, updateData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheKey := tagCacheKey(userID, id)
|
||||
r.cache.Delete(ctx, cacheKey)
|
||||
r.logger.DebugContext(ctx, "Invalidated tag cache after update", "key", cacheKey)
|
||||
return updatedTag, nil
|
||||
}
|
||||
|
||||
func (r *cachingTagRepository) Delete(ctx context.Context, id, userID uuid.UUID) error {
|
||||
err := r.next.Delete(ctx, id, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheKey := tagCacheKey(userID, id)
|
||||
r.cache.Delete(ctx, cacheKey)
|
||||
r.logger.DebugContext(ctx, "Invalidated tag cache after delete", "key", cacheKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Pass-through methods ---
|
||||
func (r *cachingTagRepository) GetByIDs(ctx context.Context, ids []uuid.UUID, userID uuid.UUID) ([]domain.Tag, error) {
|
||||
return r.next.GetByIDs(ctx, ids, userID)
|
||||
}
|
||||
|
||||
func (r *cachingTagRepository) ListByUser(ctx context.Context, userID uuid.UUID) ([]domain.Tag, error) {
|
||||
return r.next.ListByUser(ctx, userID)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user