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"
|
"time"
|
||||||
|
|
||||||
"github.com/Sosokker/todolist-backend/internal/api"
|
"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/config"
|
||||||
"github.com/Sosokker/todolist-backend/internal/repository"
|
"github.com/Sosokker/todolist-backend/internal/repository"
|
||||||
"github.com/Sosokker/todolist-backend/internal/service"
|
"github.com/Sosokker/todolist-backend/internal/service"
|
||||||
@ -53,7 +54,10 @@ func main() {
|
|||||||
os.Exit(1)
|
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
|
var storageService service.FileStorageService
|
||||||
storageService, err = service.NewGCStorageService(cfg.Storage.GCS, logger)
|
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 {
|
type memoryCache struct {
|
||||||
client *gocache.Cache
|
client *gocache.Cache
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
defaultExpiration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMemoryCache creates a new in-memory cache
|
// NewMemoryCache creates a new in-memory cache
|
||||||
func NewMemoryCache(cfg config.CacheConfig, logger *slog.Logger) 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",
|
logger.Info("In-memory cache initialized",
|
||||||
"defaultExpiration", cfg.DefaultExpiration,
|
"defaultExpiration", defaultExp,
|
||||||
"cleanupInterval", cfg.CleanupInterval)
|
"cleanupInterval", cleanupInterval)
|
||||||
return &memoryCache{
|
return &memoryCache{
|
||||||
client: c,
|
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) {
|
func (m *memoryCache) Set(ctx context.Context, key string, value interface{}, duration time.Duration) {
|
||||||
m.logger.DebugContext(ctx, "Setting cache", "key", key, "duration", duration)
|
exp := duration
|
||||||
m.client.Set(key, value, duration) // duration=0 means use default, -1 means never expire (DefaultExpiration)
|
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) {
|
func (m *memoryCache) Delete(ctx context.Context, key string) {
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sosokker/todolist-backend/internal/cache"
|
||||||
"github.com/Sosokker/todolist-backend/internal/domain"
|
"github.com/Sosokker/todolist-backend/internal/domain"
|
||||||
db "github.com/Sosokker/todolist-backend/internal/repository/sqlc/generated"
|
db "github.com/Sosokker/todolist-backend/internal/repository/sqlc/generated"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -82,14 +84,22 @@ type RepositoryRegistry struct {
|
|||||||
Pool *pgxpool.Pool
|
Pool *pgxpool.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepositoryRegistry creates a new registry
|
// NewRepositoryRegistry creates a new registry, now with caching decorators
|
||||||
func NewRepositoryRegistry(pool *pgxpool.Pool) *RepositoryRegistry {
|
func NewRepositoryRegistry(pool *pgxpool.Pool, cache cache.Cache, logger *slog.Logger) *RepositoryRegistry {
|
||||||
queries := db.New(pool)
|
queries := db.New(pool)
|
||||||
|
|
||||||
|
pgxUserRepo := NewPgxUserRepository(queries)
|
||||||
|
pgxTagRepo := NewPgxTagRepository(queries)
|
||||||
|
pgxTodoRepo := NewPgxTodoRepository(queries, pool)
|
||||||
|
pgxSubtaskRepo := NewPgxSubtaskRepository(queries)
|
||||||
|
|
||||||
|
cachingTagRepo := NewCachingTagRepository(pgxTagRepo, cache, logger)
|
||||||
|
|
||||||
return &RepositoryRegistry{
|
return &RepositoryRegistry{
|
||||||
UserRepo: NewPgxUserRepository(queries),
|
UserRepo: pgxUserRepo, // Not cached yet in this example
|
||||||
TagRepo: NewPgxTagRepository(queries),
|
TagRepo: cachingTagRepo, // Use the caching decorator
|
||||||
TodoRepo: NewPgxTodoRepository(queries, pool),
|
TodoRepo: pgxTodoRepo, // Not cached yet in this example
|
||||||
SubtaskRepo: NewPgxSubtaskRepository(queries),
|
SubtaskRepo: pgxSubtaskRepo, // Not cached yet in this example
|
||||||
Queries: queries,
|
Queries: queries,
|
||||||
Pool: pool,
|
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