pubsub_on_rails 0.0.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e3e25ad0c40aca45ee464ada4fd7f99c58b9d85c
4
- data.tar.gz: f120f19a9df7626cfe7fe8322bca917644c0763c
2
+ SHA256:
3
+ metadata.gz: 610b5e8d1415af6aa00766ef49444e4a3889aa6e1d79c0da2a74a58a09726d0c
4
+ data.tar.gz: 57ac3717989db60def406d8f6a85aa4139bdffef3ee3d05b5d7dcae70823f6dc
5
5
  SHA512:
6
- metadata.gz: 41513d591f90f71e6ffdcbc21483236619d1912b5857d78a754bec3fa6d9180b40f9919fec7e2a17f43d3d3d4b618845e686510f8f423f2f4a801f73abec9167
7
- data.tar.gz: f79dc8a3a235c565c51d1ccaaf0b06aba964bba67b322476d097776095083f989d0b9bd5fbdb482d6e128a16bf6e523f954fa4e4b0e4847e3604e8c0d8fef174
6
+ metadata.gz: 43d05d52a2f8dfbc52c9cca47a731acb8c98d2b36d2d490aef3f08cf9a902bf29e9dd394f441fcd914d74fcf53230ccaaf011c0ef64e5c1be546947e6a5ca7d1
7
+ data.tar.gz: 8c4fc34fc4404ff3476bef408760b10af12113720aadad1428566ab34e8350d902375022621148bf7e3016ae1cdc8ee682cdeacece0f95438d4a177a400bceec
data/README.md CHANGED
@@ -16,12 +16,21 @@ This is especially true for applications covering many side effects, integration
16
16
  ```ruby
17
17
  # Gemfile
18
18
 
19
- gem 'pubsub_on_rails', '~> 0.0.3'
19
+ gem 'pubsub_on_rails', '~> 1.0.0'
20
20
 
21
21
  # config/initializers/pub_sub.rb
22
22
 
23
- PubSub::Subscriptions.subscriptions_path = Rails.root.join('config/subscriptions.yml')
24
- PubSub::Subscriptions.load!
23
+ require 'pub_sub/subscriptions_list'
24
+
25
+ Rails.configuration.to_prepare do
26
+ Rails.configuration.event_store = event_store = RailsEventStore::Client.new(
27
+ repository: RailsEventStoreActiveRecord::EventRepository.new(serializer: RubyEventStore::NULL)
28
+ )
29
+
30
+ PubSub::SubscriptionsList.config_path =
31
+ Rails.root.join('config/subscriptions.yml')
32
+ PubSub::SubscriptionsList.load!(event_store)
33
+ end
25
34
  ```
26
35
 
27
36
  ## Entities
@@ -40,7 +49,6 @@ Domain example:
40
49
  # app/domains/messaging.rb
41
50
 
42
51
  module Messaging
43
- extend PubSub::Domain
44
52
  end
45
53
  ```
46
54
 
@@ -72,6 +80,26 @@ module Ordering
72
80
  end
73
81
  ```
74
82
 
83
+ Since we are using Rails Event Store to handle events, it gives us a possibility to create **stream** of events. We can treat them as sub-list of events. To be able to use that functionality we need to declare which streams given event should be part of. By default we add event to stream based on its name. In case of our example it is `ordering__order_created`. We can provide also custom streams even based on some additional data from the event attributes (for example to group all events related to given order).
84
+
85
+ Event example:
86
+
87
+ ```ruby
88
+ # app/events/rails_event_store/ordering/order_created_event.rb
89
+
90
+ module PubSub
91
+ module Ordering
92
+ class OrderCreatedEvent < PubSub::EventWithType
93
+ def stream_names
94
+ [
95
+ "order__#{data[:order_id]}"
96
+ ]
97
+ end
98
+ end
99
+ end
100
+ end
101
+ ```
102
+
75
103
  ### Event publisher
76
104
 
77
105
  Event publisher is any class capable of emitting an event.
@@ -171,42 +199,41 @@ The recommended RSpec configuration is as follows:
171
199
 
172
200
  ```ruby
173
201
  # spec/support/pub_sub.rb
174
-
202
+
175
203
  require 'pub_sub/testing'
176
204
 
177
205
  RSpec.configure do |config|
178
- config.include Wisper::RSpec::BroadcastMatcher
179
- config.include PubSub::Testing::SubscriptionsHelper
206
+ config.include PubSub::Testing::RailsEventStore
180
207
  config.include PubSub::Testing::EventDataHelper
181
208
 
182
- config.before(:suite) do
183
- PubSub::Testing::SubscriptionsHelper.clear_wisper_subscriptions!
184
- end
185
-
186
- config.around(:each, subscribers: true) do |example|
187
- domain_name = example.metadata[:described_class].to_s.underscore
188
- PubSub.subscriptions.register(domain_name)
209
+ config.around(:each, in_memory_res_client: true) do |example|
210
+ current_event_store = Rails.configuration.event_store
211
+ Rails.configuration.event_store = RubyEventStore::Client.new(
212
+ repository: RubyEventStore::InMemoryRepository.new
213
+ )
189
214
  example.run
190
- clear_wisper_subscriptions!
215
+ Rails.configuration.event_store = current_event_store
191
216
  end
192
217
  end
193
218
  ```
