-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtwoLevelBackend.go
173 lines (138 loc) · 4.17 KB
/
twoLevelBackend.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package httpcache
import (
"errors"
"fmt"
"flamingo.me/flamingo/v3/core/healthcheck/domain/healthcheck"
"flamingo.me/flamingo/v3/framework/flamingo"
)
var (
_ Backend = new(TwoLevelBackend)
_ healthcheck.Status = new(TwoLevelBackend)
ErrAllBackendsFailed = errors.New("all backends failed")
ErrAtLeastOneBackendFailed = errors.New("at least one backends failed")
)
type (
// TwoLevelBackend the cache backend interface with a two level solution
TwoLevelBackend struct {
firstBackend Backend
secondBackend Backend
logger flamingo.Logger
}
// TwoLevelBackendConfig defines the backends to be used
TwoLevelBackendConfig struct {
FirstLevel Backend
SecondLevel Backend
}
// TwoLevelBackendFactory creates instances of TwoLevel backends
TwoLevelBackendFactory struct {
logger flamingo.Logger
config TwoLevelBackendConfig
}
)
// Inject dependencies
func (f *TwoLevelBackendFactory) Inject(logger flamingo.Logger) *TwoLevelBackendFactory {
f.logger = logger
return f
}
// SetConfig for factory
func (f *TwoLevelBackendFactory) SetConfig(config TwoLevelBackendConfig) *TwoLevelBackendFactory {
f.config = config
return f
}
// Build the instance
func (f *TwoLevelBackendFactory) Build() (Backend, error) {
return &TwoLevelBackend{
firstBackend: f.config.FirstLevel,
secondBackend: f.config.SecondLevel,
logger: f.logger,
}, nil
}
// Get entry by key
func (mb *TwoLevelBackend) Get(key string) (entry Entry, found bool) {
entry, found = mb.firstBackend.Get(key)
if found {
return entry, found
}
entry, found = mb.secondBackend.Get(key)
if found {
go func() {
_ = mb.firstBackend.Set(key, entry)
}()
return entry, true
}
return Entry{}, false
}
// Set entry for key
func (mb *TwoLevelBackend) Set(key string, entry Entry) error {
errorCount := 0
err := mb.firstBackend.Set(key, entry)
if err != nil {
errorCount++
mb.logger.WithField("category", "TwoLevelBackend").Error(fmt.Sprintf("Failed to set key %v with error %v", key, err))
}
err = mb.secondBackend.Set(key, entry)
if err != nil {
errorCount++
mb.logger.WithField("category", "TwoLevelBackend").Error(fmt.Sprintf("Failed to set key %v with error %v", key, err))
}
if errorCount >= 2 { //nolint:mnd // there are two backends no need to introduce const for that
return ErrAllBackendsFailed
}
return nil
}
// Purge entry by key
func (mb *TwoLevelBackend) Purge(key string) (err error) {
var errorList []error
err = mb.firstBackend.Purge(key)
if err != nil {
errorList = append(errorList, err)
mb.logger.WithField("category", "TwoLevelBackend").Error(fmt.Sprintf("Failed Purge with error %v", err))
}
err = mb.secondBackend.Purge(key)
if err != nil {
errorList = append(errorList, err)
mb.logger.WithField("category", "TwoLevelBackend").Error(fmt.Sprintf("Failed Purge with error %v", err))
}
if 0 != len(errorList) {
return fmt.Errorf("not all backends succeeded to Purge key %v, errors: %v - %w", key, errorList, ErrAtLeastOneBackendFailed)
}
return nil
}
// Flush the whole cache
func (mb *TwoLevelBackend) Flush() (err error) {
var errorList []error
err = mb.firstBackend.Flush()
if err != nil {
errorList = append(errorList, err)
mb.logger.WithField("category", "TwoLevelBackend").Error(fmt.Sprintf("Failed Flush error %v", err))
}
err = mb.secondBackend.Flush()
if err != nil {
errorList = append(errorList, err)
mb.logger.WithField("category", "TwoLevelBackend").Error(fmt.Sprintf("Failed Flush error %v", err))
}
if 0 != len(errorList) {
return fmt.Errorf("not all backends succeeded to Flush. errors: %v - %w", errorList, ErrAtLeastOneBackendFailed)
}
return nil
}
// Status checks the health of the used backends
func (mb *TwoLevelBackend) Status() (bool, string) {
healthy := true
details := ""
if firstHealth, ok := mb.firstBackend.(healthcheck.Status); ok {
alive, notes := firstHealth.Status()
if !alive {
healthy = false
details += fmt.Sprintf("first backend: %s", notes)
}
}
if secondHealth, ok := mb.secondBackend.(healthcheck.Status); ok {
alive, notes := secondHealth.Status()
if !alive {
healthy = false
details += fmt.Sprintf("second backend: %s", notes)
}
}
return healthy, details
}