startback 0.11.5 → 0.12.0
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/event/agent.rb +63 -0
- data/lib/startback/event/bus/bunny/async.rb +165 -0
- data/lib/startback/{bus → event/bus}/bunny.rb +0 -0
- data/lib/startback/event/bus/memory/async.rb +45 -0
- data/lib/startback/event/bus/memory/sync.rb +35 -0
- data/lib/startback/{bus → event/bus}/memory.rb +0 -0
- data/lib/startback/event/bus.rb +100 -0
- data/lib/startback/event/engine.rb +127 -56
- data/lib/startback/event.rb +1 -0
- data/lib/startback/support/robustness.rb +0 -2
- data/lib/startback/version.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/event/bus/memory/test_async.rb +43 -0
- data/spec/unit/event/bus/memory/test_sync.rb +43 -0
- metadata +10 -10
- data/lib/startback/bus/bunny/async.rb +0 -123
- data/lib/startback/bus/memory/async.rb +0 -40
- data/lib/startback/bus/memory/sync.rb +0 -30
- data/lib/startback/bus.rb +0 -94
- data/spec/unit/bus/memory/test_async.rb +0 -41
- data/spec/unit/bus/memory/test_sync.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67ffd7abceaf1b4e048cc8f02f668b682ea81f62fc7de5228a9f0d12abab1df8
|
4
|
+
data.tar.gz: 1eb075bdec30a569726bc3f2a74c1ce427aef8ff0c3cbcee401a61c034e71ca6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cb4d6f1302a34cab267c836c1e0d81c54267607de7f24d350183ff05a79af7cc216a6f34bc96c0e1204f71cca1de744fde448590e99bd96a1ab45c15c8ceddf
|
7
|
+
data.tar.gz: fecf573e39eb04317d756414349d94092f24a07210629a55347191ef74ec94fcc8079f8a4f38b6c58bf94be5c59fbd79a90a168a4925819ce771269ccae6e3da
|
@@ -1,7 +1,70 @@
|
|
1
1
|
module Startback
|
2
2
|
class Event
|
3
|
+
#
|
4
|
+
# An agent listen to specific events and react with its
|
5
|
+
# `call` method.
|
6
|
+
#
|
7
|
+
# This class is intended to be subclasses and the following
|
8
|
+
# methods overriden:
|
9
|
+
#
|
10
|
+
# - install_listeners that installs sync and async listeners
|
11
|
+
# - call to create a context and implement reaction behavior
|
12
|
+
#
|
3
13
|
class Agent
|
4
14
|
include Support::OperationRunner
|
15
|
+
include Support::Robustness
|
16
|
+
|
17
|
+
def initialize(engine)
|
18
|
+
@engine = engine
|
19
|
+
install_listeners
|
20
|
+
end
|
21
|
+
attr_reader :engine
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
# Installs the various event handlers by calling `sync`
|
26
|
+
# and `async` methods.
|
27
|
+
#
|
28
|
+
# This method is intended to be overriden.
|
29
|
+
def install_listeners
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the underlying bus
|
33
|
+
def bus
|
34
|
+
engine.bus
|
35
|
+
end
|
36
|
+
|
37
|
+
# Asynchronously listen to a specific event.
|
38
|
+
#
|
39
|
+
# See Bus#listen
|
40
|
+
def async(exchange, queue)
|
41
|
+
bus.async.listen(exchange, queue) do |event|
|
42
|
+
dup.call(event)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Synchronously listen to a specific event.
|
47
|
+
#
|
48
|
+
# See Bus#listen
|
49
|
+
def sync(exchange, queue)
|
50
|
+
bus.listen(exchange, queue) do |event|
|
51
|
+
dup.call(event)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reacts to a specific event.
|
56
|
+
#
|
57
|
+
# This method must be implemented by subclasses and raises
|
58
|
+
# an error by default.
|
59
|
+
def call(event = nil)
|
60
|
+
log(:fatal, {
|
61
|
+
op: self.class,
|
62
|
+
op_data: event,
|
63
|
+
error: %Q{Unexpected call to Startback::Event::Agent#call},
|
64
|
+
backtrace: caller
|
65
|
+
})
|
66
|
+
raise NotImplementedError
|
67
|
+
end
|
5
68
|
|
6
69
|
end # class Agent
|
7
70
|
end # class Event
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
module Startback
|
3
|
+
class Event
|
4
|
+
class Bus
|
5
|
+
module Bunny
|
6
|
+
#
|
7
|
+
# Asynchronous implementation of the bus abstraction, on top of RabbitMQ
|
8
|
+
# and using the 'bunny' gem (you need to include it in your Gemfile
|
9
|
+
# yourself: it is NOT a startback official dependency).
|
10
|
+
#
|
11
|
+
# This bus implementation emits events by dumping them to RabbitMQ using
|
12
|
+
# the event type as exchange name. Listeners may use the `processor`
|
13
|
+
# parameter to specify the queue name ; otherwise a default "main" queue
|
14
|
+
# is used.
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# # Connects to RabbitMQ using all default options
|
19
|
+
# #
|
20
|
+
# # Uses the STARTBACK_BUS_BUNNY_ASYNC_URL environment variable for
|
21
|
+
# # connection URL if present.
|
22
|
+
# Startback::Bus::Bunny::Async.new
|
23
|
+
#
|
24
|
+
# # Connects to RabbitMQ using a specific URL
|
25
|
+
# Startback::Bus::Bunny::Async.new("amqp://rabbituser:rabbitpass@192.168.17.17")
|
26
|
+
# Startback::Bus::Bunny::Async.new(url: "amqp://rabbituser:rabbitpass@192.168.17.17")
|
27
|
+
#
|
28
|
+
# # Connects to RabbitMQ using specific connection options. See Bunny's own
|
29
|
+
# # documentation
|
30
|
+
# Startback::Bus::Bunny::Async.new({
|
31
|
+
# connection_options: {
|
32
|
+
# host: "192.168.17.17"
|
33
|
+
# }
|
34
|
+
# })
|
35
|
+
#
|
36
|
+
class Async
|
37
|
+
include Support::Robustness
|
38
|
+
|
39
|
+
CHANNEL_KEY = 'Startback::Bus::Bunny::Async::ChannelKey'
|
40
|
+
|
41
|
+
DEFAULT_OPTIONS = {
|
42
|
+
# (optional) The URL to use for connecting to RabbitMQ.
|
43
|
+
url: ENV['STARTBACK_BUS_BUNNY_ASYNC_URL'],
|
44
|
+
|
45
|
+
# (optional) The options has to pass to ::Bunny constructor
|
46
|
+
connection_options: nil,
|
47
|
+
|
48
|
+
# (optional) The options to use for the emitter/listener fanout
|
49
|
+
fanout_options: {},
|
50
|
+
|
51
|
+
# (optional) The options to use for the listener queue
|
52
|
+
queue_options: {},
|
53
|
+
|
54
|
+
# (optional) Default event factory to use, if any
|
55
|
+
event_factory: nil,
|
56
|
+
|
57
|
+
# (optional) A default context to use for general logging
|
58
|
+
context: nil,
|
59
|
+
|
60
|
+
# (optional) Size of consumer pool
|
61
|
+
consumer_pool_size: 1,
|
62
|
+
|
63
|
+
# (optional) Whether the program must be aborted on consumption
|
64
|
+
# error
|
65
|
+
abort_on_exception: true,
|
66
|
+
|
67
|
+
# (optional) Whether connection occurs immediately,
|
68
|
+
# or on demand later
|
69
|
+
autoconnect: false
|
70
|
+
}
|
71
|
+
|
72
|
+
# Creates a bus instance, using the various options provided to
|
73
|
+
# fine-tune behavior.
|
74
|
+
def initialize(options = {})
|
75
|
+
options = { url: options } if options.is_a?(String)
|
76
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
77
|
+
connect if @options[:autoconnect]
|
78
|
+
end
|
79
|
+
attr_reader :options
|
80
|
+
|
81
|
+
def connect
|
82
|
+
disconnect
|
83
|
+
conn = options[:connection_options] || options[:url]
|
84
|
+
try_max_times(10) do
|
85
|
+
@bunny = ::Bunny.new(conn)
|
86
|
+
@bunny.start
|
87
|
+
channel # make sure we already create the channel
|
88
|
+
log(:info, {op: "#{self.class.name}#connect", op_data: conn}, options[:context])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def disconnect
|
93
|
+
if channel = Thread.current[CHANNEL_KEY]
|
94
|
+
channel.close
|
95
|
+
Thread.current[CHANNEL_KEY] = nil
|
96
|
+
end
|
97
|
+
@bunny.close if @bunny
|
98
|
+
end
|
99
|
+
|
100
|
+
def channel
|
101
|
+
unless @bunny
|
102
|
+
raise Startback::Errors::Error, "Please connect your bus first, or use autoconnect: true"
|
103
|
+
end
|
104
|
+
|
105
|
+
Thread.current[CHANNEL_KEY] ||= @bunny.create_channel(
|
106
|
+
nil,
|
107
|
+
consumer_pool_size, # consumer_pool_size
|
108
|
+
abort_on_exception? # consumer_pool_abort_on_exception
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def emit(event)
|
113
|
+
stop_errors(self, "emit", event.context) do
|
114
|
+
fanout = channel.fanout(event.type.to_s, fanout_options)
|
115
|
+
fanout.publish(event.to_json)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
120
|
+
raise ArgumentError, "A listener must be provided" unless listener || bl
|
121
|
+
|
122
|
+
fanout = channel.fanout(type.to_s, fanout_options)
|
123
|
+
queue = channel.queue((processor || "main").to_s, queue_options)
|
124
|
+
queue.bind(fanout)
|
125
|
+
queue.subscribe do |delivery_info, properties, body|
|
126
|
+
event = stop_errors(self, "listen") do
|
127
|
+
factor_event(body)
|
128
|
+
end
|
129
|
+
stop_errors(self, "listen", event.context) do
|
130
|
+
(listener || bl).call(event)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
protected
|
136
|
+
|
137
|
+
def consumer_pool_size
|
138
|
+
options[:consumer_pool_size]
|
139
|
+
end
|
140
|
+
|
141
|
+
def abort_on_exception?
|
142
|
+
options[:abort_on_exception]
|
143
|
+
end
|
144
|
+
|
145
|
+
def fanout_options
|
146
|
+
options[:fanout_options]
|
147
|
+
end
|
148
|
+
|
149
|
+
def queue_options
|
150
|
+
options[:queue_options]
|
151
|
+
end
|
152
|
+
|
153
|
+
def factor_event(body)
|
154
|
+
if options[:event_factory]
|
155
|
+
options[:event_factory].call(body)
|
156
|
+
else
|
157
|
+
Event.json(body, options)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end # class Async
|
162
|
+
end # module Bunny
|
163
|
+
end # class Bus
|
164
|
+
end # class Event
|
165
|
+
end # module Startback
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Startback
|
2
|
+
class Event
|
3
|
+
class Bus
|
4
|
+
module Memory
|
5
|
+
#
|
6
|
+
# Asynchronous implementation of the Bus abstraction, for use between
|
7
|
+
# components sharing the same process.
|
8
|
+
#
|
9
|
+
# This implementation actually calls listeners synchronously (it mays)
|
10
|
+
# but hides error raised by them. See Bus::Bunny::Async for another
|
11
|
+
# implementation that is truly asynchronous and relies on RabbitMQ.
|
12
|
+
#
|
13
|
+
class Async
|
14
|
+
include Support::Robustness
|
15
|
+
|
16
|
+
DEFAULT_OPTIONS = {
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
21
|
+
@listeners = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
end
|
26
|
+
|
27
|
+
def emit(event)
|
28
|
+
(@listeners[event.type.to_s] || []).each do |l|
|
29
|
+
stop_errors(self, "emit", event) {
|
30
|
+
l.call(event)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
36
|
+
raise ArgumentError, "A listener must be provided" unless listener || bl
|
37
|
+
@listeners[type.to_s] ||= []
|
38
|
+
@listeners[type.to_s] << (listener || bl)
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class Sync
|
42
|
+
end # module Memory
|
43
|
+
end # class Bus
|
44
|
+
end # class Event
|
45
|
+
end # module Startback
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Startback
|
2
|
+
class Event
|
3
|
+
class Bus
|
4
|
+
module Memory
|
5
|
+
#
|
6
|
+
# Synchronous implementation of the Bus abstraction, for use between
|
7
|
+
# components sharing the same process.
|
8
|
+
#
|
9
|
+
class Sync
|
10
|
+
include Support::Robustness
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@listeners = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect
|
17
|
+
end
|
18
|
+
|
19
|
+
def emit(event)
|
20
|
+
(@listeners[event.type.to_s] || []).each do |l|
|
21
|
+
l.call(event)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
26
|
+
raise ArgumentError, "A listener must be provided" unless listener || bl
|
27
|
+
@listeners[type.to_s] ||= []
|
28
|
+
@listeners[type.to_s] << (listener || bl)
|
29
|
+
end
|
30
|
+
|
31
|
+
end # class Sync
|
32
|
+
end # module Memory
|
33
|
+
end # class Bus
|
34
|
+
end # class Event
|
35
|
+
end # module Startback
|
File without changes
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Startback
|
2
|
+
class Event
|
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
|
+
def connect
|
68
|
+
sync.connect if sync
|
69
|
+
async.connect if async
|
70
|
+
end
|
71
|
+
|
72
|
+
# Emits a particular event to the listeners.
|
73
|
+
#
|
74
|
+
# @arg event an event, should be an Event instance (through duck
|
75
|
+
# typing is allowed)
|
76
|
+
def emit(event)
|
77
|
+
monitor({
|
78
|
+
op: "Startback::Bus#emit",
|
79
|
+
op_data: {
|
80
|
+
event: { type: event.type }
|
81
|
+
}
|
82
|
+
}, event.context) do
|
83
|
+
sync.emit(event)
|
84
|
+
async.emit(event) if async
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Registers `listener` as being interested in receiving events of
|
89
|
+
# a specific type.
|
90
|
+
#
|
91
|
+
# @arg type: Symbol, the type of event the listener is interested in.
|
92
|
+
# @arg listener: Proc, the listener itself.
|
93
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
94
|
+
sync.listen(type, processor, listener, &bl)
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class Bus
|
98
|
+
end # class Event
|
99
|
+
end # module Startback
|
100
|
+
require_relative 'bus/memory'
|
@@ -4,10 +4,10 @@ require 'startback'
|
|
4
4
|
module Startback
|
5
5
|
class Event
|
6
6
|
#
|
7
|
-
# This class
|
8
|
-
# It
|
9
|
-
#
|
10
|
-
#
|
7
|
+
# This class is the starting point of event handling in
|
8
|
+
# Startback. It holds a Bus instance to which emitters
|
9
|
+
# and listeners can connect, and the possibility for the
|
10
|
+
# the listening part to start an infinite loop (ServerEngine).
|
11
11
|
#
|
12
12
|
# The Engine automatically runs a Webrick small webapp
|
13
13
|
# with a /healthcheck webservice. The class can be extended
|
@@ -15,86 +15,157 @@ module Startback
|
|
15
15
|
# checks.
|
16
16
|
#
|
17
17
|
# This class goes hand in hand with the `startback:engine`
|
18
|
-
# docker image.
|
18
|
+
# docker image. It can be extended by subclasses to override
|
19
|
+
# the following methods:
|
19
20
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# # engine.rb
|
26
|
-
# require 'startback/event/engine'
|
27
|
-
# Startback::Event::Engine.run
|
21
|
+
# - bus to use something else than a simple memory bus
|
22
|
+
# - on_health_check to check specific health conditions
|
23
|
+
# - create_agents to instantiate all listening agents
|
24
|
+
# (unless auto_create_agents is used)
|
28
25
|
#
|
29
26
|
class Engine
|
27
|
+
include Support::Robustness
|
30
28
|
|
31
29
|
DEFAULT_OPTIONS = {
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
|
31
|
+
# To be passed to ServerEngine
|
32
|
+
server_engine: {}
|
33
|
+
|
35
34
|
}
|
36
35
|
|
37
|
-
def initialize
|
38
|
-
|
36
|
+
def initialize(options = {}, context = Context.new)
|
37
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
38
|
+
@context = context
|
39
|
+
end
|
40
|
+
attr_reader :options, :context
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def auto_create_agents?
|
44
|
+
!!@auto_create_agents
|
45
|
+
end
|
46
|
+
|
47
|
+
# Register a base class which will be used to discover
|
48
|
+
# the agents to start when the engine is ran.
|
49
|
+
def auto_create_agents(base_class = nil)
|
50
|
+
@auto_create_agents ||= base_class
|
51
|
+
@auto_create_agents
|
52
|
+
end
|
39
53
|
end
|
40
54
|
|
55
|
+
# This method is executed on health check and can be
|
56
|
+
# overriden by subclasses to perform specific checks.
|
41
57
|
def on_health_check
|
42
58
|
"Ok"
|
43
59
|
end
|
44
60
|
|
61
|
+
def bus
|
62
|
+
::Startback::Event::Bus.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def connect
|
66
|
+
log(:info, self, "Connecting to the bus now!")
|
67
|
+
bus.connect
|
68
|
+
end
|
69
|
+
|
45
70
|
def run(options = {})
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
se
|
71
|
+
connect
|
72
|
+
|
73
|
+
log(:info, self, "Running agents and server engine!")
|
74
|
+
create_agents
|
75
|
+
Runner.new(self, options[:server_engine] || {}).run
|
52
76
|
end
|
53
77
|
|
54
|
-
|
55
|
-
|
56
|
-
|
78
|
+
def create_agents
|
79
|
+
return unless parent = self.class.auto_create_agents
|
80
|
+
|
81
|
+
ObjectSpace
|
82
|
+
.each_object(Class)
|
83
|
+
.select { |klass| klass < parent }
|
84
|
+
.each { |klass| klass.new(self) }
|
85
|
+
end
|
86
|
+
|
87
|
+
class Runner
|
88
|
+
|
89
|
+
DEFAULT_SERVER_ENGINE_OPTIONS = {
|
90
|
+
daemonize: false,
|
91
|
+
worker_type: 'process',
|
92
|
+
workers: 1
|
93
|
+
}
|
94
|
+
|
95
|
+
def initialize(engine, options = {})
|
96
|
+
raise ArgumentError if engine.nil?
|
97
|
+
|
98
|
+
@engine = engine
|
99
|
+
@options = DEFAULT_SERVER_ENGINE_OPTIONS.merge(options)
|
100
|
+
require 'serverengine'
|
57
101
|
end
|
102
|
+
attr_reader :engine, :options
|
58
103
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
run(health)
|
66
|
-
end
|
67
|
-
end
|
104
|
+
def run(options = {})
|
105
|
+
health = self.class.build_health_check(engine)
|
106
|
+
worker = self.class.build_worker(engine, health)
|
107
|
+
se = ServerEngine.create(nil, worker, options)
|
108
|
+
se.run
|
109
|
+
se
|
68
110
|
end
|
69
111
|
|
70
|
-
|
71
|
-
|
72
|
-
|
112
|
+
class << self
|
113
|
+
def run(*args, &bl)
|
114
|
+
new.run(*args, &bl)
|
115
|
+
end
|
73
116
|
|
74
|
-
|
75
|
-
|
117
|
+
def build_health_check(engine)
|
118
|
+
Rack::Builder.new do
|
119
|
+
map '/health-check' do
|
120
|
+
health = Startback::Web::HealthCheck.new {
|
121
|
+
engine.on_health_check
|
122
|
+
}
|
123
|
+
run(health)
|
124
|
+
end
|
76
125
|
end
|
126
|
+
end
|
77
127
|
|
78
|
-
|
79
|
-
|
80
|
-
|
128
|
+
def build_worker(engine, health)
|
129
|
+
Module.new do
|
130
|
+
include Support::Env
|
81
131
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
132
|
+
def initialize
|
133
|
+
@stop_flag = ServerEngine::BlockingFlag.new
|
134
|
+
end
|
135
|
+
|
136
|
+
define_method(:health) do
|
137
|
+
health
|
138
|
+
end
|
139
|
+
|
140
|
+
define_method(:engine) do
|
141
|
+
engine
|
88
142
|
end
|
89
|
-
end
|
90
143
|
|
91
|
-
|
92
|
-
|
93
|
-
|
144
|
+
def run
|
145
|
+
ran = false
|
146
|
+
until @stop_flag.set?
|
147
|
+
if ran
|
148
|
+
engine.send(:log, :warn, engine, "Restarting internal loop")
|
149
|
+
else
|
150
|
+
engine.send(:log, :info, engine, "Starting internal loop")
|
151
|
+
end
|
152
|
+
Rack::Handler::WEBrick.run(health, {
|
153
|
+
:Port => env('STARTBACK_ENGINE_PORT', '3000').to_i,
|
154
|
+
:Host => env('STARTBACK_ENGINE_LISTEN', '0.0.0.0')
|
155
|
+
})
|
156
|
+
ran = true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def stop
|
161
|
+
engine.send(:log, :info, engine, "Stopping internal loop")
|
162
|
+
@stop_flag.set!
|
163
|
+
Rack::Handler::WEBrick.shutdown
|
164
|
+
end
|
94
165
|
end
|
95
166
|
end
|
96
|
-
end
|
97
|
-
end # class
|
167
|
+
end # class << self
|
168
|
+
end # class Runner
|
98
169
|
end # class Engine
|
99
170
|
end # class Event
|
100
171
|
end # module Startback
|
data/lib/startback/event.rb
CHANGED
data/lib/startback/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -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
|
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.
|
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-
|
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
|
@@ -399,6 +393,12 @@ files:
|
|
399
393
|
- lib/startback/errors.rb
|
400
394
|
- lib/startback/event.rb
|
401
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
402
|
- lib/startback/event/engine.rb
|
403
403
|
- lib/startback/ext.rb
|
404
404
|
- lib/startback/ext/date_time.rb
|
@@ -431,13 +431,13 @@ files:
|
|
431
431
|
- spec/spec_helper.rb
|
432
432
|
- spec/unit/audit/test_prometheus.rb
|
433
433
|
- spec/unit/audit/test_trailer.rb
|
434
|
-
- spec/unit/bus/memory/test_async.rb
|
435
|
-
- spec/unit/bus/memory/test_sync.rb
|
436
434
|
- spec/unit/caching/test_entity_cache.rb
|
437
435
|
- spec/unit/context/test_abstraction_factory.rb
|
438
436
|
- spec/unit/context/test_dup.rb
|
439
437
|
- spec/unit/context/test_h_factory.rb
|
440
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
|
441
441
|
- spec/unit/support/hooks/test_after_hook.rb
|
442
442
|
- spec/unit/support/hooks/test_before_hook.rb
|
443
443
|
- spec/unit/support/operation_runner/test_around_run.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
|