omnes 0.1.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 +7 -0
- data/.github/workflows/lint.yml +24 -0
- data/.github/workflows/test.yml +26 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.travis.yml +6 -0
- data/.yardopts +9 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +92 -0
- data/LICENSE.txt +21 -0
- data/README.md +617 -0
- data/Rakefile +8 -0
- data/bin/console +16 -0
- data/bin/setup +8 -0
- data/lib/omnes/bus.rb +274 -0
- data/lib/omnes/errors.rb +101 -0
- data/lib/omnes/event.rb +72 -0
- data/lib/omnes/execution.rb +42 -0
- data/lib/omnes/publication.rb +43 -0
- data/lib/omnes/registry.rb +102 -0
- data/lib/omnes/subscriber/adapter/active_job.rb +74 -0
- data/lib/omnes/subscriber/adapter/method/errors.rb +51 -0
- data/lib/omnes/subscriber/adapter/method.rb +45 -0
- data/lib/omnes/subscriber/adapter/sidekiq.rb +92 -0
- data/lib/omnes/subscriber/adapter.rb +33 -0
- data/lib/omnes/subscriber/errors.rb +24 -0
- data/lib/omnes/subscriber/state.rb +68 -0
- data/lib/omnes/subscriber.rb +229 -0
- data/lib/omnes/subscription.rb +58 -0
- data/lib/omnes/unstructured_event.rb +42 -0
- data/lib/omnes/version.rb +5 -0
- data/lib/omnes.rb +83 -0
- data/omnes.gemspec +45 -0
- metadata +157 -0
data/README.md
ADDED
@@ -0,0 +1,617 @@
|
|
1
|
+
# Omnes
|
2
|
+
|
3
|
+
Pub/sub for Ruby.
|
4
|
+
|
5
|
+
Omnes is a Ruby library implementing the publish-subscribe pattern. This
|
6
|
+
pattern allows senders of messages to be decoupled from their receivers. An
|
7
|
+
Event Bus acts as a middleman where events are published while interested
|
8
|
+
parties can subscribe to them.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
`bundle add omnes`
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
There're two ways to make use of the pub/sub features Omnes provides:
|
17
|
+
|
18
|
+
- Standalone, through an [`Omnes::Bus`](lib/omnes/bus.rb) instance:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
require "omnes"
|
22
|
+
|
23
|
+
bus = Omnes::Bus.new
|
24
|
+
```
|
25
|
+
|
26
|
+
- Mixing in the behavior in another class by including the [`Omnes`](lib/omnes.rb) module.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require "omnes"
|
30
|
+
|
31
|
+
class Notifier
|
32
|
+
include Omnes
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
The following examples will use the direct `Omnes::Bus` instance. The only
|
37
|
+
difference for the mixing use case is that the methods are directly called in
|
38
|
+
the including instance.
|
39
|
+
|
40
|
+
### Registering events
|
41
|
+
|
42
|
+
Before being able to work with a given event, its name (which must be a
|
43
|
+
`Symbol`) must be registered:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
bus.register(:order_created)
|
47
|
+
```
|
48
|
+
|
49
|
+
### Publishing events
|
50
|
+
|
51
|
+
An event can be anything responding to a method `:name`, which must match with a
|
52
|
+
registered name.
|
53
|
+
|
54
|
+
Typically, there're two main ways to generate events.
|
55
|
+
|
56
|
+
1. Unstructured events
|
57
|
+
|
58
|
+
An event can be generated at publication time, where you provide its name and a
|
59
|
+
payload to be consumed by its subscribers:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
bus.publish(:order_created, number: order.number, user_email: user.email)
|
63
|
+
```
|
64
|
+
|
65
|
+
In that case, an instance of [`Omnes::UnstructuredEvent`](lib/omnes/unstructured_event.rb) is generated
|
66
|
+
under the hood.
|
67
|
+
|
68
|
+
Unstructured events are straightforward to create and use, but they're harder
|
69
|
+
to debug as they're defined at publication time. On top of that, other
|
70
|
+
features, such as event persistence, can't be reliably built on top of them.
|
71
|
+
|
72
|
+
2. Instance-backed events
|
73
|
+
|
74
|
+
You can also publish an instance of a class including
|
75
|
+
[`Omnes::Event`](lib/omnes/event.rb). The only fancy thing it provides is an
|
76
|
+
OOTB event name generated based on the class name. In fact, you can use
|
77
|
+
anything responding to `#omnes_event_name`.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class OrderCreatedEvent
|
81
|
+
include Omnes::Event
|
82
|
+
|
83
|
+
attr_reader :number, :user_email
|
84
|
+
|
85
|
+
def initialize(number:, user_email:)
|
86
|
+
@number = number
|
87
|
+
@user_email = user_email
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
event = OrderCreatedEvent.new(number: order.number, user_email: user.email)
|
92
|
+
bus.publish(event)
|
93
|
+
```
|
94
|
+
|
95
|
+
By default, an event name instance equals the event class name downcased,
|
96
|
+
underscored and with the `Event` suffix removed if present (`:order_created` in
|
97
|
+
the previous example). However, you can configure your own name generator based
|
98
|
+
on the event instance:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
event_name_as_class = ->(event) { event.class.name.to_sym } # :OrderCreatedEvent in the example
|
102
|
+
Omnes.config.event.name_builder = event_name_as_class
|
103
|
+
```
|
104
|
+
|
105
|
+
Instance-backed events provide a well-defined structure, and other features,
|
106
|
+
like event persistence, can be added on top of them.
|
107
|
+
|
108
|
+
### Subscribing to events
|
109
|
+
|
110
|
+
You can subscribe to a specific event to run some code whenever it's published.
|
111
|
+
The event is yielded to the subscription block:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
bus.subscribe(:order_created) do |event|
|
115
|
+
# ...
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
For unstructured events, the published data is made available through the
|
120
|
+
`payload` method, although `#[]` can be used as a shortcut:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
bus.subscribe(:order_created) do |event|
|
124
|
+
OrderCreationEmail.new.send(number: event[:number], email: event[:user_email])
|
125
|
+
# OrderCreationEmail.new.send(number: event.payload[:number], email: event.payload[:user_email])
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
Otherwise, use the event instance according to its structure:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
bus.subscribe(:order_created) do |event|
|
133
|
+
OrderCreationEmail.new.send(number: event.number, email: event.user_email)
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
The subscription code can also be given as anything responding to a method
|
138
|
+
`#call`.
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
class OrderCreationEmailSubscription
|
142
|
+
def call(event)
|
143
|
+
OrderCreationEmail.new.send(number: event.number, email: event.user_email)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
bus.subscribe(:order_created, OrderCreationEmailSubscription.new)
|
148
|
+
```
|
149
|
+
|
150
|
+
However, see [Event subscribers](#event-subscribers) section bellow for a more powerful way
|
151
|
+
to define standalone event handlers.
|
152
|
+
|
153
|
+
#### Global subscriptions
|
154
|
+
|
155
|
+
You can also create a subscription that will run for all events:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
class LogEventsSubscription
|
159
|
+
attr_reader :logger
|
160
|
+
|
161
|
+
def initialize(logger: Logger.new(STDOUT))
|
162
|
+
@logger = logger
|
163
|
+
end
|
164
|
+
|
165
|
+
def call(event)
|
166
|
+
logger.info("Event #{event.name} published")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
bus.subscribe_to_all(LogEventsSubscription.new)
|
171
|
+
```
|
172
|
+
|
173
|
+
#### Custom matcher subscriptions
|
174
|
+
|
175
|
+
Custom event matchers can be defined. A matcher is something responding to
|
176
|
+
`#call` and taking the event as an argument. It must return `true` or `false`
|
177
|
+
to match or ignore the event.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
ORDER_EVENTS_MATCHER = ->(event) { event.name.start_with?(:order) }
|
181
|
+
|
182
|
+
bus.subscribe_with_matcher(ORDER_EVENTS_MATCHER) do |event|
|
183
|
+
# ...
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
## Event subscribers
|
188
|
+
|
189
|
+
Events subscribers offer a way to define event subscriptions from a custom
|
190
|
+
class.
|
191
|
+
|
192
|
+
In its simplest form, you can match an event to a method in the class.
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class OrderCreationEmailSubscriber
|
196
|
+
include Omnes::Subscriber
|
197
|
+
|
198
|
+
handle :order_created, with: :send_confirmation_email
|
199
|
+
|
200
|
+
attr_reader :service
|
201
|
+
|
202
|
+
def initialize(service: OrderCreationEmail.new)
|
203
|
+
@service = service
|
204
|
+
end
|
205
|
+
|
206
|
+
def send_confirmation_email(event)
|
207
|
+
service.send(number: event.number, email: event.user_email)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
OrderCreationEmailSubscriber.new.subscribe_to(bus)
|
212
|
+
```
|
213
|
+
|
214
|
+
Equivalent to the subscribe methods we've seen above, you can also subscribe to
|
215
|
+
all events:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
class LogEventsSubscriber
|
219
|
+
include Omnes::Subscriber
|
220
|
+
|
221
|
+
handle_all with: :log_event
|
222
|
+
|
223
|
+
attr_reader :logger
|
224
|
+
|
225
|
+
def initialize(logger: Logger.new(STDOUT))
|
226
|
+
@logger = logger
|
227
|
+
end
|
228
|
+
|
229
|
+
def log_event(event)
|
230
|
+
logger.info("Event #{event.name} published")
|
231
|
+
end
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
You can also handle the event with your own custom matcher:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
class OrderSubscriber
|
239
|
+
include Omnes::Subscriber
|
240
|
+
|
241
|
+
handle_with_matcher ORDER_EVENTS_MATCHER, with: :register_order_event
|
242
|
+
|
243
|
+
def register_order_event(event)
|
244
|
+
# ...
|
245
|
+
end
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
### Autodiscovering event handlers
|
250
|
+
|
251
|
+
You can let the event handlers to be automatically discovered.You need to
|
252
|
+
enable the `autodiscover` feature and prefix the event name with `on_` for your
|
253
|
+
handler name.
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
class OrderCreationEmailSubscriber
|
257
|
+
include Omnes::Subscriber[
|
258
|
+
autodiscover: true
|
259
|
+
]
|
260
|
+
|
261
|
+
# ...
|
262
|
+
|
263
|
+
def on_order_created(event)
|
264
|
+
# ...
|
265
|
+
end
|
266
|
+
end
|
267
|
+
```
|
268
|
+
|
269
|
+
If you prefer, you can make `autodiscover` on by default:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
Omnes.config.subscriber.autodiscover = true
|
273
|
+
```
|
274
|
+
|
275
|
+
You can also specify your own autodiscover strategy. It must be something
|
276
|
+
callable, transforming the event name into the handler name.
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
AUTODISCOVER_STRATEGY = ->(event_name) { event_name }
|
280
|
+
|
281
|
+
class OrderCreationEmailSubscriber
|
282
|
+
include Omnes::Subscriber[
|
283
|
+
autodiscover: true,
|
284
|
+
autodiscover_strategy: AUTODISCOVER_STRATEGY
|
285
|
+
]
|
286
|
+
|
287
|
+
# ...
|
288
|
+
|
289
|
+
def order_created(event)
|
290
|
+
# ...
|
291
|
+
end
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
### Adapters
|
296
|
+
|
297
|
+
Subscribers are not limited to use a method as event handler. They can interact
|
298
|
+
with the whole instance context and leverage it to build adapters.
|
299
|
+
|
300
|
+
Omnes ships with a few of them.
|
301
|
+
|
302
|
+
#### Sidekiq adapter
|
303
|
+
|
304
|
+
The Sidekiq adapter allows creating a subscription to be processed as a
|
305
|
+
[Sidekiq](https://sidekiq.org) background job.
|
306
|
+
|
307
|
+
Sidekiq requires that the argument passed to `#perform` is serializable. By
|
308
|
+
default, the result of calling `#payload` in the event is taken.
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
class OrderCreationEmailSubscriber
|
312
|
+
include Omnes::Subscriber
|
313
|
+
include Sidekiq::Job
|
314
|
+
|
315
|
+
handle :order_created, with: Adapter::Sidekiq
|
316
|
+
|
317
|
+
def perform(payload)
|
318
|
+
OrderCreationEmail.send(number: payload["number"], email: payload["user_email"])
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
bus = Omnes::Bus.new
|
323
|
+
bus.register(:order_created)
|
324
|
+
OrderCreationEmailSubscriber.new.subscribe_to(bus)
|
325
|
+
bus.publish(:order_created, "number" => order.number, "user_email" => user.email)
|
326
|
+
```
|
327
|
+
|
328
|
+
However, you can configure how the event is serialized thanks to the
|
329
|
+
`serializer:` option. It needs to be something callable taking the event as
|
330
|
+
argument:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
handle :order_created, with: Adapter::Sidekiq[serializer: :serialized_payload.to_proc]
|
334
|
+
```
|
335
|
+
|
336
|
+
You can also globally configure the default serializer:
|
337
|
+
|
338
|
+
```ruby
|
339
|
+
Omnes.config.subscriber.adapter.sidekiq.serializer = :serialized_payload.to_proc
|
340
|
+
```
|
341
|
+
|
342
|
+
You can delay the callback execution from the publication time with the `.in`
|
343
|
+
method (analogous to `Sidekiq::Job.perform_in`):
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
handle :order_created, with: Adapter::Sidekiq.in(60)
|
347
|
+
```
|
348
|
+
|
349
|
+
#### ActiveJob adapter
|
350
|
+
|
351
|
+
The ActiveJob adapter allows creating a subscription to be processed as an
|
352
|
+
[ActiveJob](https://edgeguides.rubyonrails.org/active_job_basics.html)
|
353
|
+
background job.
|
354
|
+
|
355
|
+
ActiveJob requires that the argument passed to `#perform` is serializable. By
|
356
|
+
default, the result of calling `#payload` in the event is taken.
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
class OrderCreationEmailSubscriber < ActiveJob
|
360
|
+
include Omnes::Subscriber
|
361
|
+
|
362
|
+
handle :order_created, with: Adapter::ActiveJob
|
363
|
+
|
364
|
+
def perform(payload)
|
365
|
+
OrderCreationEmail.send(number: payload["number"], email: payload["user_email"])
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
bus = Omnes::Bus.new
|
370
|
+
bus.register(:order_created)
|
371
|
+
OrderCreationEmailSubscriber.new.subscribe_to(bus)
|
372
|
+
bus.publish(:order_created, "number" => order.number, "user_email" => user.email)
|
373
|
+
```
|
374
|
+
|
375
|
+
However, you can configure how the event is serialized thanks to the
|
376
|
+
`serializer:` option. It needs to be something callable taking the event as
|
377
|
+
argument:
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
handle :order_created, with: Adapter::ActiveJob[serializer: :serialized_payload.to_proc]
|
381
|
+
```
|
382
|
+
|
383
|
+
You can also globally configure the default serializer:
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
Omnes.config.subscriber.adapter.active_job.serializer = :serialized_payload.to_proc
|
387
|
+
```
|
388
|
+
|
389
|
+
#### Custom adapters
|
390
|
+
|
391
|
+
Custom adapters can be built. They need to implement a method `#call` taking
|
392
|
+
the instance of `Omnes::Subscriber` and the event.
|
393
|
+
|
394
|
+
Here's a custom adapter executing a subscriber method in a different
|
395
|
+
thread (we add an extra argument for the method name, and we partially apply it
|
396
|
+
at the definition time to obey the adapter requirements).
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
THREAD_ADAPTER = lambda do |method_name, instance, event|
|
400
|
+
Thread.new { instance.method(method_name).call(event) }
|
401
|
+
end
|
402
|
+
|
403
|
+
class OrderCreationEmailSubscriber
|
404
|
+
include Omnes::Subscriber
|
405
|
+
include Sidekiq::Job
|
406
|
+
|
407
|
+
handle :order_created, with: THREAD_ADAPTER.curry[:order_created]
|
408
|
+
|
409
|
+
def order_created(event)
|
410
|
+
# ...
|
411
|
+
end
|
412
|
+
end
|
413
|
+
```
|
414
|
+
|
415
|
+
Alternatively, adapters can be curried and only take the instance as an
|
416
|
+
argument, returning a one-argument callable taking the event. For instance, we
|
417
|
+
could also have defined the thread adapter like this:
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
class ThreadAdapter
|
421
|
+
attr_reader :method_name
|
422
|
+
|
423
|
+
def initialize(method_name)
|
424
|
+
@method_name = method_name
|
425
|
+
end
|
426
|
+
|
427
|
+
def call(instance)
|
428
|
+
raise unless instance.respond_to?(method_name)
|
429
|
+
|
430
|
+
->(event) { instance.method(:call).(event) }
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
# ...
|
435
|
+
handle :order_created, with: ThreadAdapter.new(:order_created)
|
436
|
+
# ...
|
437
|
+
```
|
438
|
+
|
439
|
+
## Debugging
|
440
|
+
|
441
|
+
### Unsubscribing
|
442
|
+
|
443
|
+
When you create a subscription, an instance of
|
444
|
+
[`Omnes::Subscription`](lib/omnes/subscription.rb) is returned (or an array for
|
445
|
+
`Omnes::Subscriber`). It can be used to unsubscribe it in case you need to
|
446
|
+
debug some behavior.
|
447
|
+
|
448
|
+
```ruby
|
449
|
+
subscription = bus.subscribe(:order_created, OrderCreationEmailSubscription.new)
|
450
|
+
bus.unsubscribe(subscription)
|
451
|
+
```
|
452
|
+
|
453
|
+
### Registration
|
454
|
+
|
455
|
+
Whenever you register an event, you get back an [`Omnes::Registry::Registration`](lib/omnes/registry.rb)
|
456
|
+
instance. It gives access to both the registered `#event_name` and the
|
457
|
+
`#caller_location` of the registration.
|
458
|
+
|
459
|
+
An `Omnes::Bus` contains a reference to its registry, which can be used to
|
460
|
+
retrieve a registration later on.
|
461
|
+
|
462
|
+
```ruby
|
463
|
+
bus.registry.registration(:order_created)
|
464
|
+
```
|
465
|
+
|
466
|
+
You can also use the registry to retrieve all registered event names:
|
467
|
+
|
468
|
+
```ruby
|
469
|
+
bus.registry.event_names
|
470
|
+
```
|
471
|
+
|
472
|
+
See [`Omnes::Registry`](lib/omnes/registry.rb) for other available methods.
|
473
|
+
|
474
|
+
### Publication
|
475
|
+
|
476
|
+
When you publish an event, you get back an
|
477
|
+
[`Omnes::Publication`](lib/omnes/publication.rb) instance. It contains some
|
478
|
+
attributes that allow observing what happened:
|
479
|
+
|
480
|
+
- `#event` contains the event instance that has been published.
|
481
|
+
- `#caller_location` refers to the publication caller.
|
482
|
+
- `#time` is the time stamp for the publication.
|
483
|
+
- `#executions` contains an array of
|
484
|
+
`Omnes::Execution`(lib/omnes/execution.rb). Read more below.
|
485
|
+
|
486
|
+
`Omnes::Execution` represents a subscription individual execution. It contains
|
487
|
+
the following attributes:
|
488
|
+
|
489
|
+
- `#subscription` is an instance of [`Omnes::Subscription`](lib/omnes/subscripiton.rb).
|
490
|
+
- `#result` contains the result of the execution.
|
491
|
+
- `#benchmark` of the operation.
|
492
|
+
- `#time` is the time where the execution started.
|
493
|
+
|
494
|
+
## Testing
|
495
|
+
|
496
|
+
Ideally, you wouldn't need big setups to test your event-driven behavior. You
|
497
|
+
could design your subscribers to use lightweight mocks for any external or
|
498
|
+
operation at the integration level. Example:
|
499
|
+
|
500
|
+
```ruby
|
501
|
+
if # test environment
|
502
|
+
bus.subscribe(:order_created, OrderCreationEmailSubscriber.new(service: MockService.new)
|
503
|
+
else
|
504
|
+
bus.subscribe(:order_created, OrderCreationEmailSubscriber.new)
|
505
|
+
end
|
506
|
+
```
|
507
|
+
|
508
|
+
Then, at the unit level, you can test your subscribers as any other class.
|
509
|
+
|
510
|
+
However, there's also a handy `Omnes::Bus#performing_only` method that allows
|
511
|
+
running a code block with only a selection of subscriptions as potential
|
512
|
+
callbacks for published events.
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
creation_subscription = bus.subscribe(:order_created, OrderCreationEmailSubscriber.new)
|
516
|
+
deletion_subscription = bus.subscribe(:order_deleted, OrderDeletionSubscriber.new)
|
517
|
+
bus.performing_only(creation_subscription) do
|
518
|
+
bus.publish(:order_created, number: order.number, user_email: user.email) # `creation_subscription` will run
|
519
|
+
bus.publish(:order_deleted, number: order.number) # `deletion_subscription` won't run
|
520
|
+
end
|
521
|
+
bus.publish(:order_deleted, number: order.number) # `deletion_subscription` will run
|
522
|
+
```
|
523
|
+
|
524
|
+
Remember that the array of created subscriptions is returned on `Omnes::Subscriber#subscribe_to`.
|
525
|
+
|
526
|
+
There's also a specialized `Omnes::Bus#performing_nothing` method that runs no
|
527
|
+
subscriptions for the duration of the block.
|
528
|
+
|
529
|
+
## Recipes
|
530
|
+
|
531
|
+
### Rails
|
532
|
+
|
533
|
+
Create an initializer in `config/initializers/omnes.rb`:
|
534
|
+
|
535
|
+
```ruby
|
536
|
+
require "omnes"
|
537
|
+
|
538
|
+
Omnes.config.subscriber.autodiscover = true
|
539
|
+
|
540
|
+
Bus = Omnes::Bus.new
|
541
|
+
Bus.register(:order_created)
|
542
|
+
|
543
|
+
OrderCreationEmailSubscriber.new.subscribe_to(Bus)
|
544
|
+
```
|
545
|
+
|
546
|
+
We can define `OrderCreationEmailSubscriber` in
|
547
|
+
`app/subscribers/order_creation_email_subscriber.rb`:
|
548
|
+
|
549
|
+
```ruby
|
550
|
+
# frozen_string_literal: true
|
551
|
+
|
552
|
+
class OrderCreationEmailSubscriber
|
553
|
+
include Omnes::Subscriber
|
554
|
+
|
555
|
+
def on_order_created(event)
|
556
|
+
# ...
|
557
|
+
end
|
558
|
+
end
|
559
|
+
```
|
560
|
+
|
561
|
+
Ideally, you'll publish your event in a [custom service
|
562
|
+
layer](https://www.toptal.com/ruby-on-rails/rails-service-objects-tutorial). If
|
563
|
+
that's not possible, you can publish it in the controller.
|
564
|
+
|
565
|
+
We strongly discourage publishing events as part of an `ActiveRecord` callback.
|
566
|
+
Subscribers should run code that is independent of the main business
|
567
|
+
transaction. As such, they shouldn't run within the same database transaction,
|
568
|
+
and they should be decoupled of persistence responsibilities altogether.
|
569
|
+
|
570
|
+
## Why is it called Omnes?
|
571
|
+
|
572
|
+
Why an Event Bus is called an _Event Bus_? It's a long story:
|
573
|
+
|
574
|
+
- The first leap leaves us with the hardware computer buses. They move data from one hardware component to another.
|
575
|
+
- The name leaked to the software to describe architectures that communicate parts by sending messages, like an Event Bus.
|
576
|
+
- That was given as an analogy of buses as vehicles, where not data but people are transported.
|
577
|
+
- _Bus_ is a clipped version of the Latin _omnibus_. That's what buses used to be called (and they're still called like that in some places, like Argentina).
|
578
|
+
- _Bus_ stands for the preposition _for_, while _Omni_ means _all_. That's _for
|
579
|
+
all_, but, for some reason, we decided to keep the part void of meaning.
|
580
|
+
- Why were they called _omnibus_? Let's move back to 1823 and talk about a man named Stanislas Baudry.
|
581
|
+
- Stanislas lived in a suburb of Nantes, France. There, he ran a corn mill.
|
582
|
+
- Hot water was a by-product of the mill, so Stanislas decided to build a spa business.
|
583
|
+
- As the mill was on the city's outskirts, he arranged some horse-drawn
|
584
|
+
transportation to bring people to his spa.
|
585
|
+
- It turned out that people weren't interested in it, but they did use the carriage to go to and fro.
|
586
|
+
- The first stop of the service was in front of the shop of a hatter called
|
587
|
+
__Omnes__.
|
588
|
+
- Omnes was a witty man. He'd named his shop with a pun on his Latin-sounding
|
589
|
+
name: _Omnes Omnibus_. That means something like _everything for everyone_.
|
590
|
+
- Therefore, people in Nantes started to call _Omnibus_ to the new service.
|
591
|
+
|
592
|
+
So, it turns out we call it the "Event Bus" because presumably, the parents of
|
593
|
+
Omnes gave him that name. So, the name of this library, it's a tribute to
|
594
|
+
Omnes, the hatter.
|
595
|
+
|
596
|
+
By the way, in case you're wondering, Stanislas, the guy of the mill, closed
|
597
|
+
both it and the spa to run his service.
|
598
|
+
Eventually, he moved to Paris to earn more money in a bigger city.
|
599
|
+
|
600
|
+
## Development
|
601
|
+
|
602
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
603
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
604
|
+
prompt that will allow you to experiment.
|
605
|
+
|
606
|
+
## Contributing
|
607
|
+
|
608
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nebulab/omnes. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nebulab/omnes/blob/master/CODE_OF_CONDUCT.md).
|
609
|
+
|
610
|
+
|
611
|
+
## License
|
612
|
+
|
613
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
614
|
+
|
615
|
+
## Code of Conduct
|
616
|
+
|
617
|
+
Everyone interacting in the Omnes project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nebulab/omnes/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "bundler/setup"
|
6
|
+
require "omnes"
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
+
# require "pry"
|
13
|
+
# Pry.start
|
14
|
+
|
15
|
+
require "irb"
|
16
|
+
IRB.start(__FILE__)
|