startback 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/startback/audit/trailer.rb +1 -0
- data/lib/startback/caching/entity_cache.rb +94 -17
- data/lib/startback/support/robustness.rb +11 -11
- data/lib/startback/version.rb +1 -1
- data/lib/startback/web/catch_all.rb +0 -1
- data/spec/unit/caching/test_entity_cache.rb +27 -0
- data/spec/unit/support/test_robusteness.rb +11 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61d4c08ece5a3e956448ef53ca8bfa1a0e4e3b5e9280eb39e705858b9939b708
|
4
|
+
data.tar.gz: 3c481e062a5ab699d8c72ba9625f7528f41b94f075177df284f238d9117cacce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc079a6ced8d0cbec9a23b552056d06a4ebc139a5afb54cbc4eadb7c416ca390263e3c3c29c47009e53954db8176305a398fb8ceaddb3696a676bf6d414f0a45
|
7
|
+
data.tar.gz: 6ed71d2a2b742a0634cb3fca8c92e1e0b9a835bbf55f9ada5a9144f40184d01338065ce587d98035e6409a31424ef2a01818e566f5a1cb56e618962f7ac360df
|
@@ -5,16 +5,29 @@ module Startback
|
|
5
5
|
#
|
6
6
|
# This class MUST be overriden:
|
7
7
|
#
|
8
|
-
# * the `
|
9
|
-
#
|
10
|
-
#
|
8
|
+
# * the `load_entity` protected method MUST be implemented to load data from
|
9
|
+
# a primary & context unaware key.
|
10
|
+
#
|
11
|
+
# * the `primary_key` protected method MAY be implemented to convert candidate
|
12
|
+
# keys (received from ultimate callers) to primary keys. The method is also
|
13
|
+
# a good place to check and/or log the keys actually used by callers.
|
14
|
+
#
|
15
|
+
# * the `context_free_key` protected method MAY be overriden to provide
|
16
|
+
# domain unrelated caching keys from primary keys, e.g. by encoding the
|
17
|
+
# context into the caching key itself, if needed.
|
18
|
+
#
|
11
19
|
# * the `valid?` protected method MAY be overriden to check validity of data
|
12
|
-
# extracted from the cache.
|
20
|
+
# extracted from the cache and force a refresh even if found.
|
13
21
|
#
|
14
22
|
# An EntityCache takes an actual store at construction. The object must meet the
|
15
23
|
# specification writtern in Store. The 'cache' ruby gem can be used in practice.
|
16
24
|
#
|
25
|
+
# Cache hits, outdated and miss are logged in debug, info, and info severity.
|
26
|
+
# The `cache_hit`, `cache_outdated`, `cache_miss` protected methods MAY be
|
27
|
+
# overriden to change that behavior.
|
28
|
+
#
|
17
29
|
class EntityCache
|
30
|
+
include Support::Robustness
|
18
31
|
|
19
32
|
class << self
|
20
33
|
|
@@ -37,42 +50,106 @@ module Startback
|
|
37
50
|
#
|
38
51
|
# If the entity is not in cache, loads it and puts it in cache using
|
39
52
|
# the caching options passed as second parameter.
|
40
|
-
def get(
|
41
|
-
|
53
|
+
def get(candidate_key, caching_options = default_caching_options)
|
54
|
+
pkey = primary_key(candidate_key)
|
55
|
+
cache_key = encode_key(context_free_key(pkey))
|
42
56
|
if store.exist?(cache_key)
|
43
57
|
cached = store.get(cache_key)
|
44
|
-
|
58
|
+
if valid?(pkey, cached)
|
59
|
+
cache_hit(pkey, cached)
|
60
|
+
return cached
|
61
|
+
else
|
62
|
+
cache_outdated(pkey, cached)
|
63
|
+
end
|
45
64
|
end
|
46
|
-
|
65
|
+
cache_miss(pkey)
|
66
|
+
load_entity(pkey).tap{|to_cache|
|
47
67
|
store.set(cache_key, to_cache, caching_options)
|
48
68
|
}
|
49
69
|
end
|
50
70
|
|
51
71
|
# Invalidates the cache under a given key.
|
52
|
-
def invalidate(
|
53
|
-
|
72
|
+
def invalidate(candidate_key)
|
73
|
+
pkey = primary_key(candidate_key)
|
74
|
+
cache_key = encode_key(context_free_key(pkey))
|
75
|
+
store.delete(cache_key)
|
54
76
|
end
|
55
77
|
|
56
78
|
protected
|
57
79
|
|
58
|
-
def
|
59
|
-
|
80
|
+
def cache_hit(pkey, cached)
|
81
|
+
log(:debug, self, "cache_hit", op_data: pkey)
|
82
|
+
end
|
83
|
+
|
84
|
+
def cache_outdated(pkey, cached)
|
85
|
+
log(:info, self, "cache_outdated", op_data: pkey)
|
86
|
+
end
|
87
|
+
|
88
|
+
def cache_miss(pkey)
|
89
|
+
log(:info, self, "cache_miss", op_data: pkey)
|
60
90
|
end
|
61
91
|
|
62
92
|
def default_caching_options
|
63
93
|
{ ttl: self.class.default_ttl }
|
64
94
|
end
|
65
95
|
|
66
|
-
|
96
|
+
# Converts a candidate key to a primary key, so as to prevent
|
97
|
+
# cache duplicates if callers are allowed to request an entity
|
98
|
+
# through various keys.
|
99
|
+
#
|
100
|
+
# The default implementation returns the candidate key and MAY
|
101
|
+
# be overriden.
|
102
|
+
def primary_key(candidate_key)
|
103
|
+
candidate_key
|
104
|
+
end
|
105
|
+
|
106
|
+
# Encodes a context free key to an actual cache key.
|
107
|
+
#
|
108
|
+
# Default implementation uses JSON.fast_generate but MAY be
|
109
|
+
# overriden.
|
110
|
+
def encode_key(context_free_key)
|
111
|
+
JSON.fast_generate(context_free_key)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns whether `cached` entity seems fresh enough to
|
115
|
+
# be returned as a cache hit.
|
116
|
+
#
|
117
|
+
# This method provides a way to check freshness using, e.g.
|
118
|
+
# `updated_at` or `etag` kind of entity fields. The default
|
119
|
+
# implementation returns true and MAY be overriden.
|
120
|
+
def valid?(primary_key, cached)
|
67
121
|
true
|
68
122
|
end
|
69
123
|
|
70
|
-
|
71
|
-
|
124
|
+
# Converts a primary_key to a context_free_key, using the
|
125
|
+
# context (instance variable) to encode the context itself
|
126
|
+
# into the actual cache key.
|
127
|
+
#
|
128
|
+
# The default implementation simply returns the primary key
|
129
|
+
# and MAY be overriden.
|
130
|
+
def context_free_key(primary_key)
|
131
|
+
full_key(primary_key)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Deprecated, will be removed in 0.6.0. Use context_free_key
|
135
|
+
# instead.
|
136
|
+
def full_key(primary_key)
|
137
|
+
primary_key
|
138
|
+
end
|
139
|
+
|
140
|
+
# Actually loads the entity using the given primary key, and
|
141
|
+
# possibly the cache context.
|
142
|
+
#
|
143
|
+
# This method MUST be implemented and raises a NotImplementedError
|
144
|
+
# by default.
|
145
|
+
def load_entity(primary_key)
|
146
|
+
load_raw_data(primary_key)
|
72
147
|
end
|
73
148
|
|
74
|
-
|
75
|
-
|
149
|
+
# Deprecated, will be removed in 0.6.0. Use load_entity
|
150
|
+
# instead.
|
151
|
+
def load_raw_data(*args, &bl)
|
152
|
+
raise NotImplementedError, "#{self.class.name}#load_entity"
|
76
153
|
end
|
77
154
|
|
78
155
|
end # class EntityCache
|
@@ -24,12 +24,13 @@ module Startback
|
|
24
24
|
#
|
25
25
|
# Examples:
|
26
26
|
#
|
27
|
-
# log(op: "hello", op_data: {foo: 12})
|
28
|
-
# log("A simple message")
|
29
|
-
# log(Startback, "hello")
|
30
|
-
# log(Event.new, "hello")
|
31
|
-
# log(
|
32
|
-
# log(self,
|
27
|
+
# log(:info, op: "hello", op_data: {foo: 12}) => logged as such on STDOUT
|
28
|
+
# log(:info, "A simple message") => { op: "A simple message" } on STDOUT
|
29
|
+
# log(:info, Startback, "hello") => { op: "Startback#hello" } on STDOUT
|
30
|
+
# log(:info, Event.new, "hello") => { op: "Event#hello" } on STDOUT
|
31
|
+
# log(:info, Event.new, "hello", "hello world") => { op: "Event#hello", op_data: { message: "hello world" } } on STDOUT
|
32
|
+
# log(:info, self, context) => { op: "..." } on context's logger or STDOUT
|
33
|
+
# log(:info, self, event) => { op: "..." } on event context's logger or STDOUT
|
33
34
|
# ...
|
34
35
|
#
|
35
36
|
module Robustness
|
@@ -41,7 +42,6 @@ module Startback
|
|
41
42
|
def default_logger
|
42
43
|
@@default_logger ||= ::Logger.new(STDOUT)
|
43
44
|
from = caller.reject{|x| x =~ /lib\/startback/ }.first
|
44
|
-
@@default_logger.debug("Watch out, using default logger from: #{from}")
|
45
45
|
@@default_logger
|
46
46
|
end
|
47
47
|
module_function :default_logger
|
@@ -56,7 +56,8 @@ module Startback
|
|
56
56
|
|
57
57
|
def parse_args(log_msg, method = nil, context = nil, extra = nil)
|
58
58
|
method, context, extra = nil, method, context unless method.is_a?(String)
|
59
|
-
context, extra = nil, context if context.is_a?(Hash) && extra.nil?
|
59
|
+
context, extra = nil, context if context.is_a?(Hash) || context.is_a?(String) && extra.nil?
|
60
|
+
extra = { op_data: { message: extra } } if extra.is_a?(String)
|
60
61
|
logger = logger_for(context) || logger_for(log_msg)
|
61
62
|
log_msg = if log_msg.is_a?(Hash)
|
62
63
|
log_msg.dup
|
@@ -98,10 +99,9 @@ module Startback
|
|
98
99
|
took = Benchmark.realtime {
|
99
100
|
result = bl.call
|
100
101
|
}
|
101
|
-
Tools.
|
102
|
+
Tools.info(args, op_took: took)
|
102
103
|
result
|
103
104
|
rescue => ex
|
104
|
-
Tools.error(args, error: ex)
|
105
105
|
raise
|
106
106
|
end
|
107
107
|
|
@@ -117,7 +117,7 @@ module Startback
|
|
117
117
|
}
|
118
118
|
result
|
119
119
|
rescue => ex
|
120
|
-
Tools.
|
120
|
+
Tools.fatal(args, op_took: took, error: ex)
|
121
121
|
nil
|
122
122
|
end
|
123
123
|
|
data/lib/startback/version.rb
CHANGED
@@ -16,6 +16,18 @@ module Startback
|
|
16
16
|
|
17
17
|
protected
|
18
18
|
|
19
|
+
def primary_key(ckey)
|
20
|
+
case ckey
|
21
|
+
when Integer then "a key"
|
22
|
+
when String then ckey
|
23
|
+
else
|
24
|
+
raise "Invalid key `#{ckey}`"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# We use the deprecated methods below to test
|
29
|
+
# backward compatibility with 0.5.0.
|
30
|
+
|
19
31
|
def full_key(key)
|
20
32
|
{ k: key }
|
21
33
|
end
|
@@ -77,6 +89,21 @@ module Startback
|
|
77
89
|
|
78
90
|
end
|
79
91
|
|
92
|
+
describe "primary_key" do
|
93
|
+
|
94
|
+
subject{
|
95
|
+
cache.get(12)
|
96
|
+
}
|
97
|
+
|
98
|
+
it 'allows using candidate keys' do
|
99
|
+
expect(subject).to eql("a value")
|
100
|
+
expect(subject).to eql("a value")
|
101
|
+
expect(cache.called).to eql(1)
|
102
|
+
expect(cache.last_key).to eql("a key")
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
80
107
|
describe "invalidate" do
|
81
108
|
|
82
109
|
it 'strips the key on the store, yielding a cache miss' do
|
@@ -61,7 +61,7 @@ module Startback
|
|
61
61
|
seen += 1
|
62
62
|
raise "Test"
|
63
63
|
end
|
64
|
-
}.to raise_error
|
64
|
+
}.to raise_error("Test")
|
65
65
|
expect(seen).to eql(2)
|
66
66
|
expect(the_logger.last_msg.keys).to eql([:op, :op_took, :error])
|
67
67
|
end
|
@@ -116,6 +116,16 @@ module Startback
|
|
116
116
|
expect(logger).to be_a(::Logger)
|
117
117
|
end
|
118
118
|
|
119
|
+
it 'works fine with an string, a method name, and a message' do
|
120
|
+
expected = {
|
121
|
+
op: "AClass#method",
|
122
|
+
op_data: { message: "Hello world" }
|
123
|
+
}
|
124
|
+
log_msg, logger = parse_args("AClass", "method", "Hello world")
|
125
|
+
expect(log_msg).to eql(expected)
|
126
|
+
expect(logger).to be_a(::Logger)
|
127
|
+
end
|
128
|
+
|
119
129
|
it 'works fine with a string only' do
|
120
130
|
expected = {
|
121
131
|
op: "a message"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: startback
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|