startback 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|