event_sourcery 0.21.0 → 0.24.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
2
  SHA256:
3
- metadata.gz: bab180e1a9e9380aad56c19810e3effb6911350010a4e1538711885fe177cd49
4
- data.tar.gz: 0c5ca78c052c3b8a61f7c9a4141038e25e77c9d1802aea80a6bc9b6045b5e255
3
+ metadata.gz: f837642276b30c8fae75c9314c2adc4854e5b46105b520870f4960cf2b13b461
4
+ data.tar.gz: 3622196388903fcf3b4f9b98fb0f5317cbffd71c6cd8b8ccc3cf571d63a773d2
5
5
  SHA512:
6
- metadata.gz: 4db8e9b04415d9dc57871f2ebd25a9e2cb70752e109ee4fec6b396fd11528ccdc660edf57a29e04373e62946eca596ebc56495da2ae17a0946decfeb0ef88ba1
7
- data.tar.gz: 232ee9d65f9e61545f5f914f5a8de5893495fa22ce515d818bd1dca07bf7da9992711233843e1bbbe01f894b8e8ddbcf148929f0ab69f84a47a78270cff356d9
6
+ metadata.gz: 110dde9b74656dffc1838f5855c203bbd3e74b593a81f5d8a876c2629f99a032b8c577bcd3e59c35f96945032504051d9a7dd1103daa3e44a282b91660f01ac7
7
+ data.tar.gz: cf26df2449e5db4a83ae3ce336e382b13c81bd3e6a7381dccab52bcc37d0928c2fa1e44f30d0d351475b520ab739e36b7f9438d2afca6f606d0e091a56725b30
data/CHANGELOG.md CHANGED
@@ -5,6 +5,107 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [Unreleased]
9
+ ## [0.24.0] - 2021-11-18
10
+
11
+ ### Added
12
+
13
+ - Test against Ruby 3.0 in the CI build ([#229]).
14
+
15
+ ### Changed
16
+
17
+ - Use GitHub Actions for the CI build instead of Travis CI ([#228]).
18
+ - This project now uses `main` as its default branch ([#230]).
19
+ - Documentation updated to refer to `main` and links updated accordingly.
20
+
21
+ ### Removed
22
+ - Remove Ruby 2.3, 2.4 and 2.5 from the CI test matrix ([#232]).
23
+
24
+ [#228]: https://github.com/envato/event_sourcery/pull/228
25
+ [#229]: https://github.com/envato/event_sourcery/pull/229
26
+ [#230]: https://github.com/envato/event_sourcery/pull/230
27
+ [#232]: https://github.com/envato/event_sourcery/pull/232
28
+
29
+ ## [0.23.1] - 2020-10-02
30
+ ### Fixed
31
+ - Upgrade development dependency Rake to version 13. This resolves
32
+ [CVE-2020-8130](https://github.com/advisories/GHSA-jppv-gw3r-w3q8).
33
+
34
+ - Patch `ESPRunner` to gracefully handle terminating subprocesses it did
35
+ not start ([#223]).
36
+
37
+ - Resolve warnings raised by Ruby 2.7 ([#225]).
38
+
39
+ [#223]: https://github.com/envato/event_sourcery/pull/223
40
+ [#225]: https://github.com/envato/event_sourcery/pull/225
41
+
42
+ ## [0.23.0] - 2019-07-11
43
+ ### Added
44
+ - Add Ruby 2.6 to the CI test matrix.
45
+ - `ESPRunner` supports an `after_subprocess_termination` hook. This optional
46
+ initializer argument will will be executed when each child process
47
+ terminates. This allows for monitoring and alerts to be configured.
48
+ For example, Rollbar:
49
+
50
+ ```ruby
51
+ EventSourcery::EventProcessing::ESPRunner.new(
52
+ event_processors: processors,
53
+ event_source: source,
54
+ after_subprocess_termination: proc do |processor:, runner:, exit_status:|
55
+ if exit_status != 0
56
+ Rollbar.error("Processor #{processor.processor_name} "\
57
+ "terminated with exit status #{exit_status}")
58
+ end
59
+ end
60
+ ).start!
61
+ ```
62
+
63
+ - `ESPRunner` exposes three new public methods `start_processor`, `shutdown`,
64
+ and `shutdown_requested?`. These provide options for handling subprocess
65
+ failure/termination. For example, shutting down the `ESPRunner`:
66
+
67
+ ```ruby
68
+ EventSourcery::EventProcessing::ESPRunner.new(
69
+ event_processors: processors,
70
+ event_source: source,
71
+ after_subprocess_termination: proc do |processor:, runner:, exit_status:|
72
+ runner.shutdown
73
+ end
74
+ ).start!
75
+ ```
76
+
77
+ Or restarting the event processor:
78
+
79
+ ```ruby
80
+ EventSourcery::EventProcessing::ESPRunner.new(
81
+ event_processors: processors,
82
+ event_source: source,
83
+ after_subprocess_termination: proc do |processor:, runner:, exit_status:|
84
+ runner.start_processor(processor) unless runner.shutdown_requested?
85
+ end
86
+ ).start!
87
+ ```
88
+
89
+ - `ESPRunner` checks for dead child processes every second. This means we
90
+ shouldn't see `[ruby] <defunct>` in the process list (ps) when a processor
91
+ fails.
92
+ - `ESPRunner` logs when child processes die.
93
+ - `ESPRunner` logs when sending signals to child processes.
94
+
95
+ ### Removed
96
+ - Remove Ruby 2.2 from the CI test matrix.
97
+
98
+ ## [0.22.0] - 2018-10-04
99
+ ### Added
100
+ - Log critical exceptions to the application provided block via the new
101
+ configuration option ([#209](https://github.com/envato/event_sourcery/pull/209)):
102
+
103
+ ```ruby
104
+ config.on_event_processor_critical_error = proc do |exception, processor_name|
105
+ # report the death of this processor to an error reporting service like Rollbar.
106
+ end
107
+ ```
108
+
8
109
  ## [0.21.0] - 2018-07-02
9
110
  ### Added
10
111
  - Graceful shutdown interrupts poll-wait sleep for quicker quitting
@@ -18,7 +119,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
18
119
 
19
120
  ## [0.20.0] - 2018-06-21
20
121
  ### Changed
21
- - Changed signature of `ESPProcess#initialize` to include a default value for `after_fork`. This prevents the
122
+ - Changed signature of `ESPProcess#initialize` to include a default value for `after_fork`. This prevents the
22
123
  `after_fork` change from 0.19.0 from being a breaking change to external creators of ESPProcess.
23
124
  - Added more logging when a fatal exception occurs in ESPProcess
24
125
 
@@ -152,7 +253,11 @@ moving all Postgres related code into a separate gem.
152
253
  - EventSourcery no longer depends on Virtus.
153
254
  - `Command` and `CommandHandler` have been removed.
154
255
 
155
- [Unreleased]: https://github.com/envato/event_sourcery/compare/v0.21.0...HEAD
256
+ [Unreleased]: https://github.com/envato/event_sourcery/compare/v0.24.0...HEAD
257
+ [0.24.0]: https://github.com/envato/event_sourcery/compare/v0.23.1...v0.24.0
258
+ [0.23.1]: https://github.com/envato/event_sourcery/compare/v0.23.0...v0.23.1
259
+ [0.23.0]: https://github.com/envato/event_sourcery/compare/v0.22.0...v0.23.0
260
+ [0.22.0]: https://github.com/envato/event_sourcery/compare/v0.21.0...v0.22.0
156
261
  [0.21.0]: https://github.com/envato/event_sourcery/compare/v0.20.0...v0.21.0
157
262
  [0.20.0]: https://github.com/envato/event_sourcery/compare/v0.19.0...v0.20.0
158
263
  [0.19.0]: https://github.com/envato/event_sourcery/compare/v0.18.0...v0.19.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # EventSourcery
2
2
 
3
- [![Build Status](https://travis-ci.org/envato/event_sourcery.svg?branch=master)](https://travis-ci.org/envato/event_sourcery)
3
+ [![Build Status](https://github.com/envato/event_sourcery/workflows/tests/badge.svg?branch=main)](https://github.com/envato/event_sourcery/actions?query=workflow%3Atests+branch%3Amain)
4
4
 
5
5
  A framework for building event sourced, CQRS applications.
6
6
 
@@ -67,6 +67,7 @@ EventSourcery.configure do |config|
67
67
  # Add custom reporting of errors occurring during event processing.
68
68
  # One might set up an error reporting service like Rollbar here.
69
69
  config.on_event_processor_error = proc { |exception, processor_name| … }
70
+ config.on_event_processor_critical_error = proc { |exception, processor_name| … }
70
71
 
71
72
  # Enable EventSourcery logging.
72
73
  config.logger = Logger.new('logs/my_event_sourcery_app.log')
@@ -104,7 +105,7 @@ To release a new version:
104
105
 
105
106
  1. Update the version number in `lib/event_sourcery/version.rb`
106
107
  2. Add the new version with release notes to CHANGELOG.md
107
- 3. Get these changes onto master via the normal PR process
108
+ 3. Get these changes onto main via the normal PR process
108
109
  4. Run `bundle exec rake release`, this will create a git tag for the
109
110
  version, push tags up to GitHub, and package the code in a `.gem` file.
110
111
 
@@ -217,7 +218,7 @@ The event store is a persistent store of events.
217
218
 
218
219
  EventSourcery currently supports a Postgres-based event store via the [event_sourcery-postgres gem](https://github.com/envato/event_sourcery-postgres).
219
220
 
220
- For more information about the `EventStore` API refer to [the postgres event store](https://github.com/envato/event_sourcery-postgres/blob/master/lib/event_sourcery/postgres/event_store.rb) or the [in memory event store in this repo](lib/event_sourcery/event_store/memory.rb)
221
+ For more information about the `EventStore` API refer to [the postgres event store](https://github.com/envato/event_sourcery-postgres/blob/HEAD/lib/event_sourcery/postgres/event_store.rb) or the [in memory event store in this repo](lib/event_sourcery/memory/event_store.rb)
221
222
 
222
223
  #### Storing Events
223
224
 
@@ -293,7 +294,7 @@ Reactors can be used to build [process managers or sagas](https://msdn.microsoft
293
294
 
294
295
  #### Running Multiple ESPs
295
296
 
296
- An EventSourcery application will typically have multiple ESPs running. EventSourcery provides a class called [ESPRunner](lib/event_sourcery/event_processing/esp_runner.rb) which can be used to run ESPs. It runs each ESP in a forked child process so each ESP can process the event store independently. You can find an example in [event_sourcery_todo_app](https://github.com/envato/event_sourcery_todo_app/blob/master/Rakefile).
297
+ An EventSourcery application will typically have multiple ESPs running. EventSourcery provides a class called [ESPRunner](lib/event_sourcery/event_processing/esp_runner.rb) which can be used to run ESPs. It runs each ESP in a forked child process so each ESP can process the event store independently. You can find an example in [event_sourcery_todo_app](https://github.com/envato/event_sourcery_todo_app/blob/HEAD/Rakefile).
297
298
 
298
299
  Note that you may instead choose to run each ESP in their own process directly. The coordination of this is not currently provided by EventSourcery.
299
300
 
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = 'https://github.com/envato/event_sourcery'
15
15
  spec.metadata = {
16
16
  'bug_tracker_uri' => 'https://github.com/envato/event_sourcery/issues',
17
- 'changelog_uri' => 'https://github.com/envato/event_sourcery/blob/master/CHANGELOG.md',
17
+ 'changelog_uri' => 'https://github.com/envato/event_sourcery/blob/HEAD/CHANGELOG.md',
18
18
  'source_code_uri' => 'https://github.com/envato/event_sourcery',
19
19
  }
20
20
 
@@ -25,8 +25,8 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.required_ruby_version = '>= 2.2.0'
27
27
 
28
- spec.add_development_dependency 'bundler', '~> 1.10'
29
- spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'bundler'
29
+ spec.add_development_dependency 'rake', '~> 13'
30
30
  spec.add_development_dependency 'rspec'
31
31
  spec.add_development_dependency 'pry'
32
32
  spec.add_development_dependency 'benchmark-ips'
@@ -3,7 +3,7 @@ module EventSourcery
3
3
  # EventSourcery::AggregateRoot provides a foundation for writing your own aggregate root classes.
4
4
  # You can use it by including it in your classes, as show in the example code.
5
5
  #
6
- # Excerpt from {https://github.com/envato/event_sourcery/blob/master/docs/core-concepts.md EventSourcery Core Concepts} on Aggregates follows:
6
+ # Excerpt from {https://github.com/envato/event_sourcery/blob/HEAD/docs/core-concepts.md EventSourcery Core Concepts} on Aggregates follows:
7
7
  # === Aggregates and Command Handling
8
8
  #
9
9
  # An aggregate is a cluster of domain objects that can be treated as a single unit.
@@ -18,7 +18,7 @@ module EventSourcery
18
18
  # A typical EventSourcery application will have one or more aggregate roots with multiple commands.
19
19
  #
20
20
  # The following partial example is taken from the EventSourceryTodoApp.
21
- # Refer a more complete example {https://github.com/envato/event_sourcery_todo_app/blob/master/app/aggregates/todo.rb here}.
21
+ # Refer a more complete example {https://github.com/envato/event_sourcery_todo_app/blob/HEAD/app/aggregates/todo.rb here}.
22
22
  #
23
23
  # @example
24
24
  # module EventSourceryTodoApp
@@ -19,6 +19,14 @@ module EventSourcery
19
19
  # @return Proc
20
20
  attr_accessor :on_event_processor_error
21
21
 
22
+ # A Proc to be executed on an event processor critical error.
23
+ # App defined behaviour can be provided. This will be called
24
+ # if an exception causes an a event processor to die.
25
+ # i.e. report to an error reporting service like Rollbar.
26
+ #
27
+ # @return Proc
28
+ attr_accessor :on_event_processor_critical_error
29
+
22
30
  # @return EventStore::EventTypeSerializers::Underscored
23
31
  attr_accessor :event_type_serializer
24
32
 
@@ -40,6 +48,9 @@ module EventSourcery
40
48
  @on_event_processor_error = proc { |exception, processor_name|
41
49
  # app specific custom logic ie. report to an error reporting service like Rollbar.
42
50
  }
51
+ @on_event_processor_critical_error = proc { |exception, processor_name|
52
+ # app specific custom logic ie. report to an error reporting service like Rollbar.
53
+ }
43
54
  @event_builder = nil
44
55
  @event_type_serializer = EventStore::EventTypeSerializers::Underscored.new
45
56
  @error_handler_class = EventProcessing::ErrorHandlers::ConstantRetry
@@ -47,10 +58,10 @@ module EventSourcery
47
58
  end
48
59
 
49
60
  # Logger instance used by EventSourcery.
50
- # By default EventSourcery will log to STDOUT with a log level of Logger::DEBUG
61
+ # By default EventSourcery will log to STDOUT with a log level of Logger::INFO
51
62
  def logger
52
63
  @logger ||= ::Logger.new(STDOUT).tap do |logger|
53
- logger.level = Logger::DEBUG
64
+ logger.level = Logger::INFO
54
65
  end
55
66
  end
56
67
 
@@ -26,6 +26,7 @@ module EventSourcery
26
26
  rescue Exception => e
27
27
  EventSourcery.logger.fatal("An unhandled exception occurred in #{processor_name}")
28
28
  EventSourcery.logger.fatal(e)
29
+ EventSourcery.config.on_event_processor_critical_error.call(e, processor_name)
29
30
  raise e
30
31
  end
31
32
 
@@ -7,14 +7,18 @@ module EventSourcery
7
7
  event_source:,
8
8
  max_seconds_for_processes_to_terminate: 30,
9
9
  shutdown_requested: false,
10
- after_fork: nil)
10
+ after_fork: nil,
11
+ after_subprocess_termination: nil,
12
+ logger: EventSourcery.logger)
11
13
  @event_processors = event_processors
12
14
  @event_source = event_source
13
- @pids = []
15
+ @pids = {}
14
16
  @max_seconds_for_processes_to_terminate = max_seconds_for_processes_to_terminate
15
17
  @shutdown_requested = shutdown_requested
16
18
  @exit_status = true
17
19
  @after_fork = after_fork
20
+ @after_subprocess_termination = after_subprocess_termination
21
+ @logger = logger
18
22
  end
19
23
 
20
24
  # Start each Event Stream Processor in a new child process.
@@ -22,8 +26,9 @@ module EventSourcery
22
26
  with_logging do
23
27
  start_processes
24
28
  listen_for_shutdown_signals
25
- wait_till_shutdown_requested
26
- record_terminated_processes
29
+ while_waiting_for_shutdown do
30
+ record_terminated_processes
31
+ end
27
32
  terminate_remaining_processes
28
33
  until all_processes_terminated? || waited_long_enough?
29
34
  record_terminated_processes
@@ -34,25 +39,36 @@ module EventSourcery
34
39
  exit_indicating_status_of_processes
35
40
  end
36
41
 
42
+ def start_processor(event_processor)
43
+ process = ESPProcess.new(
44
+ event_processor: event_processor,
45
+ event_source: @event_source,
46
+ after_fork: @after_fork,
47
+ )
48
+ pid = Process.fork { process.start }
49
+ @pids[pid] = event_processor
50
+ end
51
+
52
+ def shutdown
53
+ @shutdown_requested = true
54
+ end
55
+
56
+ def shutdown_requested?
57
+ @shutdown_requested
58
+ end
59
+
37
60
  private
38
61
 
62
+ attr_reader :logger
63
+
39
64
  def with_logging
40
- EventSourcery.logger.info { 'Forking ESP processes' }
65
+ logger.info('ESPRunner: Forking processes')
41
66
  yield
42
- EventSourcery.logger.info { 'ESP processes shutdown' }
67
+ logger.info('ESPRunner: Processes shutdown')
43
68
  end
44
69
 
45
70
  def start_processes
46
- @event_processors.each(&method(:start_process))
47
- end
48
-
49
- def start_process(event_processor)
50
- process = ESPProcess.new(
51
- event_processor: event_processor,
52
- event_source: @event_source,
53
- after_fork: @after_fork,
54
- )
55
- @pids << Process.fork { process.start }
71
+ @event_processors.each(&method(:start_processor))
56
72
  end
57
73
 
58
74
  def listen_for_shutdown_signals
@@ -61,12 +77,12 @@ module EventSourcery
61
77
  end
62
78
  end
63
79
 
64
- def shutdown
65
- @shutdown_requested = true
66
- end
67
-
68
- def wait_till_shutdown_requested
69
- sleep(1) until @shutdown_requested
80
+ def while_waiting_for_shutdown
81
+ loop do
82
+ yield
83
+ break if shutdown_requested?
84
+ sleep(1)
85
+ end
70
86
  end
71
87
 
72
88
  def terminate_remaining_processes
@@ -77,19 +93,24 @@ module EventSourcery
77
93
  send_signal_to_remaining_processes(:KILL)
78
94
  end
79
95
 
80
-
81
96
  def send_signal_to_remaining_processes(signal)
82
- Process.kill(signal, *@pids) unless all_processes_terminated?
97
+ return if all_processes_terminated?
98
+
99
+ logger.info("ESPRunner: Sending #{signal} to [#{@pids.values.map(&:processor_name).join(', ')}]")
100
+ Process.kill(signal, *@pids.keys)
83
101
  rescue Errno::ESRCH
84
102
  record_terminated_processes
85
103
  retry
86
104
  end
87
105
 
88
106
  def record_terminated_processes
89
- until all_processes_terminated? ||
90
- ((pid, status) = Process.wait2(-1, Process::WNOHANG)).nil?
91
- @pids.delete(pid)
107
+ until all_processes_terminated? || (pid, status = Process.wait2(-1, Process::WNOHANG)).nil?
108
+ event_processor = @pids.delete(pid)
109
+ logger.info("ESPRunner: Process #{event_processor&.processor_name || pid} " \
110
+ "terminated with exit status: #{status.exitstatus.inspect}")
111
+ next unless event_processor
92
112
  @exit_status &&= !!status.success?
113
+ @after_subprocess_termination&.call(processor: event_processor, runner: self, exit_status: status.exitstatus)
93
114
  end
94
115
  end
95
116
 
@@ -5,10 +5,10 @@ module EventSourcery
5
5
  @event_type_serializer = event_type_serializer
6
6
  end
7
7
 
8
- def build(event_data)
8
+ def build(**event_data)
9
9
  type = event_data.fetch(:type)
10
10
  klass = event_type_serializer.deserialize(type)
11
- upcast(klass.new(event_data))
11
+ upcast(klass.new(**event_data))
12
12
  end
13
13
 
14
14
  private
@@ -212,9 +212,9 @@ RSpec.shared_examples 'an event store' do
212
212
  end
213
213
  end
214
214
 
215
- def events_by_range(*args)
215
+ def events_by_range(from_event_id, to_event_id, **args)
216
216
  [].tap do |events|
217
- event_store.each_by_range(*args) do |event|
217
+ event_store.each_by_range(from_event_id, to_event_id, **args) do |event|
218
218
  events << event
219
219
  end
220
220
  end
@@ -1,4 +1,4 @@
1
1
  module EventSourcery
2
2
  # Defines the version
3
- VERSION = '0.21.0'.freeze
3
+ VERSION = '0.24.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_sourcery
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Envato
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-01 00:00:00.000000000 Z
11
+ date: 2021-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.10'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13'
34
34
  type: :development
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: '10.0'
40
+ version: '13'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -127,7 +127,7 @@ homepage: https://github.com/envato/event_sourcery
127
127
  licenses: []
128
128
  metadata:
129
129
  bug_tracker_uri: https://github.com/envato/event_sourcery/issues
130
- changelog_uri: https://github.com/envato/event_sourcery/blob/master/CHANGELOG.md
130
+ changelog_uri: https://github.com/envato/event_sourcery/blob/HEAD/CHANGELOG.md
131
131
  source_code_uri: https://github.com/envato/event_sourcery
132
132
  post_install_message:
133
133
  rdoc_options: []
@@ -144,8 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
146
  requirements: []
147
- rubyforge_project:
148
- rubygems_version: 2.7.3
147
+ rubygems_version: 3.0.3
149
148
  signing_key:
150
149
  specification_version: 4
151
150
  summary: Event Sourcing Library