@@ -7,7 +7,22 @@ protocol FeatureFlagCaching {
7
7
8
8
/// Retrieve all cached data for the given cache key.
9
9
///
10
- /// - parameter cacheKey: The unique key into the local cache store.
10
+ /// The cache key is used as the index into the cache. Values retrieved
11
+ /// using this cache key should be loaded into the store and favored over
12
+ /// the default values.
13
+ ///
14
+ /// The context hash value is used to determine if the cache is considered
15
+ /// out of date. If the hash saved alongside the cached value does not
16
+ /// match, then the cache's etag and lastUpdated responses should be nil as
17
+ /// they are invalid.
18
+ ///
19
+ /// If the hash hasn't changed, then the cache is still considered accurate
20
+ /// and the associated etag and last updated values are meaningful and can
21
+ /// be returned.
22
+ ///
23
+ /// - parameter cacheKey: The index key into the local cache store.
24
+ /// - parameter contextHash: A hash value representing a fully unique context.
25
+ ///
11
26
/// - returns: Returns a tuple of cached value information.
12
27
/// items: This is the associated flag evaluation results associated with this context.
13
28
/// etag: The last known e-tag value from a polling request (see saveCachedData
@@ -16,7 +31,7 @@ protocol FeatureFlagCaching {
16
31
/// values, this should return nil.
17
32
///
18
33
///
19
- func getCachedData( cacheKey: String ) -> ( items: StoredItems ? , etag: String ? , lastUpdated: Date ? )
34
+ func getCachedData( cacheKey: String , contextHash : String ) -> ( items: StoredItems ? , etag: String ? , lastUpdated: Date ? )
20
35
21
36
// When we update the cache, we save the flag data and if we have it, an
22
37
// etag. For polling, we should always have the flag data and an etag
@@ -35,7 +50,11 @@ protocol FeatureFlagCaching {
35
50
//
36
51
// 2. Updates have been made at which point the e-tag will be ignored
37
52
// upstream and we will still receive updated information as expected.
38
- func saveCachedData( _ storedItems: StoredItems , cacheKey: String , lastUpdated: Date , etag: String ? )
53
+ //
54
+ // The context hash is stored alongside the stored items. This is used as a
55
+ // marker to determine when the values are useful but not potentially
56
+ // accurate.
57
+ func saveCachedData( _ storedItems: StoredItems , cacheKey: String , contextHash: String , lastUpdated: Date , etag: String ? )
39
58
}
40
59
41
60
final class FeatureFlagCache : FeatureFlagCaching {
@@ -53,11 +72,19 @@ final class FeatureFlagCache: FeatureFlagCaching {
53
72
self . maxCachedContexts = maxCachedContexts
54
73
}
55
74
56
- func getCachedData( cacheKey: String ) -> ( items: StoredItems ? , etag: String ? , lastUpdated: Date ? ) {
75
+ func getCachedData( cacheKey: String , contextHash: String ) -> ( items: StoredItems ? , etag: String ? , lastUpdated: Date ? ) {
76
+
57
77
guard let cachedFlagsData = keyedValueCache. data ( forKey: " flags- \( cacheKey) " ) ,
58
78
let cachedFlags = try ? JSONDecoder ( ) . decode ( StoredItemCollection . self, from: cachedFlagsData)
59
79
else { return ( items: nil , etag: nil , lastUpdated: nil ) }
60
80
81
+ guard let cachedContextHashData = keyedValueCache. data ( forKey: " fingerprint- \( cacheKey) " ) ,
82
+ let cachedContextHash = try ? JSONDecoder ( ) . decode ( String . self, from: cachedContextHashData)
83
+ else { return ( items: cachedFlags. flags, etag: nil , lastUpdated: nil ) }
84
+
85
+ guard cachedContextHash == contextHash
86
+ else { return ( items: cachedFlags. flags, etag: nil , lastUpdated: nil ) }
87
+
61
88
guard let cachedETagData = keyedValueCache. data ( forKey: " etag- \( cacheKey) " ) ,
62
89
let etag = try ? JSONDecoder ( ) . decode ( String . self, from: cachedETagData)
63
90
else { return ( items: cachedFlags. flags, etag: nil , lastUpdated: nil ) }
@@ -73,14 +100,19 @@ final class FeatureFlagCache: FeatureFlagCaching {
73
100
return ( items: cachedFlags. flags, etag: etag, lastUpdated: Date ( timeIntervalSince1970: TimeInterval ( lastUpdated / 1_000 ) ) )
74
101
}
75
102
76
- func saveCachedData( _ storedItems: StoredItems , cacheKey: String , lastUpdated: Date , etag: String ? ) {
103
+ func saveCachedData( _ storedItems: StoredItems , cacheKey: String , contextHash: String , lastUpdated: Date , etag: String ? ) {
104
+
77
105
guard self . maxCachedContexts != 0 , let encoded = try ? JSONEncoder ( ) . encode ( StoredItemCollection ( storedItems) )
78
106
else { return }
79
107
80
108
self . keyedValueCache. set ( encoded, forKey: " flags- \( cacheKey) " )
81
109
82
- if let tag = etag, let encodedCachedData = try ? JSONEncoder ( ) . encode ( tag) {
83
- self . keyedValueCache. set ( encodedCachedData, forKey: " etag- \( cacheKey) " )
110
+ if let encodedContextHashData = try ? JSONEncoder ( ) . encode ( contextHash) {
111
+ self . keyedValueCache. set ( encodedContextHashData, forKey: " fingerprint- \( cacheKey) " )
112
+
113
+ if let tag = etag, let encodedCachedData = try ? JSONEncoder ( ) . encode ( tag) {
114
+ self . keyedValueCache. set ( encodedCachedData, forKey: " etag- \( cacheKey) " )
115
+ }
84
116
}
85
117
86
118
var cachedContexts : [ String : Int64 ] = [ : ]
@@ -94,6 +126,7 @@ final class FeatureFlagCache: FeatureFlagCaching {
94
126
cachedContexts. removeValue ( forKey: sha)
95
127
self . keyedValueCache. removeObject ( forKey: " flags- \( sha) " )
96
128
self . keyedValueCache. removeObject ( forKey: " etag- \( sha) " )
129
+ self . keyedValueCache. removeObject ( forKey: " fingerprint- \( sha) " )
97
130
}
98
131
}
99
132
if let encoded = try ? JSONEncoder ( ) . encode ( cachedContexts) {
0 commit comments