omnes 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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__)
|