event_sourcery 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +82 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +399 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +6 -0
  12. data/bin/setup +15 -0
  13. data/event_sourcery.gemspec +28 -0
  14. data/lib/event_sourcery.rb +49 -0
  15. data/lib/event_sourcery/aggregate_root.rb +68 -0
  16. data/lib/event_sourcery/config.rb +43 -0
  17. data/lib/event_sourcery/errors.rb +19 -0
  18. data/lib/event_sourcery/event.rb +49 -0
  19. data/lib/event_sourcery/event_body_serializer.rb +42 -0
  20. data/lib/event_sourcery/event_processing/error_handlers/constant_retry.rb +23 -0
  21. data/lib/event_sourcery/event_processing/error_handlers/error_handler.rb +20 -0
  22. data/lib/event_sourcery/event_processing/error_handlers/exponential_backoff_retry.rb +40 -0
  23. data/lib/event_sourcery/event_processing/error_handlers/no_retry.rb +19 -0
  24. data/lib/event_sourcery/event_processing/esp_process.rb +41 -0
  25. data/lib/event_sourcery/event_processing/esp_runner.rb +105 -0
  26. data/lib/event_sourcery/event_processing/event_stream_processor.rb +125 -0
  27. data/lib/event_sourcery/event_processing/event_stream_processor_registry.rb +29 -0
  28. data/lib/event_sourcery/event_store/each_by_range.rb +25 -0
  29. data/lib/event_sourcery/event_store/event_builder.rb +19 -0
  30. data/lib/event_sourcery/event_store/event_sink.rb +18 -0
  31. data/lib/event_sourcery/event_store/event_source.rb +21 -0
  32. data/lib/event_sourcery/event_store/event_type_serializers/class_name.rb +19 -0
  33. data/lib/event_sourcery/event_store/event_type_serializers/legacy.rb +17 -0
  34. data/lib/event_sourcery/event_store/event_type_serializers/underscored.rb +68 -0
  35. data/lib/event_sourcery/event_store/poll_waiter.rb +18 -0
  36. data/lib/event_sourcery/event_store/signal_handling_subscription_master.rb +22 -0
  37. data/lib/event_sourcery/event_store/subscription.rb +43 -0
  38. data/lib/event_sourcery/memory/event_store.rb +76 -0
  39. data/lib/event_sourcery/memory/tracker.rb +27 -0
  40. data/lib/event_sourcery/repository.rb +31 -0
  41. data/lib/event_sourcery/rspec/event_store_shared_examples.rb +352 -0
  42. data/lib/event_sourcery/version.rb +3 -0
  43. metadata +158 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "event_sourcery"
