startback 0.11.3 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,7 @@ module Startback
14
14
 
15
15
  end # module Support
16
16
  end # module Startback
17
+ require_relative 'support/env'
17
18
  require_relative 'support/log_formatter'
18
19
  require_relative 'support/logger'
19
20
  require_relative 'support/robustness'
@@ -1,8 +1,8 @@
1
1
  module Startback
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 11
5
- TINY = 3
4
+ MINOR = 12
5
+ TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'startback'
3
- require 'startback/bus'
3
+ require 'startback/event'
4
4
  require 'startback/support/fake_logger'
5
5
  require 'rack/test'
6
6
 
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ module Startback
3
+ class Event
4
+ describe Bus::Memory do
5
+
6
+ subject{
7
+ Bus::Memory::Async.new
8
+ }
9
+
10
+ it 'allows emiting an receiving' do
11
+ seen = nil
12
+ subject.listen("user_changed") do |evt|
13
+ seen = evt
14
+ end
15
+ subject.emit(Event.new("user_changed", {id: 12}))
16
+ expect(seen).to be_a(Event)
17
+ expect(seen.type).to eql("user_changed")
18
+ expect(seen.data.to_h).to eql({id: 12})
19
+ end
20
+
21
+ it 'allows mixin Symbol vs. String for event type' do
22
+ seen = nil
23
+ subject.listen(:user_changed) do |evt|
24
+ seen = evt
25
+ end
26
+ subject.emit(Event.new(:user_changed, {id: 12}))
27
+ expect(seen).to be_a(Event)
28
+ expect(seen.type).to eql("user_changed")
29
+ expect(seen.data.to_h).to eql({id: 12})
30
+ end
31
+
32
+ it 'does not raise errors synchronously' do
33
+ subject.listen("user_changed") do |evt|
34
+ raise "An error occured"
35
+ end
36
+ expect {
37
+ subject.emit(Event.new("user_changed", {id: 12}))
38
+ }.not_to raise_error
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ module Startback
3
+ class Event
4
+ describe Bus::Memory do
5
+
6
+ subject{
7
+ Bus::Memory::Sync.new
8
+ }
9
+
10
+ it 'allows emiting an receiving' do
11
+ seen = nil
12
+ subject.listen("user_changed") do |evt|
13
+ seen = evt
14
+ end
15
+ subject.emit(Event.new("user_changed", {id: 12}))
16
+ expect(seen).to be_a(Event)
17
+ expect(seen.type).to eql("user_changed")
18
+ expect(seen.data.to_h).to eql({id: 12})
19
+ end
20
+
21
+ it 'allows mixin Symbol vs. String for event type' do
22
+ seen = nil
23
+ subject.listen(:user_changed) do |evt|
24
+ seen = evt
25
+ end
26
+ subject.emit(Event.new(:user_changed, {id: 12}))
27
+ expect(seen).to be_a(Event)
28
+ expect(seen.type).to eql("user_changed")
29
+ expect(seen.data.to_h).to eql({id: 12})
30
+ end
31
+
32
+ it 'raises emit errors synchronously' do
33
+ subject.listen("user_changed") do |evt|
34
+ raise "An error occured"
35
+ end
36
+ expect {
37
+ subject.emit(Event.new("user_changed", {id: 12}))
38
+ }.to raise_error("An error occured")
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ module Startback
3
+ module Support
4
+ describe Env do
5
+ include Env
6
+
7
+ before do
8
+ ENV['FOO'] = 'BAR'
9
+ ENV['FOOL'] = ''
10
+ ENV['FOOLISH'] = ' BAR '
11
+ end
12
+
13
+ after do
14
+ ENV.delete('FOO')
15
+ ENV.delete('FOOL')
16
+ end
17
+
18
+ describe "env" do
19
+ it 'returns an env variable' do
20
+ expect(env('FOO')).to eql('BAR')
21
+ end
22
+
23
+ it 'returns nil otherwise' do
24
+ expect(env('BAR')).to be_nil
25
+ end
26
+
27
+ it 'strips the value' do
28
+ expect(env('FOOLISH')).to eql('BAR')
29
+ end
30
+
31
+ it 'yields the block if any' do
32
+ expect(env('FOO'){|x| x.downcase }).to eql('bar')
33
+ end
34
+
35
+ it 'support a default value' do
36
+ expect(env('BAR', 'BAZ')).to eql('BAZ')
37
+ end
38
+
39
+ it 'yields the block with the default if any' do
40
+ expect(env('BAR', 'BAZ'){|x| x.downcase }).to eql('baz')
41
+ end
42
+
43
+ it 'returns nil when empty' do
44
+ expect(env('FOOL')).to be_nil
45
+ end
46
+
47
+ it 'yields the block with the default if empty' do
48
+ expect(env('FOOL', 'BAZ'){|x| x.downcase }).to eql('baz')
49
+ end
50
+ end
51
+
52
+ describe "env!" do
53
+ it 'returns an env variable' do
54
+ expect(env!('FOO')).to eql('BAR')
55
+ end
56
+
57
+ it 'strips the value' do
58
+ expect(env!('FOOLISH')).to eql('BAR')
59
+ end
60
+
61
+ it 'raise otherwise' do
62
+ expect{ env!('BAR') }.to raise_error(Startback::Errors::Error, /BAR/)
63
+ end
64
+
65
+ it 'raise on empty' do
66
+ expect{ env!('FOOL') }.to raise_error(Startback::Errors::Error, /FOOL/)
67
+ end
68
+
69
+ it 'yields the block if any' do
70
+ expect(env('FOO'){|x| x.downcase }).to eql('bar')
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -135,6 +135,16 @@ module Startback
135
135
  expect(logger).to be_a(::Logger)