194
219
 
220
+ This will allow you to use `in_memory_res_client` which will not create object (event) in the database and do not call all dependent logic (handlers).
221
+
195
222
  ### Testing subscription
196
223
 
197
224
  Testing subscription is as easy as telling what domains should subscribe to what event in what way.
198
- Example of `subscribe_to` matcher can be found [here](https://gist.github.com/stevo/bd11c8dae812c919de6a61d1292dbfe1).
225
+
199
226
  Example:
200
227
 
201
228
  ```ruby
202
- RSpec.describe Messaging, subscribers: true do
229
+ RSpec.describe Messaging do
203
230
  it { is_expected.to subscribe_to(:ordering__order_created).asynchronously }
204
231
  end
205
232
  ```
206
233
 
207
234
  ### Testing publishers
208
235
 
209
- To test publisher it is crucial to test if event was emitted (broadcasted) under certain conditions (if any).
236
+ To test publisher it is crucial to test if event was emitted under certain conditions (if any).
210
237
 
211
238
  Example:
212
239
 
@@ -217,20 +244,21 @@ RSpec.describe Order do
217
244
  customer = create(:customer)
218
245
  line_items = create_list(:line_item, 2)
219
246
 
220
- expect {
221
- Order.create(
222
- customer: customer,
223
- total_amount: 100.99,
224
- comment: 'Small order',
225
- line_items: line_items
247
+ Order.create(
248
+ customer: customer,
249
+ total_amount: 100.99,
250
+ comment: 'Small order',
251
+ line_items: line_items
252
+ )
253
+
254
+ expect(event_store).to have_published(
255
+ an_event(PubSub::Ordering::OrderCreatedEvent).with_data(
256
+ order_id: fetch_next_id_for(Order),
257
+ total_amount: 100.99,
258
+ comment: 'Small order',
259
+ line_items: line_items
226
260
  )
227
- }.to broadcast(
228
- :ordering__order_created,
229
- order_id: fetch_next_id_for(Order),
230
- total_amount: 100.99,
231
- comment: 'Small order',
232
- line_items: line_items
233
- )
261
+ ).in_stream('ordering__order_created')
234
262
  end
235
263
  end
236
264
  end
@@ -269,19 +297,6 @@ module Messaging
269
297
  end
270
298
  end
271
299
  ```
272
- ### Integration testing
273
-
274
- By default all subscriptions are cleared in testing environment to enforce testing in isolation.
275
- To enable integration testing, a test can be wrapped in block provided by `with_subscription_to` helper.
276
- This way events emitted by code executed within this block will be handled by handlers from domains provided as arguments to `with_subscription_to` helper.
277
-
278
- ```ruby
279
- it 'does something' do
280
- with_subscription_to('messaging', 'logging') do
281
- # setup, subject, assertions etc.
282
- end
283
- end
284
- ```
285
300
 
286
301
  ### Subscriptions linting
287
302
 
@@ -334,33 +349,6 @@ logging:
334
349
  all_events: sync
335
350
  ```
336
351
 
337
- # `emit` vs `broadcast`
338
-
339
- PubSub on Rails leverages `wisper` and `wisper-sidekiq` under the hood.
340
- This is why instead of using `emit`, you can broadcast events by using wisper's `broadcast` method.
341
-
342
- ```ruby
343
- # app/models/order.rb
344
-
345
- class Order < ApplicationRecord
346
- include Wisper::Publisher
347
-
348
- #...
349
-
350
- after_create do
351
- broadcast(
352
- :ordering__guest_checked_in,
353
- order_id: id,
354
- line_items: line_items,
355
- total_amount: total_amount,
356
- comment: comment
357
- )
358
- end
359
- end
360
- ```
361
-
362
- Why `emit` then? `emit` provides a couple of simplifications that make emitting events easier and more reliable.
363
-
364
352
  ## Payload verification
365
353
 
366
354
  Every time event is emitted, its payload is supplied to corresponding event class and is verified.
@@ -370,7 +358,7 @@ Example:
370
358
 
371
359
  ```ruby
372
360
  module Accounts
373
- class PersonCreatedEvent < DomainEvent
361
+ class PersonCreatedEvent < PubSub::DomainEvent
374
362
  attribute :person_id, Types::Strict::Integer
375
363
  end
376
364
  end
@@ -401,7 +389,7 @@ end
401
389
 
402
390
  ## Automatic event payload population
403
391
 
404
- Whenever you emit an event, it will try to populate its payload with data using public interface of object it is emitted from within.
392
+ Whenever you emit an event, it will try to populate its payload with data using public interface of object it is emitted from within.
405
393
 
406
394
  ```ruby
407
395
  # app/models/oriering/order.rb
@@ -423,16 +411,6 @@ module Ordering
423
411
  end
424
412
  ```
425
413
 
426
- ## Manually broadcasting events
427
-
428
- If for some reason you need to safely broadcast some event, the best way is to instantiate its class and call `#broadcast!` on it.
429
-
430
- Example:
431
-
432
- ```ruby
433
- Ordering::OrderCreatedEvent.new(order_id: 1, total_amount: 25, line_items: []).broadcast!
434
- ```
435
-
436
414
  # TODO
437
415
 
438
416
  - Dynamic event classes
@@ -1,28 +1,4 @@
1
- require 'pub_sub/pure_event'
2
-
3
1
  module PubSub
4
- class DomainEvent < PureEvent
5
- attribute :event_trace_id, Types::Strict::String.default {
6
- EventTrace.trace_id ||= SecureRandom.hex(8)
7
- }
8
- attribute :event_id, Types::Strict::String.default {
9
- SecureRandom.hex(8)
10
- }
11
- attribute :trigger_id, Types::Strict::String.default {
12
- EventTrace.last_event_id || 'ORIGIN'
13
- }
14
-
15
- def initialize(*args)
16
- super(*args)
17
- EventTrace.last_event_id = event_id
18
- end
19
-
20
- private
21
-
22
- def attributes_to_broadcast
23
- super.
24
- except(:event_trace_id, :event_id, :trigger_id).
25
- merge(event_uid: "#{event_trace_id}-#{trigger_id}-#{event_id}")
26
- end
2
+ class DomainEvent < Dry::Struct
27
3
  end
28
4
  end
@@ -1,7 +1,7 @@
1
1
  module PubSub
2
2
  class DomainEventHandler
3
- def initialize(*args)
4
- @event_data_hash = args.extract_options!
3
+ def initialize(event)
4
+ @event = event
5
5
  end
6
6
 
7
7
  def call
@@ -14,7 +14,7 @@ module PubSub
14
14
 
15
15
  private
16
16
 
17
- attr_reader :event_data_hash
17
+ attr_reader :event
18
18
 
19
19
  def process_event?
20
20
  true
@@ -23,5 +23,9 @@ module PubSub
23
23
  def event_data
24
24
  @event_data ||= OpenStruct.new(event_data_hash)
25
25
  end
26
+
27
+ def event_data_hash
28
+ event.data
29
+ end
26
30
  end
27
31
  end
data/lib/pub_sub/emit.rb CHANGED
@@ -4,13 +4,14 @@ require 'pub_sub/event_emission'
4
4
  module PubSub
5
5
  module Emit
6
6
  def emit(event_name, explicit_payload = {})
7
+ abstract_event_class = explicit_payload.delete(:abstract_event_class)
7
8
  event_class = EventClassFactory.build(
8
9
  event_name,
9
10
  domain_name: self.class.name.deconstantize.demodulize,
10
- abstract_event_class: explicit_payload.delete(:abstract_event_class)
11
+ abstract_event_class:
11
12
  )
12
13
 
13
- EventEmission.new(event_class, explicit_payload, self).call
14
+ EventEmission.new(abstract_event_class, event_class, event_name, explicit_payload, self).call
14
15
  end
15
16
  end
16
17
  end
@@ -5,8 +5,8 @@ module PubSub
5
5
  def self.build(event_name, domain_name: nil, abstract_event_class: nil)
6
6
  new(
7
7
  event_name,
8
- domain_name: domain_name,
9
- abstract_event_class: abstract_event_class
8
+ domain_name:,
9
+ abstract_event_class:
10
10
  ).build_event_class
11
11
  end
12
12
 
@@ -17,9 +17,13 @@ module PubSub
17
17
  end
18
18
 
19
19
  def build_event_class
20
+ event_class = res_event_class_name.safe_constantize
21
+
22
+ return event_class if event_class.present?
23
+
20
24
  event_class = event_class_name.safe_constantize
21
25
 
22
- return event_class unless event_class.nil?
26
+ return event_class if event_class.present?
23
27
 
24
28
  if abstract_event_class.nil?
25
29
  raise(EventClassDoesNotExist, event_class_name)
@@ -46,12 +50,16 @@ module PubSub
46
50
 
47
51
  def event_name_with_domain
48
52
  if event_name_includes_domain?
49
- event_name.to_s.sub('__', '/')
53
+ event_name.to_s.downcase.sub('__', '/')
50
54
  else
51
- "#{domain_name}/#{event_name}"
55
+ [domain_name&.underscore, event_name].compact.join('/')
52
56
  end
53
57
  end
54
58
 
59
+ def res_event_class_name
60
+ @res_event_class_name ||= "pub_sub/#{event_name_with_domain}_event".classify
61
+ end
62
+
55
63
  def event_class_name
56
64
  @event_class_name ||= "#{event_name_with_domain}_event".classify
57
65
  end
@@ -1,44 +1,78 @@
1
1
  require 'pub_sub/payload_attribute'
2
2
 
3
3
  module PubSub
4
- class EventEmission
5
- EventPayloadArgumentMissing = Class.new(StandardError)
4
+ Error = Class.new(StandardError)
5
+ EventMissing = Class.new(Error)
6
+ EventPayloadArgumentMissing = Class.new(Error)
6
7
 
7
- def initialize(event_class, explicit_payload, context)
8
+ class EventEmission
9
+ def initialize(abstract_event_class, event_class, event_name, explicit_payload, context)
10
+ @abstract_event_class = abstract_event_class
8
11
  @event_class = event_class
12
+ @event_name = event_name
9
13
  @explicit_payload = explicit_payload
10
14
  @context = context
11
15
  end
12
16
 
13
17
  def call
14
- event_class.new(full_payload).broadcast!
18
+ if event_class.ancestors.include?(PubSub::EventWithType)
19
+ event_store.publish(event, stream_name:)
20
+ else
21
+ raise(EventMissing, event_name)
22
+ end
15
23
  end
16
24
 
17
25
  private
18
26
 
19
- attr_reader :event_class, :explicit_payload, :context
27
+ attr_reader :abstract_event_class, :event_class, :event_name, :explicit_payload, :context
28
+
29
+ def event
30
+ event_class.new(data: full_payload)
31
+ end
32
+
33
+ def event_name_includes_domain?
34
+ event_name.to_s.include?('__')
35
+ end
36
+
37
+ def stream_name
38
+ return event_name.to_s.downcase if event_name_includes_domain?
20
39
 
21
- def event_payload_attribute_names
22
- event_class.attribute_names
40
+ "#{domain}__#{event_name}"
41
+ end
42
+
43
+ def domain
44
+ if abstract_event_class
45
+ abstract_event_class.name.deconstantize.underscore
46
+ else
47
+ context.class.name.deconstantize.demodulize.underscore
48
+ end
49
+ end
50
+
51
+ def dry_struct_event_class
52
+ @dry_struct_event_class ||= event_class.name.remove('PubSub').constantize
23
53
  end
24
54
 
25
55
  # rubocop:disable Metrics/MethodLength
26
56
  def full_payload
27
- event_payload_attribute_names.each_with_object({}) do |attribute_name, result|
28
- result[attribute_name] = PayloadAttribute.new(attribute_name, explicit_payload, context).get
29
- rescue PayloadAttribute::CannotEvaluate => cannot_evaluate_error
30
- if event_class.schema.key(attribute_name).default?
31
- next
32
- else
33
- raise(
34
- EventPayloadArgumentMissing,
35
- "Event [#{event_class.name}] expects [#{attribute_name}] payload attribute to be" \
36
- " either exposed as [#{cannot_evaluate_error.message}] method in emitting object" \
37
- ' or provided as argument'
38
- )
39
- end
57
+ dry_struct_event_class.attribute_names.each_with_object({}) do |attribute_name, result|
58
+ result[attribute_name] = PayloadAttribute.new(
59
+ attribute_name, explicit_payload, context
60
+ ).get
61
+ rescue PayloadAttribute::CannotEvaluate => e
62
+ next if dry_struct_event_class.schema.key(attribute_name).default?
63
+
64
+ raise(
65
+ EventPayloadArgumentMissing,
66
+ "Event [#{dry_struct_event_class.name}] expects [#{attribute_name}] " \
67
+ "payload attribute to be either exposed as [#{e.message}] method in emitting object " \
68
+ 'or provided as argument'
69
+ )
40
70
  end
41
71
  end
42
72
  # rubocop:enable Metrics/MethodLength
73
+
74
+ def event_store
75
+ Rails.configuration.event_store
76
+ end
43
77
  end
44
78
  end
@@ -0,0 +1,30 @@
1
+ module PubSub
2
+ class EventHandlerBuilder
3
+ def initialize(class_name, subscription_type)
4
+ @class_name = class_name
5
+ @subscription_type = subscription_type.to_sym
6
+ end
7
+
8
+ def call(event)
9
+ if async?
10
+ EventWorker.perform_async(class_name.to_s, event.event_id)
11
+ else
12
+ class_name.new(event).call!
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ attr_reader :class_name, :subscription_type
19
+
20
+ def ==(other)
21
+ class_name == other.class_name && subscription_type == other.subscription_type
22
+ end
23
+
24
+ private
25
+
26
+ def async?
27
+ subscription_type == :async
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module PubSub
2
+ class EventWithType < RailsEventStore::Event
3
+ def initialize(event_id: SecureRandom.uuid, metadata: nil, data: {})
4
+ super(
5
+ event_id:,
6
+ metadata:,
7
+ data: self.class.name.remove('PubSub').constantize.new(
8
+ data.deep_symbolize_keys
9
+ ).attributes
10
+ )
11
+ end
12
+
13
+ def stream_names
14
+ []
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module PubSub
2
+ class EventWorker
3
+ include Sidekiq::Job
4
+
5
+ def perform(class_name, event_id)
6
+ class_name.constantize.new(
7
+ event_store.read.event(event_id)
8
+ ).call!
9
+ end
10
+
11
+ private
12
+
13
+ def event_store
14
+ Rails.configuration.event_store
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ module PubSub
2
+ class SubscriptionsLinter
3
+ MissingSubscriptions = Class.new(StandardError)
4
+
5
+ def initialize(subscriptions)
6
+ @subscriptions = subscriptions
7
+ end
8
+
9
+ def lint!
10
+ raise MissingSubscriptions, error_message if missing_subscriptions.present?
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :subscriptions
16
+
17
+ def error_message
18
+ "The following subscriptions are missing: \n#{missing_subscriptions.join("\n")}"
19
+ end
20
+
21
+ def missing_subscriptions
22
+ (handled_subscription_names - all_subscription_names)
23
+ end
24
+
25
+ # :reek:UtilityFunction, :reek:DuplicateMethodCall
26
+ def handled_subscription_names
27
+ Dir[Rails.root.join('app/event_handlers/*/*.rb')].map do |file_path|
28
+ file_path.
29
+ sub(Rails.root.join('app/event_handlers/').to_s, '').
30
+ sub('_handler.rb', '')
31
+ end
32
+ end
33
+
34
+ def all_subscription_names
35
+ subscriptions.flat_map do |domain_name, subscriptions|
36
+ subscriptions.keys.map do |event_name|
37
+ "#{domain_name}/#{event_name.sub('__', '_')}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,62 @@
1
+ require 'pub_sub/subscriptions_linter'
2
+ require 'pub_sub/event_handler_builder'
3
+
4
+ module PubSub
5
+ class SubscriptionsList
6
+ include Singleton
7
+
8
+ cattr_accessor :config_path
9
+ self.config_path = 'config/subscriptions.yml'
10
+
11
+ def self.load!(event_store)
12
+ instance.event_store = event_store
13
+ instance.load!
14
+ end
15
+
16
+ def self.lint!
17
+ instance.lint!
18
+ end
19
+
20
+ attr_accessor :event_store
21
+
22
+ def load!
23
+ domain_subscriptions.each do |domain_name, subscriptions|
24
+ subscriptions.each do |event_name, subscription_type|
25
+ if event_name == 'all_events'
26
+ subscribe_to_all_events(domain_name, subscription_type)
27
+ else
28
+ subscribe_to_event(domain_name, event_name, subscription_type)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def lint!
35
+ SubscriptionsLinter.new(domain_subscriptions).lint!
36
+ end
37
+
38
+ def initialize
39
+ @domain_subscriptions = YAML.load_file(self.class.config_path)
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :domain_subscriptions
45
+
46
+ def subscribe_to_all_events(domain_name, subscription_type)
47
+ handler_class = "#{domain_name.camelize}Handler".constantize
48
+ event_store.subscribe_to_all_events(
49
+ EventHandlerBuilder.new(handler_class, subscription_type)
50
+ )
51
+ end
52
+
53
+ def subscribe_to_event(domain_name, event_name, subscription_type)
54
+ event_domain, name = event_name.split('__').map(&:camelize)
55
+ event_class = "PubSub::#{event_domain}::#{name}Event".constantize
56
+ handler_class = "#{domain_name.camelize}::#{event_domain}#{name}Handler".constantize
57
+ event_store.subscribe(
58
+ EventHandlerBuilder.new(handler_class, subscription_type), to: [event_class]
59
+ )
60
+ end
61
+ end
62
+ end
@@ -2,10 +2,15 @@ module PubSub
2
2
  module Testing
3
3
  module EventDataHelper
4
4
  def event_data_for(event_name, **payload)
5
- EventClassFactory.
6
- build(event_name, abstract_event_class: payload.delete(:abstract_event_class)).
7
- new(payload).
8
- attributes
5
+ event_class = PubSub::EventClassFactory.build(
6
+ event_name, abstract_event_class: payload.delete(:abstract_event_class)
7
+ )
8
+
9
+ if event_class.ancestors.include?(PubSub::EventWithType)
10
+ event_class.new(data: payload)
11
+ else
12
+ event_class.new(payload).attributes
13
+ end
9
14
  end
10
15
  end
11
16
  end
@@ -0,0 +1,9 @@
1
+ module PubSub
2
+ module Testing
3
+ module RailsEventStore
4
+ def event_store
5
+ Rails.configuration.event_store
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ RSpec::Matchers.define :subscribe_to do |event_name|
2
+ match do |domain|
3
+ handler_class = build_handler_class(event_name, domain)
4
+ event_class = build_event_class(event_name)
5
+ subscription_type = async? ? :async : :sync
6
+
7
+ expect(
8
+ PubSub::EventHandlerBuilder.new(handler_class, subscription_type)
9
+ ).to have_subscribed_to_events(event_class).in(event_store)
10
+ end
11
+
12
+ chain :asynchronously do
13
+ @asynchronously = true
14
+ end
15
+
16
+ private
17
+
18
+ def build_handler_class(event_name, domain)
19
+ handler_name = event_name.to_s.sub('__', '/').camelize
20
+ handler_name.remove!('::')
21
+ "#{domain.name}::#{handler_name}Handler".constantize
22
+ end
23
+
24
+ def build_event_class(event_name)
25
+ event_class_name = event_name.to_s.sub('__', '/').camelize
26
+ "PubSub::#{event_class_name}Event".constantize
27
+ end
28
+
29
+ def async?
30
+ @asynchronously
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
- require 'wisper/rspec/matchers'
1
+ require 'pub_sub/testing/rails_event_store'
2
2
  require 'pub_sub/testing/event_data_helper'
3
- require 'pub_sub/testing/subscription_helpers'
3
+ require 'pub_sub/testing/subscribe_to'
@@ -1,3 +1,3 @@
1
1
  module PubSub
2
- VERSION = '0.0.7'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -1,11 +1,9 @@
1
- require 'wisper'
2
- require 'wisper/sidekiq'
1
+ require 'rails_event_store'
3
2
 
4
3
  require 'pub_sub/dry'
5
4
  require 'pub_sub'
6
5
  require 'pub_sub/emit'
7
- require 'pub_sub/subscriptions'
8
- require 'pub_sub/event_trace'
9
- require 'pub_sub/domain'
6
+ require 'pub_sub/event_with_type'
7
+ require 'pub_sub/subscriptions_list'
10
8
  require 'pub_sub/domain_event'
11
9
  require 'pub_sub/domain_event_handler'
Binary file
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+ require 'pub_sub/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'pubsub_on_rails'
8
+ s.version = PubSub::VERSION
9
+ s.authors = ['Stevo']
10
+ s.email = ['b.kosmowski@selleo.com']
11
+ s.homepage = 'https://github.com/Selleo/pubsub_on_rails'
12
+ s.licenses = ['MIT']
13
+ s.summary = 'Opinionated publish-subscribe pattern for ruby and rails'
14
+ s.description = 'Opinionated publish-subscribe pattern for ruby and rails'
15
+
16
+ s.files = Dir.glob('{bin/*,lib/**/*,[A-Z]*}')
17
+ s.platform = Gem::Platform::RUBY
18
+ s.require_paths = ['lib']
19
+ s.add_dependency 'dry-struct'
20
+ s.add_dependency 'sidekiq'
21
+ s.add_dependency 'rails_event_store'
22
+ s.add_dependency 'ruby_event_store-rspec'
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubsub_on_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stevo
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-10 00:00:00.000000000 Z
11
+ date: 2023-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-struct
@@ -25,21 +25,21 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: wisper
28
+ name: sidekiq
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 2.0.0
33
+ version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 2.0.0
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: wisper-sidekiq
42
+ name: rails_event_store
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: wisper-rspec
56
+ name: ruby_event_store-rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -74,32 +74,34 @@ extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
76
  - Gemfile
77
- - Gemfile.lock
78
77
  - LICENSE.md
79
78
  - README.md
80
79
  - lib/pub_sub.rb
81
- - lib/pub_sub/domain.rb
82
80
  - lib/pub_sub/domain_event.rb
83
81
  - lib/pub_sub/domain_event_handler.rb
84
82
  - lib/pub_sub/dry.rb
85
83
  - lib/pub_sub/emit.rb
86
84
  - lib/pub_sub/event_class_factory.rb
87
85
  - lib/pub_sub/event_emission.rb
88
- - lib/pub_sub/event_trace.rb
89
- - lib/pub_sub/linter.rb
86
+ - lib/pub_sub/event_handler_builder.rb
87
+ - lib/pub_sub/event_with_type.rb
88
+ - lib/pub_sub/event_worker.rb
90
89
  - lib/pub_sub/payload_attribute.rb
91
- - lib/pub_sub/pure_event.rb
92
- - lib/pub_sub/subscriptions.rb
90
+ - lib/pub_sub/subscriptions_linter.rb
91
+ - lib/pub_sub/subscriptions_list.rb
93
92
  - lib/pub_sub/testing.rb
94
93
  - lib/pub_sub/testing/event_data_helper.rb
95
- - lib/pub_sub/testing/subscription_helpers.rb
94
+ - lib/pub_sub/testing/rails_event_store.rb
95
+ - lib/pub_sub/testing/subscribe_to.rb
96
96
  - lib/pub_sub/version.rb
97
97
  - lib/pubsub_on_rails.rb
98
+ - pubsub_on_rails-0.0.7.gem
99
+ - pubsub_on_rails.gemspec
98
100
  homepage: https://github.com/Selleo/pubsub_on_rails
99
101
  licenses:
100
102
  - MIT
101
103
  metadata: {}
102
- post_install_message:
104
+ post_install_message:
103
105
  rdoc_options: []
104
106
  require_paths:
105
107
  - lib
@@ -114,9 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
116
  - !ruby/object:Gem::Version
115
117
  version: '0'
116
118
  requirements: []
117
- rubyforge_project:
118
- rubygems_version: 2.6.14
119
- signing_key:
119
+ rubygems_version: 3.4.12
120
+ signing_key:
120
121
  specification_version: 4
121
122
  summary: Opinionated publish-subscribe pattern for ruby and rails
122
123
  test_files: []
data/Gemfile.lock DELETED
@@ -1,64 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- pubsub_on_rails (0.0.1)
5
- dry-struct
6
- wisper (~> 2.0.0)
7
- wisper-rspec
8
- wisper-sidekiq
9
-
10
- GEM
11
- remote: https://rubygems.org/
12
- specs:
13
- concurrent-ruby (1.1.5)
14
- connection_pool (2.2.2)
15
- dry-configurable (0.8.2)
16
- concurrent-ruby (~> 1.0)
17
- dry-core (~> 0.4, >= 0.4.7)
18
- dry-container (0.7.0)
19
- concurrent-ruby (~> 1.0)
20
- dry-configurable (~> 0.1, >= 0.1.3)
21
- dry-core (0.4.7)
22
- concurrent-ruby (~> 1.0)
23
- dry-equalizer (0.2.2)
24
- dry-inflector (0.1.2)
25
- dry-logic (1.0.0)
26
- concurrent-ruby (~> 1.0)
27
- dry-core (~> 0.2)
28
- dry-equalizer (~> 0.2)
29
- dry-struct (1.0.0)
30
- dry-core (~> 0.4, >= 0.4.3)
31
- dry-equalizer (~> 0.2)
32
- dry-types (~> 1.0)
33
- ice_nine (~> 0.11)
34
- dry-types (1.0.0)
35
- concurrent-ruby (~> 1.0)
36
- dry-container (~> 0.3)
37
- dry-core (~> 0.4, >= 0.4.4)
38
- dry-equalizer (~> 0.2, >= 0.2.2)
39
- dry-inflector (~> 0.1, >= 0.1.2)
40
- dry-logic (~> 1.0)
41
- ice_nine (0.11.2)
42
- rack (2.0.7)
43
- rack-protection (2.0.5)
44
- rack
45
- redis (4.1.1)
46
- sidekiq (5.2.7)
47
- connection_pool (~> 2.2, >= 2.2.2)
48
- rack (>= 1.5.0)
49
- rack-protection (>= 1.5.0)
50
- redis (>= 3.3.5, < 5)
51
- wisper (2.0.0)
52
- wisper-rspec (1.1.0)
53
- wisper-sidekiq (1.2.0)
54
- sidekiq (>= 4.1)
55
- wisper
56
-
57
- PLATFORMS
58
- ruby
59
-
60
- DEPENDENCIES
61
- pubsub_on_rails!
62
-
63
- BUNDLED WITH
64
- 1.17.2
@@ -1,25 +0,0 @@
1
- module PubSub
2
- module Domain
3
- def method_missing(event_or_method_name, *event_data)
4
- if handler_name(event_or_method_name)
5
- event_payload = event_data.extract_options!
6
- EventTrace.load_from(event_payload.delete(:event_uid))
7
- const_get(handler_name(event_or_method_name)).new(event_payload).call!
8
- else
9
- super
10
- end
11
- end
12
-
13
- def handler_name(event_name)
14
- return nil unless event_name.to_s.start_with?(/[a-z_]+__/)
15
- "#{event_name.to_s.camelize}Handler"
16
- end
17
-
18
- def respond_to_missing?(event_or_method_name, include_private = false)
19
- return super unless handler_name(event_or_method_name)
20
- const_get(handler_name(event_or_method_name))
21
- rescue NameError
22
- super
23
- end
24
- end
25
- end
@@ -1,18 +0,0 @@
1
- module PubSub
2
- class EventTrace < ActiveSupport::CurrentAttributes
3
- EVENT_TRACE_UID_REGEX = /^(?<trace_id>\w+)-(?<trigger_id>\w+)-(?<event_id>\w+)$/
4
- private_constant :EVENT_TRACE_UID_REGEX
5
-
6
- attribute :trace_id
7
- attribute :last_event_id
8
-
9
- def self.load_from(event_trace_uid)
10
- match_data = event_trace_uid&.match(EVENT_TRACE_UID_REGEX)
11
-
12
- if match_data
13
- self.trace_id = match_data['trace_id']
14
- self.last_event_id = match_data['event_id']
15
- end
16
- end
17
- end
18
- end
@@ -1,39 +0,0 @@
1
- class Linter
2
- MissingSubscriptions = Class.new(StandardError)
3
-
4
- def initialize(config)
5
- @config = config
6
- end
7
-
8
- def lint!
9
- raise MissingSubscriptions, error_message if missing_subscriptions.present?
10
- end
11
-
12
- private
13
-
14
- def error_message
15
- "The following subscriptions are missing: \n#{missing_subscriptions.join("\n")}"
16
- end
17
-
18
- def missing_subscriptions
19
- (handlers_list - subscriptions_list)
20
- end
21
-
22
- attr_reader :config
23
-
24
- def subscriptions_list
25
- config.flat_map do |domain_name, subscriptions|
26
- subscriptions.keys.map do |event_name|
27
- "#{domain_name}/#{event_name.sub('__', '_')}"
28
- end
29
- end
30
- end
31
-
32
- def handlers_list
33
- Dir[Rails.root.join('app/event_handlers/*/*.rb')].map do |file_path|
34
- file_path.
35
- sub("#{Rails.root}/app/event_handlers/", '').
36
- sub('_handler.rb', '')
37
- end
38
- end
39
- end
@@ -1,19 +0,0 @@
1
- module PubSub
2
- class PureEvent < Dry::Struct
3
- include Wisper::Publisher
4
-
5
- def broadcast!
6
- broadcast(event_name, attributes_to_broadcast)
7
- end
8
-
9
- private
10
-
11
- def attributes_to_broadcast
12
- attributes
13
- end
14
-
15
- def event_name
16
- self.class.name.underscore.sub('/', '__').chomp('_event')
17
- end
18
- end
19
- end
@@ -1,49 +0,0 @@
1
- require 'pub_sub/linter'
2
-
3
- module PubSub
4
- mattr_accessor :subscriptions
5
-
6
- class Subscriptions
7
- include Singleton
8
-
9
- cattr_accessor :subscriptions_path
10
- self.subscriptions_path = 'config/subscriptions.yml'
11
-
12
- def self.load!
13
- PubSub.subscriptions = Subscriptions.instance
14
- PubSub.subscriptions.register(:all)
15
- end
16
-
17
- def self.lint!
18
- instance.lint!
19
- end
20
-
21
- def lint!
22
- Linter.new(config).lint!
23
- end
24
-
25
- def initialize
26
- @config = YAML.load_file(self.class.subscriptions_path)
27
- end
28
-
29
- def register(scope = :all)
30
- (scope == :all ? config : config.slice(scope.to_s)).each do |domain_name, subscriptions|
31
- subscriptions.each do |event_name, subscription_type|
32
- options = {}
33
- options[:on] = event_name unless event_name == 'all_events'
34
- options[:broadcaster] = subscription_type == 'sync' ? :default : subscription_type.to_sym
35
-
36
- Wisper.subscribe("::#{domain_name.camelize}".constantize, options)
37
- end
38
- end
39
- end
40
-
41
- def clear!
42
- Wisper.clear
43
- end
44
-
45
- private
46
-
47
- attr_reader :config
48
- end
49
- end
@@ -1,24 +0,0 @@
1
- module PubSub
2
- module Testing
3
- module SubscriptionsHelper
4
- def with_subscription_to(*domains)
5
- domains.each do |domain|
6
- PubSub.subscriptions.register(domain)
7
- end
8
- yield
9
- clear_wisper_subscriptions!
10
- end
11
-
12
- def subscribe_logger!
13
- PubSub.subscriptions.register(:logging)
14
- end
15
- module_function :subscribe_logger!
16
-
17
- def clear_wisper_subscriptions!
18
- PubSub.subscriptions.clear!
19
- subscribe_logger!
20
- end
21
- module_function :clear_wisper_subscriptions!
22
- end
23
- end
24
- end