5
+ require "pry"
6
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ echo
6
+ echo "--- Bundling"
7
+ echo
8
+
9
+ bundle install
10
+
11
+ echo
12
+ echo "--- Preparing databases"
13
+ echo
14
+
15
+ createdb event_sourcery_test
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'event_sourcery/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'event_sourcery'
8
+ spec.version = EventSourcery::VERSION
9
+ spec.authors = ['Steve Hodgkiss', 'Tao Guo', 'Sebastian von Conrad']
10
+ spec.email = ['steve@hodgkiss.me', 'tao.guo@envato.com', 'sebastian.von.conrad@envato.com']
11
+
12
+ spec.summary = 'Event Sourcing Library'
13
+ spec.description = ''
14
+ spec.homepage = 'https://github.com/envato/event_sourcery'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 2.2.0'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.10'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'benchmark-ips'
28
+ end
@@ -0,0 +1,49 @@
1
+ require 'json'
2
+ require 'securerandom'
3
+ require 'time'
4
+
5
+ require 'event_sourcery/version'
6
+ require 'event_sourcery/event'
7
+ require 'event_sourcery/event_store/event_sink'
8
+ require 'event_sourcery/event_store/event_source'
9
+ require 'event_sourcery/errors'
10
+ require 'event_sourcery/event_store/each_by_range'
11
+ require 'event_sourcery/event_store/subscription'
12
+ require 'event_sourcery/event_store/poll_waiter'
13
+ require 'event_sourcery/event_store/event_builder'
14
+ require 'event_sourcery/event_store/event_type_serializers/class_name'
15
+ require 'event_sourcery/event_store/event_type_serializers/legacy'
16
+ require 'event_sourcery/event_store/event_type_serializers/underscored'
17
+ require 'event_sourcery/event_store/signal_handling_subscription_master'
18
+ require 'event_sourcery/event_processing/error_handlers/error_handler'
19
+ require 'event_sourcery/event_processing/error_handlers/no_retry'
20
+ require 'event_sourcery/event_processing/error_handlers/constant_retry'
21
+ require 'event_sourcery/event_processing/error_handlers/exponential_backoff_retry'
22
+ require 'event_sourcery/event_processing/esp_process'
23
+ require 'event_sourcery/event_processing/esp_runner'
24
+ require 'event_sourcery/event_processing/event_stream_processor'
25
+ require 'event_sourcery/event_processing/event_stream_processor_registry'
26
+ require 'event_sourcery/config'
27
+ require 'event_sourcery/event_body_serializer'
28
+ require 'event_sourcery/aggregate_root'
29
+ require 'event_sourcery/repository'
30
+ require 'event_sourcery/memory/tracker'
31
+ require 'event_sourcery/memory/event_store'
32
+
33
+ module EventSourcery
34
+ def self.configure
35
+ yield config
36
+ end
37
+
38
+ def self.config
39
+ @config ||= Config.new
40
+ end
41
+
42
+ def self.logger
43
+ config.logger
44
+ end
45
+
46
+ def self.event_stream_processor_registry
47
+ @event_stream_processor_registry ||= EventProcessing::EventStreamProcessorRegistry.new
48
+ end
49
+ end
@@ -0,0 +1,68 @@
1
+ module EventSourcery
2
+ module AggregateRoot
3
+ UnknownEventError = Class.new(RuntimeError)
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.class_eval do
8
+ @event_handlers = Hash.new { |hash, key| hash[key] = [] }
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ attr_reader :event_handlers
14
+
15
+ def apply(*event_classes, &block)
16
+ event_classes.each do |event_class|
17
+ @event_handlers[event_class.type] << block
18
+ end
19
+ end
20
+ end
21
+
22
+ def initialize(id, events, on_unknown_event: EventSourcery.config.on_unknown_event)
23
+ @id = id.to_str
24
+ @version = 0
25
+ @on_unknown_event = on_unknown_event
26
+ @changes = []
27
+ load_history(events)
28
+ end
29
+
30
+ attr_reader :changes, :version
31
+
32
+ def clear_changes
33
+ @changes.clear
34
+ end
35
+
36
+ private
37
+
38
+ def load_history(events)
39
+ events.each do |event|
40
+ mutate_state_from(event)
41
+ end
42
+ end
43
+
44
+ attr_reader :id
45
+
46
+ def apply_event(event_class, options = {})
47
+ event = event_class.new(**options.merge(aggregate_id: id))
48
+ mutate_state_from(event)
49
+ @changes << event
50
+ end
51
+
52
+ def mutate_state_from(event)
53
+ handlers = self.class.event_handlers[event.type]
54
+ if handlers.any?
55
+ handlers.each do |handler|
56
+ instance_exec(event, &handler)
57
+ end
58
+ else
59
+ @on_unknown_event.call(event, self)
60
+ end
61
+ increment_version
62
+ end
63
+
64
+ def increment_version
65
+ @version += 1
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ require 'logger'
2
+
3
+ module EventSourcery
4
+ class Config
5
+ attr_accessor :on_unknown_event,
6
+ :on_event_processor_error,
7
+ :event_type_serializer,
8
+ :error_handler_class
9
+
10
+ attr_writer :logger,
11
+ :event_body_serializer,
12
+ :event_builder
13
+
14
+ def initialize
15
+ @on_unknown_event = proc { |event, aggregate|
16
+ raise AggregateRoot::UnknownEventError, "#{event.type} is unknown to #{aggregate.class.name}"
17
+ }
18
+ @on_event_processor_error = proc { |exception, processor_name|
19
+ # app specific custom logic ie. report to rollbar
20
+ }
21
+ @event_store = nil
22
+ @event_type_serializer = EventStore::EventTypeSerializers::Underscored.new
23
+ @error_handler_class = EventProcessing::ErrorHandlers::ConstantRetry
24
+ end
25
+
26
+ def logger
27
+ @logger ||= ::Logger.new(STDOUT).tap do |logger|
28
+ logger.level = Logger::DEBUG
29
+ end
30
+ end
31
+
32
+ def event_builder
33
+ @event_builder || EventStore::EventBuilder.new(event_type_serializer: @event_type_serializer)
34
+ end
35
+
36
+ def event_body_serializer
37
+ @event_body_serializer ||= EventBodySerializer.new
38
+ .add(Hash, EventBodySerializer::HashSerializer)
39
+ .add(Array, EventBodySerializer::ArraySerializer)
40
+ .add(Time, &:iso8601)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module EventSourcery
2
+ Error = Class.new(StandardError)
3
+ UnableToLockProcessorError = Class.new(Error)
4
+ UnableToProcessEventError = Class.new(Error)
5
+ ConcurrencyError = Class.new(Error)
6
+ AtomicWriteToMultipleAggregatesNotSupported = Class.new(Error)
7
+
8
+ class EventProcessingError < Error
9
+ attr_reader :event
10
+
11
+ def initialize(event)
12
+ @event = event
13
+ end
14
+
15
+ def message
16
+ cause.message if cause
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ module EventSourcery
2
+ class Event
3
+ include Comparable
4
+
5
+ def self.type
6
+ unless self == Event
7
+ EventSourcery.config.event_type_serializer.serialize(self)
8
+ end
9
+ end
10
+
11
+ attr_reader :id, :uuid, :aggregate_id, :type, :body, :version, :created_at, :correlation_id, :causation_id
12
+
13
+ def initialize(id: nil,
14
+ uuid: SecureRandom.uuid,
15
+ aggregate_id: nil,
16
+ type: nil,
17
+ body: nil,
18
+ version: nil,
19
+ created_at: nil,
20
+ correlation_id: nil,
21
+ causation_id: nil)
22
+ @id = id
23
+ @uuid = uuid && uuid.downcase
24
+ @aggregate_id = aggregate_id
25
+ @type = self.class.type || type.to_s
26
+ @body = body ? EventSourcery::EventBodySerializer.serialize(body) : {}
27
+ @version = version ? Integer(version) : nil
28
+ @created_at = created_at
29
+ @correlation_id = correlation_id
30
+ @causation_id = causation_id
31
+ end
32
+
33
+ def persisted?
34
+ !id.nil?
35
+ end
36
+
37
+ def hash
38
+ [self.class, uuid].hash
39
+ end
40
+
41
+ def eql?(other)
42
+ instance_of?(other.class) && uuid.eql?(other.uuid)
43
+ end
44
+
45
+ def <=>(other)
46
+ id <=> other.id if other.is_a? Event
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+ module EventSourcery
2
+ class EventBodySerializer
3
+ def self.serialize(event_body,
4
+ serializer: EventSourcery.config.event_body_serializer)
5
+ serializer.serialize(event_body)
6
+ end
7
+
8
+ def initialize
9
+ @serializers = Hash.new(IdentitySerializer)
10
+ end
11
+
12
+ def add(type, serializer = nil, &block_serializer)
13
+ @serializers[type] = serializer || block_serializer
14
+ self
15
+ end
16
+
17
+ def serialize(object)
18
+ serializer = @serializers[object.class]
19
+ serializer.call(object, &method(:serialize))
20
+ end
21
+
22
+ module HashSerializer
23
+ def self.call(hash, &serialize)
24
+ hash.each_with_object({}) do |(key, value), memo|
25
+ memo[key.to_s] = serialize.call(value)
26
+ end
27
+ end
28
+ end
29
+
30
+ module ArraySerializer
31
+ def self.call(array, &serialize)
32
+ array.map(&serialize)
33
+ end
34
+ end
35
+
36
+ module IdentitySerializer
37
+ def self.call(object)
38
+ object
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module EventSourcery
2
+ module EventProcessing
3
+ module ErrorHandlers
4
+ class ConstantRetry
5
+ include EventSourcery::EventProcessing::ErrorHandlers::ErrorHandler
6
+ DEFAULT_RETRY_INVERAL = 1
7
+ def initialize(processor_name:)
8
+ @processor_name = processor_name
9
+ @retry_interval = DEFAULT_RETRY_INVERAL
10
+ end
11
+
12
+ def with_error_handling
13
+ yield
14
+ rescue => error
15
+ report_error(error)
16
+ sleep(@retry_interval)
17
+
18
+ retry
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module EventSourcery
2
+ module EventProcessing
3
+ module ErrorHandlers
4
+ module ErrorHandler
5
+ def with_error_handling
6
+ raise NotImplementedError, 'Please implement #with_error_handling method'
7
+ end
8
+
9
+ private
10
+
11
+ def report_error(error)
12
+ error = error.cause if error.instance_of?(EventSourcery::EventProcessingError)
13
+ EventSourcery.logger.error("Processor #{@processor_name} died with #{error}.\n#{error.backtrace.join("\n")}")
14
+
15
+ EventSourcery.config.on_event_processor_error.call(error, @processor_name)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ module EventSourcery
2
+ module EventProcessing
3
+ module ErrorHandlers
4
+ class ExponentialBackoffRetry
5
+ include EventSourcery::EventProcessing::ErrorHandlers::ErrorHandler
6
+ DEFAULT_RETRY_INVERAL = 1
7
+ MAX_RETRY_INVERVAL = 64
8
+
9
+ def initialize(processor_name:)
10
+ @processor_name = processor_name
11
+ @retry_interval = DEFAULT_RETRY_INVERAL
12
+ end
13
+
14
+ def with_error_handling
15
+ yield
16
+ rescue => error
17
+ report_error(error)
18
+ update_retry_interval(error)
19
+ sleep(@retry_interval)
20
+ retry
21
+ end
22
+
23
+ private
24
+
25
+ def update_retry_interval(error)
26
+ if error.instance_of?(EventSourcery::EventProcessingError)
27
+ if @error_event_uuid == error.event.uuid
28
+ @retry_interval *= 2 if @retry_interval < MAX_RETRY_INVERVAL
29
+ else
30
+ @error_event_uuid = error.event.uuid
31
+ @retry_interval = DEFAULT_RETRY_INVERAL
32
+ end
33
+ else
34
+ @retry_interval = DEFAULT_RETRY_INVERAL
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module EventSourcery
2
+ module EventProcessing
3
+ module ErrorHandlers
4
+ class NoRetry
5
+ include EventSourcery::EventProcessing::ErrorHandlers::ErrorHandler
6
+ def initialize(processor_name:)
7
+ @processor_name = processor_name
8
+ end
9
+
10
+ def with_error_handling
11
+ yield
12
+ rescue => error
13
+ report_error(error)
14
+ Process.exit(false)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end