136
136
  end
137
137
 
138
+ it 'works fine with a Exception only' do
139
+ exception = StandardError.new('hello')
140
+ expected = {
141
+ error: exception
142
+ }
143
+ log_msg, logger = parse_args(exception)
144
+ expect(log_msg).to eql(expected)
145
+ expect(logger).to be_a(::Logger)
146
+ end
147
+
138
148
  it 'works fine with a string and a context with logger' do
139
149
  expected = {
140
150
  op: "a message"
@@ -216,4 +226,4 @@ module Startback
216
226
 
217
227
  end
218
228
  end
219
- end
229
+ end
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.11.3
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-10 00:00:00.000000000 Z
11
+ date: 2022-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -385,12 +385,6 @@ files:
385
385
  - lib/startback/audit/prometheus.rb
386
386
  - lib/startback/audit/shared.rb
387
387
  - lib/startback/audit/trailer.rb
388
- - lib/startback/bus.rb
389
- - lib/startback/bus/bunny.rb
390
- - lib/startback/bus/bunny/async.rb
391
- - lib/startback/bus/memory.rb
392
- - lib/startback/bus/memory/async.rb
393
- - lib/startback/bus/memory/sync.rb
394
388
  - lib/startback/caching/entity_cache.rb
395
389
  - lib/startback/caching/no_store.rb
396
390
  - lib/startback/caching/store.rb
@@ -398,6 +392,14 @@ files:
398
392
  - lib/startback/context/middleware.rb
399
393
  - lib/startback/errors.rb
400
394
  - lib/startback/event.rb
395
+ - lib/startback/event/agent.rb
396
+ - lib/startback/event/bus.rb
397
+ - lib/startback/event/bus/bunny.rb
398
+ - lib/startback/event/bus/bunny/async.rb
399
+ - lib/startback/event/bus/memory.rb
400
+ - lib/startback/event/bus/memory/async.rb
401
+ - lib/startback/event/bus/memory/sync.rb
402
+ - lib/startback/event/engine.rb
401
403
  - lib/startback/ext.rb
402
404
  - lib/startback/ext/date_time.rb
403
405
  - lib/startback/ext/time.rb
@@ -405,6 +407,7 @@ files:
405
407
  - lib/startback/operation/error_operation.rb
406
408
  - lib/startback/operation/multi_operation.rb
407
409
  - lib/startback/support.rb
410
+ - lib/startback/support/env.rb
408
411
  - lib/startback/support/fake_logger.rb
409
412
  - lib/startback/support/hooks.rb
410
413
  - lib/startback/support/log_formatter.rb
@@ -428,17 +431,18 @@ files:
428
431
  - spec/spec_helper.rb
429
432
  - spec/unit/audit/test_prometheus.rb
430
433
  - spec/unit/audit/test_trailer.rb
431
- - spec/unit/bus/memory/test_async.rb
432
- - spec/unit/bus/memory/test_sync.rb
433
434
  - spec/unit/caching/test_entity_cache.rb
434
435
  - spec/unit/context/test_abstraction_factory.rb
435
436
  - spec/unit/context/test_dup.rb
436
437
  - spec/unit/context/test_h_factory.rb
437
438
  - spec/unit/context/test_middleware.rb
439
+ - spec/unit/event/bus/memory/test_async.rb
440
+ - spec/unit/event/bus/memory/test_sync.rb
438
441
  - spec/unit/support/hooks/test_after_hook.rb
439
442
  - spec/unit/support/hooks/test_before_hook.rb
440
443
  - spec/unit/support/operation_runner/test_around_run.rb
441
444
  - spec/unit/support/operation_runner/test_before_after_call.rb
445
+ - spec/unit/support/test_env.rb
442
446
  - spec/unit/support/test_robusteness.rb
443
447
  - spec/unit/support/test_transaction_manager.rb
444
448
  - spec/unit/test_event.rb
@@ -1,123 +0,0 @@
1
- require 'bunny'
2
- module Startback
3
- class Bus
4
- module Bunny
5
- #
6
- # Asynchronous implementation of the bus abstraction, on top of RabbitMQ
7
- # and using the 'bunny' gem (you need to include it in your Gemfile
8
- # yourself: it is NOT a startback official dependency).
9
- #
10
- # This bus implementation emits events by dumping them to RabbitMQ using
11
- # the event type as exchange name. Listeners may use the `processor`
12
- # parameter to specify the queue name ; otherwise a default "main" queue
13
- # is used.
14
- #
15
- # Examples:
16
- #
17
- # # Connects to RabbitMQ using all default options
18
- # #
19
- # # Uses the STARTBACK_BUS_BUNNY_ASYNC_URL environment variable for
20
- # # connection URL if present.
21
- # Startback::Bus::Bunny::Async.new
22
- #
23
- # # Connects to RabbitMQ using a specific URL
24
- # Startback::Bus::Bunny::Async.new("amqp://rabbituser:rabbitpass@192.168.17.17")
25
- # Startback::Bus::Bunny::Async.new(url: "amqp://rabbituser:rabbitpass@192.168.17.17")
26
- #
27
- # # Connects to RabbitMQ using specific connection options. See Bunny's own
28
- # # documentation
29
- # Startback::Bus::Bunny::Async.new({
30
- # connection_options: {
31
- # host: "192.168.17.17"
32
- # }
33
- # })
34
- #
35
- class Async
36
- include Support::Robustness
37
-
38
- CHANNEL_KEY = 'Startback::Bus::Bunny::Async::ChannelKey'
39
-
40
- DEFAULT_OPTIONS = {
41
- # (optional) The URL to use for connecting to RabbitMQ.
42
- url: ENV['STARTBACK_BUS_BUNNY_ASYNC_URL'],
43
-
44
- # (optional) The options has to pass to ::Bunny constructor
45
- connection_options: nil,
46
-
47
- # (optional) The options to use for the emitter/listener fanout
48
- fanout_options: {},
49
-
50
- # (optional) The options to use for the listener queue
51
- queue_options: {},
52
-
53
- # (optional) Default event factory to use, if any
54
- event_factory: nil,
55
-
56
- # (optional) A default context to use for general logging
57
- context: nil
58
- }
59
-
60
- # Creates a bus instance, using the various options provided to
61
- # fine-tune behavior.
62
- def initialize(options = {})
63
- options = { url: options } if options.is_a?(String)
64
- @options = DEFAULT_OPTIONS.merge(options)
65
- retried = 0
66
- conn = options[:connection_options] || options[:url]
67
- try_max_times(10) do
68
- @bunny = ::Bunny.new(conn)
69
- @bunny.start
70
- channel
71
- log(:info, {op: "#{self.class.name}#connect", op_data: conn}, options[:context])
72
- end
73
- end
74
- attr_reader :options
75
-
76
- def channel
77
- Thread.current[CHANNEL_KEY] ||= @bunny.create_channel
78
- end
79
-
80
- def emit(event)
81
- stop_errors(self, "emit", event.context) do
82
- fanout = channel.fanout(event.type.to_s, fanout_options)
83
- fanout.publish(event.to_json)
84
- end
85
- end
86
-
87
- def listen(type, processor = nil, listener = nil, &bl)
88
- raise ArgumentError, "A listener must be provided" unless listener || bl
89
- fanout = channel.fanout(type.to_s, fanout_options)
90
- queue = channel.queue((processor || "main").to_s, queue_options)
91
- queue.bind(fanout)
92
- queue.subscribe do |delivery_info, properties, body|
93
- event = stop_errors(self, "listen") do
94
- factor_event(body)
95
- end
96
- stop_errors(self, "listen", event.context) do
97
- (listener || bl).call(event)
98
- end
99
- end
100
- end
101
-
102
- protected
103
-
104
- def fanout_options
105
- options[:fanout_options]
106
- end
107
-
108
- def queue_options
109
- options[:queue_options]
110
- end
111
-
112
- def factor_event(body)
113
- if options[:event_factory]
114
- options[:event_factory].call(body)
115
- else
116
- Event.json(body, options)
117
- end
118
- end
119
-
120
- end # class Async
121
- end # module Bunny
122
- end # class Bus
123
- end # module Klaro
@@ -1,40 +0,0 @@
1
- module Startback
2
- class Bus
3
- module Memory
4
- #
5
- # Asynchronous implementation of the Bus abstraction, for use between
6
- # components sharing the same process.
7
- #
8
- # This implementation actually calls listeners synchronously (it mays)
9
- # but hides error raised by them. See Bus::Bunny::Async for another
10
- # implementation that is truly asynchronous and relies on RabbitMQ.
11
- #
12
- class Async
13
- include Support::Robustness
14
-
15
- DEFAULT_OPTIONS = {
16
- }
17
-
18
- def initialize(options = {})
19
- @options = DEFAULT_OPTIONS.merge(options)
20
- @listeners = {}
21
- end
22
-
23
- def emit(event)
24
- (@listeners[event.type.to_s] || []).each do |l|
25
- stop_errors(self, "emit", event) {
26
- l.call(event)
27
- }
28
- end
29
- end
30
-
31
- def listen(type, processor = nil, listener = nil, &bl)
32
- raise ArgumentError, "A listener must be provided" unless listener || bl
33
- @listeners[type.to_s] ||= []
34
- @listeners[type.to_s] << (listener || bl)
35
- end
36
-
37
- end # class Sync
38
- end # module Memory
39
- end # class Bus
40
- end # module Klaro
@@ -1,30 +0,0 @@
1
- module Startback
2
- class Bus
3
- module Memory
4
- #
5
- # Synchronous implementation of the Bus abstraction, for use between
6
- # components sharing the same process.
7
- #
8
- class Sync
9
- include Support::Robustness
10
-
11
- def initialize
12
- @listeners = {}
13
- end
14
-
15
- def emit(event)
16
- (@listeners[event.type.to_s] || []).each do |l|
17
- l.call(event)
18
- end
19
- end
20
-
21
- def listen(type, processor = nil, listener = nil, &bl)
22
- raise ArgumentError, "A listener must be provided" unless listener || bl
23
- @listeners[type.to_s] ||= []
24
- @listeners[type.to_s] << (listener || bl)
25
- end
26
-
27
- end # class Sync
28
- end # module Memory
29
- end # class Bus
30
- end # module Klaro
data/lib/startback/bus.rb DELETED
@@ -1,94 +0,0 @@
1
- require 'startback/event'
2
- module Startback
3
- #
4
- # Sync and async bus abstraction allowing to register listeners and
5
- # emitting events towards them.
6
- #
7
- # This bus actually decorates two busses, one in synchronous and the
8
- # other one is asynchronous (optional).
9
- #
10
- # * A synchronous bus MUST call the listeners as part of emitting
11
- # process, and MUST re-raise any error occuring during that process.
12
- # See, e.g. Startback::Bus::Memory::Sync
13
- #
14
- # * An asynchronous bus MAY call the listeners later, but MUST hide
15
- # errors to the emitter.
16
- # See, e.g. Startback::Bus::Memory::Async
17
- #
18
- # This bus facade emits events to both sync and async busses (if any),
19
- # and listen on the sync one by default.
20
- #
21
- # For emitters:
22
- #
23
- # # This will synchronously call every listeners who `listen`
24
- # # on the synchronous bus (& reraise exceptions) then call
25
- # # (possibly later) all listeners who `listen` on the
26
- # # asynchronous bus if any (& hide exceptions).
27
- # bus.emit(event)
28
- #
29
- # # This only reaches sync listeners
30
- # bus.sync.emit(event)
31
- #
32
- # # This only reaches async listeners (an async bus must be set)
33
- # bus.async.emit(event)
34
- #
35
- # Please note that there is currently no way to reach sync listeners
36
- # without having to implement error handling on the emitter side.
37
- #
38
- # For listeners:
39
- #
40
- # # This will listen synchronously and make the emitter fail if
41
- # # anything goes wrong with the callback:
42
- # bus.listen(event_type) do |event|
43
- # ...
44
- # end
45
- #
46
- # # It is a shortcut for:
47
- # bus.sync.listen(event_type) do |event| ... end
48
- #
49
- # This will listen asynchronously and could not make the emitter
50
- # fail if something goes wrong with the callback.
51
- # bus.async.listen(event_type) do |event|
52
- # ...
53
- # end
54
- #
55
- # Feel free to access the sync and async busses directly for specific
56
- # cases though.
57
- #
58
- class Bus
59
- include Support::Robustness
60
-
61
- def initialize(sync = Memory::Sync.new, async = nil)
62
- @sync = sync
63
- @async = async
64
- end
65
- attr_reader :sync, :async
66
-
67
- # Emits a particular event to the listeners.
68
- #
69
- # @arg event an event, should be an Event instance (through duck
70
- # typing is allowed)
71
- def emit(event)
72
- monitor({
73
- op: "Startback::Bus#emit",
74
- op_data: {
75
- event: { type: event.type }
76
- }
77
- }, event.context) do
78
- sync.emit(event)
79
- async.emit(event) if async
80
- end
81
- end
82
-
83
- # Registers `listener` as being interested in receiving events of
84
- # a specific type.
85
- #
86
- # @arg type: Symbol, the type of event the listener is interested in.
87
- # @arg listener: Proc, the listener itself.
88
- def listen(type, processor = nil, listener = nil, &bl)
89
- sync.listen(type, processor, listener, &bl)
90
- end
91
-
92
- end # class Bus
93
- end # module Klaro
94
- require_relative 'bus/memory'
@@ -1,41 +0,0 @@
1
- require 'spec_helper'
2
- module Startback
3
- describe Bus::Memory do
4
-
5
- subject{
6
- Bus::Memory::Async.new
7
- }
8
-
9
- it 'allows emiting an receiving' do
10
- seen = nil
11
- subject.listen("user_changed") do |evt|
12
- seen = evt
13
- end
14
- subject.emit(Event.new("user_changed", {id: 12}))
15
- expect(seen).to be_a(Event)
16
- expect(seen.type).to eql("user_changed")
17
- expect(seen.data.to_h).to eql({id: 12})
18
- end
19
-
20
- it 'allows mixin Symbol vs. String for event type' do
21
- seen = nil
22
- subject.listen(:user_changed) do |evt|
23
- seen = evt
24
- end
25
- subject.emit(Event.new(:user_changed, {id: 12}))
26
- expect(seen).to be_a(Event)
27
- expect(seen.type).to eql("user_changed")
28
- expect(seen.data.to_h).to eql({id: 12})
29
- end
30
-
31
- it 'does not raise errors synchronously' do
32
- subject.listen("user_changed") do |evt|
33
- raise "An error occured"
34
- end
35
- expect {
36
- subject.emit(Event.new("user_changed", {id: 12}))
37
- }.not_to raise_error
38
- end
39
-
40
- end
41
- end
@@ -1,41 +0,0 @@
1
- require 'spec_helper'
2
- module Startback
3
- describe Bus::Memory do
4
-
5
- subject{
6
- Bus::Memory::Sync.new
7
- }
8
-
9
- it 'allows emiting an receiving' do
10
- seen = nil
11
- subject.listen("user_changed") do |evt|
12
- seen = evt
13
- end
14
- subject.emit(Event.new("user_changed", {id: 12}))
15
- expect(seen).to be_a(Event)
16
- expect(seen.type).to eql("user_changed")
17
- expect(seen.data.to_h).to eql({id: 12})
18
- end
19
-
20
- it 'allows mixin Symbol vs. String for event type' do
21
- seen = nil
22
- subject.listen(:user_changed) do |evt|
23
- seen = evt
24
- end
25
- subject.emit(Event.new(:user_changed, {id: 12}))
26
- expect(seen).to be_a(Event)
27
- expect(seen.type).to eql("user_changed")
28
- expect(seen.data.to_h).to eql({id: 12})
29
- end
30
-
31
- it 'raises emit errors synchronously' do
32
- subject.listen("user_changed") do |evt|
33
- raise "An error occured"
34
- end
35
- expect {
36
- subject.emit(Event.new("user_changed", {id: 12}))
37
- }.to raise_error("An error occured")
38
- end
39
-
40
- end
41
- end