/
cache.go
139 lines (117 loc) · 3.09 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package api
import (
"context"
"fmt"
"time"
"github.com/alexdunne/gs-onboarding/internal/database"
"github.com/alexdunne/gs-onboarding/internal/models"
"github.com/go-redis/cache/v8"
"github.com/go-redis/redis/v8"
"github.com/pkg/errors"
"go.uber.org/zap"
)
// Cache is an interace to expose cache methods
type Cache interface {
GetAll(ctx context.Context) ([]models.Item, error)
GetStories(ctx context.Context) ([]models.Item, error)
GetJobs(ctx context.Context) ([]models.Item, error)
}
type itemCache struct {
db database.Database
cache *cache.Cache
ring *redis.Ring
ttl time.Duration
logger *zap.Logger
}
// CacheOption is an interface for a functional option
type CacheOption func(c *itemCache)
// WithTTL is a functional option to configure the cache TTL
func WithTTL(ttl time.Duration) CacheOption {
return func(c *itemCache) {
c.ttl = ttl
}
}
// NewCache creates a new cache
func NewCache(ctx context.Context, redisAddr string, db database.Database, logger *zap.Logger, opts ...CacheOption) (*itemCache, error) {
ring := redis.NewRing(&redis.RingOptions{
Addrs: map[string]string{
"leader": redisAddr,
},
})
c := cache.New(&cache.Options{
Redis: ring,
LocalCache: cache.NewTinyLFU(1000, time.Minute),
})
_, err := ring.Ping(ctx).Result()
if err != nil {
return nil, errors.Wrap(err, "pinging with new client")
}
ret := &itemCache{
db: db,
cache: c,
ring: ring,
ttl: 5 * time.Minute,
logger: logger,
}
for _, opt := range opts {
opt(ret)
}
return ret, nil
}
// GetAll fetches all items from the cache and falls back to fetching from the database
func (c *itemCache) GetAll(ctx context.Context) ([]models.Item, error) {
var items []models.Item
key := "items:all"
err := c.cache.Once(&cache.Item{
Key: key,
Value: &items,
TTL: c.ttl,
Do: func(*cache.Item) (interface{}, error) {
c.logger.Info(fmt.Sprintf("%s cache missed. fetching from source", key))
return c.db.GetAll(ctx)
},
})
if err != nil {
return nil, err
}
return items, nil
}
// GetStories fetches all story items from the cache and falls back to fetching from the database
func (c *itemCache) GetStories(ctx context.Context) ([]models.Item, error) {
var items []models.Item
key := "items:stories"
err := c.cache.Once(&cache.Item{
Key: key,
Value: &items,
TTL: c.ttl,
Do: func(*cache.Item) (interface{}, error) {
c.logger.Info(fmt.Sprintf("%s cache missed. fetching from source", key))
return c.db.GetStories(ctx)
},
})
if err != nil {
return nil, err
}
return items, nil
}
// GetJobs fetches all job items from the cache and falls back to fetching from the database
func (c *itemCache) GetJobs(ctx context.Context) ([]models.Item, error) {
var items []models.Item
key := "items:jobs"
err := c.cache.Once(&cache.Item{
Key: key,
Value: &items,
TTL: c.ttl,
Do: func(*cache.Item) (interface{}, error) {
c.logger.Info(fmt.Sprintf("%s cache missed. fetching from source", key))
return c.db.GetJobs(ctx)
},
})
if err != nil {
return nil, err
}
return items, nil
}
func (c *itemCache) Close() {
c.ring.Close()
}