sequent 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequent/core/aggregate_repository.rb +94 -0
  3. data/lib/sequent/core/aggregate_root.rb +87 -0
  4. data/lib/sequent/core/base_command_handler.rb +39 -0
  5. data/lib/sequent/core/base_event_handler.rb +51 -0
  6. data/lib/sequent/core/command.rb +79 -0
  7. data/lib/sequent/core/command_record.rb +26 -0
  8. data/lib/sequent/core/command_service.rb +118 -0
  9. data/lib/sequent/core/core.rb +15 -0
  10. data/lib/sequent/core/event.rb +62 -0
  11. data/lib/sequent/core/event_record.rb +34 -0
  12. data/lib/sequent/core/event_store.rb +110 -0
  13. data/lib/sequent/core/helpers/association_validator.rb +39 -0
  14. data/lib/sequent/core/helpers/attribute_support.rb +207 -0
  15. data/lib/sequent/core/helpers/boolean_support.rb +36 -0
  16. data/lib/sequent/core/helpers/copyable.rb +25 -0
  17. data/lib/sequent/core/helpers/equal_support.rb +41 -0
  18. data/lib/sequent/core/helpers/helpers.rb +9 -0
  19. data/lib/sequent/core/helpers/mergable.rb +21 -0
  20. data/lib/sequent/core/helpers/param_support.rb +80 -0
  21. data/lib/sequent/core/helpers/self_applier.rb +45 -0
  22. data/lib/sequent/core/helpers/string_support.rb +22 -0
  23. data/lib/sequent/core/helpers/uuid_helper.rb +17 -0
  24. data/lib/sequent/core/record_sessions/active_record_session.rb +92 -0
  25. data/lib/sequent/core/record_sessions/record_sessions.rb +2 -0
  26. data/lib/sequent/core/record_sessions/replay_events_session.rb +306 -0
  27. data/lib/sequent/core/tenant_event_store.rb +18 -0
  28. data/lib/sequent/core/transactions/active_record_transaction_provider.rb +16 -0
  29. data/lib/sequent/core/transactions/no_transactions.rb +13 -0
  30. data/lib/sequent/core/transactions/transactions.rb +2 -0
  31. data/lib/sequent/core/value_object.rb +48 -0
  32. data/lib/sequent/migrations/migrate_events.rb +53 -0
  33. data/lib/sequent/migrations/migrations.rb +7 -0
  34. data/lib/sequent/sequent.rb +3 -0
  35. data/lib/sequent/test/command_handler_helpers.rb +101 -0
  36. data/lib/sequent/test/test.rb +1 -0
  37. data/lib/version.rb +3 -0
  38. metadata +38 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ae011c7edda7f721ed6973ee7dc7d34a1bd4bef1
4
- data.tar.gz: 29349622be7751d7d371ad9c56232aaa880e7aa1
3
+ metadata.gz: ffdbe1d8e772c148815f98b0d7baa96f32dd5870
4
+ data.tar.gz: 3b7ed01b18a23c5d7b8522a4b5d0c1af311be02f
5
5
  SHA512:
6
- metadata.gz: 5b832a64d8daaaf0008426f7acd40489934ff95ec666feac04751602305cf404d0b6f2b6a2dc0c89aa504de1bc5bcc468788d22f3f1f3a57240e99658fc01bfb
7
- data.tar.gz: 77e7beb17a0e7341d0cff134c03a6428ceec7fcb64266cf230058c493a37fe766713a2bd23bdf20095eeb14a90ede3cc2c0abfa5a49502e7f149bf213d8f7b04
6
+ metadata.gz: 5ec2840094aa3f915222047623d3f570563efa800736883c9d07b724773b0948b74602d26560337bb20455fde183a066181cbe594959ca52ebd1d73fea6a0f9e
7
+ data.tar.gz: 60ac65d32eda16ce7db21cb51ba0a61b6b741b86636185569b1683c76a983c35d6ca806a744befdd195df997ecb3b8df481fd17a15e312d648a7fa816d3127a7
@@ -0,0 +1,94 @@
1
+ module Sequent
2
+ module Core
3
+ # Repository for aggregates.
4
+ #
5
+ # Implements the Unit-Of-Work and Identity-Map patterns
6
+ # to ensure each aggregate is only loaded once per transaction
7
+ # and that you always get the same aggregate instance back.
8
+ #
9
+ # On commit all aggregates associated with the Unit-Of-Work are
10
+ # queried for uncommitted events. After persisting these events
11
+ # the uncommitted events are cleared from the aggregate.
12
+ #
13
+ # The repository is keeps track of the Unit-Of-Work per thread,
14
+ # so can be shared between threads.
15
+ class AggregateRepository
16
+ # Key used in thread local
17
+ AGGREGATES_KEY = 'Sequent::Core::AggregateRepository::aggregates'.to_sym
18
+
19
+ class NonUniqueAggregateId < Exception
20
+ def initialize(existing, new)
21
+ super "Duplicate aggregate #{new} with same key as existing #{existing}"
22
+ end
23
+ end
24
+
25
+ def initialize(event_store)
26
+ @event_store = event_store
27
+ clear
28
+ end
29
+
30
+ # Adds the given aggregate to the repository (or unit of work).
31
+ #
32
+ # Only when +commit+ is called all aggregates in the unit of work are 'processed'
33
+ # and all uncammited_events are stored in the +event_store+
34
+ #
35
+ def add_aggregate(aggregate)
36
+ if aggregates.has_key?(aggregate.id)
37
+ raise NonUniqueAggregateId.new(aggregate, aggregates[aggregate.id]) unless aggregates[aggregate.id].equal?(aggregate)
38
+ else
39
+ aggregates[aggregate.id] = aggregate
40
+ end
41
+ end
42
+
43
+ # Throws exception if not exists.
44
+ def ensure_exists(aggregate_id, clazz)
45
+ !load_aggregate(aggregate_id, clazz).nil?
46
+ end
47
+
48
+ # Loads aggregate by given id and class
49
+ # Returns the one in the current Unit Of Work otherwise loads it from history.
50
+ #
51
+ # If we implement snapshotting this is the place.
52
+ def load_aggregate(aggregate_id, clazz)
53
+ if aggregates.has_key?(aggregate_id)
54
+ result = aggregates[aggregate_id]
55
+ raise TypeError, "#{result.class} is not a #{clazz}" unless result.is_a?(clazz)
56
+ result
57
+ else
58
+ events = @event_store.load_events(aggregate_id)
59
+ aggregates[aggregate_id] = clazz.load_from_history(events)
60
+ end
61
+ end
62
+
63
+ # Gets all uncommitted_events from the 'registered' aggregates
64
+ # and stores them in the event store.
65
+ # The command is 'attached' for traceability purpose so we can see
66
+ # which command resulted in which events.
67
+ #
68
+ # This is all abstracted away if you use the Sequent::Core::CommandService
69
+ #
70
+ def commit(command)
71
+ all_events = []
72
+ aggregates.each_value { |aggregate| all_events += aggregate.uncommitted_events }
73
+ return if all_events.empty?
74
+ aggregates.each_value { |aggregate| aggregate.clear_events }
75
+ store_events command, all_events
76
+ end
77
+
78
+ # Clears the Unit of Work.
79
+ def clear
80
+ Thread.current[AGGREGATES_KEY] = {}
81
+ end
82
+
83
+ private
84
+
85
+ def aggregates
86
+ Thread.current[AGGREGATES_KEY]
87
+ end
88
+
89
+ def store_events(command, events)
90
+ @event_store.commit_events(command, events)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,87 @@
1
+ require_relative 'helpers/self_applier'
2
+
3
+ module Sequent
4
+ module Core
5
+ # Base class for all your domain classes.
6
+ #
7
+ # +load_from_history+ functionality to be loaded_from_history, meaning a stream of events.
8
+ #
9
+ class AggregateRoot
10
+ include Helpers::SelfApplier
11
+
12
+ attr_reader :id, :uncommitted_events, :sequence_number
13
+
14
+ def self.load_from_history(events)
15
+ aggregate_root = allocate() # allocate without calling new
16
+ aggregate_root.load_from_history(events)
17
+ aggregate_root
18
+ end
19
+
20
+ def initialize(id)
21
+ @id = id
22
+ @uncommitted_events = []
23
+ @sequence_number = 1
24
+ end
25
+
26
+ def load_from_history(events)
27
+ raise "Empty history" if events.empty?
28
+ @id = events.first.aggregate_id
29
+ @uncommitted_events = []
30
+ @sequence_number = events.size + 1
31
+ events.each { |event| handle_message(event) }
32
+ end
33
+
34
+ def to_s
35
+ "#{self.class.name}: #{@id}"
36
+ end
37
+
38
+
39
+ def clear_events
40
+ uncommitted_events.clear
41
+ end
42
+
43
+ protected
44
+
45
+ def build_event(event, params = {})
46
+ event.new({aggregate_id: @id, sequence_number: @sequence_number}.merge(params))
47
+ end
48
+
49
+ # Provide subclasses nice DSL to 'apply' events via:
50
+ #
51
+ # def send_invoice
52
+ # apply InvoiceSentEvent, send_date: DateTime.now
53
+ # end
54
+ #
55
+ def apply(event, params={})
56
+ event = build_event(event, params) if event.is_a?(Class)
57
+ handle_message(event)
58
+ @uncommitted_events << event
59
+ @sequence_number += 1
60
+ end
61
+ end
62
+
63
+ # You can use this class when running in a multi tenant environment
64
+ # It basically makes sure that the +organization_id+ (the tenant_id for historic reasons)
65
+ # is available for the subclasses
66
+ class TenantAggregateRoot < AggregateRoot
67
+ attr_reader :organization_id
68
+
69
+ def initialize(id, organization_id)
70
+ super(id)
71
+ @organization_id = organization_id
72
+ end
73
+
74
+ def load_from_history(events)
75
+ raise "Empty history" if events.empty?
76
+ @organization_id = events.first.organization_id
77
+ super(events)
78
+ end
79
+
80
+ protected
81
+
82
+ def build_event(event, params = {})
83
+ super(event, {organization_id: @organization_id}.merge(params))
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'helpers/self_applier'
2
+ require_relative 'helpers/uuid_helper'
3
+
4
+ module Sequent
5
+ module Core
6
+ # Base class for command handlers
7
+ # CommandHandlers are responsible for propagating a command to the correct Sequent::Core::AggregateRoot
8
+ # or creating a new one. For example:
9
+ #
10
+ # class InvoiceCommandHandler < Sequent::Core::BaseCommandHandler
11
+ # on CreateInvoiceCommand do |command|
12
+ # repository.add_aggregate Invoice.new(command.aggregate_id)
13
+ # end
14
+ #
15
+ # on PayInvoiceCommanddo |command|
16
+ # do_with_aggregate(command, Invoice) {|invoice|invoice.pay(command.pay_date)}
17
+ # end
18
+ # end
19
+ class BaseCommandHandler
20
+ include Sequent::Core::Helpers::SelfApplier,
21
+ Sequent::Core::Helpers::UuidHelper
22
+
23
+ def initialize(repository)
24
+ @repository = repository
25
+ end
26
+
27
+ protected
28
+ def do_with_aggregate(command, clazz, aggregate_id = nil)
29
+ aggregate = @repository.load_aggregate(aggregate_id.nil? ? command.aggregate_id : aggregate_id, clazz)
30
+ yield aggregate if block_given?
31
+ end
32
+
33
+ protected
34
+ def repository
35
+ @repository
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'helpers/self_applier'
2
+
3
+ module Sequent
4
+ module Core
5
+ # EventHandlers listen to events and handle them according to their responsibility.
6
+ #
7
+ # Examples:
8
+ # * Updating view states
9
+ # * Sending emails
10
+ # * Executing other commands based on events (chainging)
11
+ #
12
+ # Example of updating view state, in this case the InvoiceRecord table representing an Invoice
13
+ #
14
+ # class InvoiceCommandHandler < Sequent::Core::BaseCommandHandler
15
+ # on CreateInvoiceCommand do |command|
16
+ # create_record(
17
+ # InvoiceRecord,
18
+ # recipient: command.recipient,
19
+ # amount: command.amount
20
+ # )
21
+ # end
22
+ # end
23
+ #
24
+ # Please note that the actual storage is abstracted away in the +record_session+. Reason
25
+ # is when replaying the entire event_store our default choice, active_record, is too slow.
26
+ # Also we want to give the opportunity to use other storage mechanisms for the view state.
27
+ # See the +def_delegators+ which method to implement.
28
+ # Due to this abstraction you can not traverse into child objects when using ActiveRecord
29
+ # like you are used to:
30
+ #
31
+ # invoice_record.line_item_records << create_record(LineItemRecord, ...)
32
+ #
33
+ # In this case you should simply do:
34
+ #
35
+ # create_record(LineItemRecord, invoice_id: invoice_record.aggregate_id)
36
+ #
37
+ class BaseEventHandler
38
+ extend Forwardable
39
+ include Helpers::SelfApplier
40
+
41
+ def initialize(record_session = Sequent::Core::RecordSessions::ActiveRecordSession.new)
42
+ @record_session = record_session
43
+ end
44
+
45
+ def_delegators :@record_session, :update_record, :create_record, :create_or_update_record, :get_record!, :get_record,
46
+ :delete_all_records, :update_all_records, :do_with_records, :do_with_record, :delete_record,
47
+ :find_records, :last_record
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,79 @@
1
+ require_relative 'helpers/copyable'
2
+ require_relative 'helpers/attribute_support'
3
+ require_relative 'helpers/uuid_helper'
4
+ require_relative 'helpers/equal_support'
5
+ require_relative 'helpers/param_support'
6
+ require_relative 'helpers/mergable'
7
+
8
+ module Sequent
9
+ module Core
10
+ # Base command
11
+ class BaseCommand
12
+ include ActiveModel::Validations,
13
+ ActiveModel::Serializers::JSON,
14
+ Sequent::Core::Helpers::Copyable,
15
+ Sequent::Core::Helpers::AttributeSupport,
16
+ Sequent::Core::Helpers::UuidHelper,
17
+ Sequent::Core::Helpers::EqualSupport,
18
+ Sequent::Core::Helpers::ParamSupport,
19
+ Sequent::Core::Helpers::Mergable
20
+
21
+ attrs created_at: DateTime
22
+
23
+ self.include_root_in_json = false
24
+
25
+ def initialize(args = {})
26
+ update_all_attributes args
27
+ @created_at = DateTime.now
28
+ end
29
+
30
+ end
31
+
32
+ module UpdateSequenceNumber
33
+ extend ActiveSupport::Concern
34
+ included do
35
+ attrs sequence_number: Integer
36
+ validates_presence_of :sequence_number
37
+ validates_numericality_of :sequence_number, only_integer: true, allow_nil: true, allow_blank: true, greater_than: 0
38
+ end
39
+ end
40
+
41
+ # Most commonly used command
42
+ # Command can be instantiated just by using:
43
+ #
44
+ # Command.new(aggregate_id: "1", user_id: "joe")
45
+ #
46
+ # But the Sequent::Core::Helpers::ParamSupport also enables Commands
47
+ # to be created from a params hash (like the one from Sinatra) as follows:
48
+ #
49
+ # command = Command.from_params(params)
50
+ #
51
+ class Command < BaseCommand
52
+ attrs aggregate_id: String, user_id: String
53
+
54
+ def initialize(args = {})
55
+ raise ArgumentError, "Missing aggregate_id" if args[:aggregate_id].nil?
56
+ super
57
+ end
58
+
59
+ end
60
+
61
+ class UpdateCommand < Command
62
+ include UpdateSequenceNumber
63
+ end
64
+
65
+ class TenantCommand < Command
66
+ attrs organization_id: String
67
+
68
+ def initialize(args = {})
69
+ raise ArgumentError, "Missing organization_id" if args[:organization_id].nil?
70
+ super
71
+ end
72
+ end
73
+
74
+ class UpdateTenantCommand < TenantCommand
75
+ include UpdateSequenceNumber
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_record'
2
+ module Sequent
3
+ module Core
4
+ # For storing Sequent::Core::Command in the database using active_record
5
+ class CommandRecord < ActiveRecord::Base
6
+
7
+ self.table_name = "command_records"
8
+
9
+ validates_presence_of :command_type, :command_json
10
+
11
+ def command
12
+ args = JSON.parse(command_json)
13
+ Class.const_get(command_type.to_sym).deserialize_from_json(args)
14
+ end
15
+
16
+ def command=(command)
17
+ self.created_at = command.created_at
18
+ self.aggregate_id = command.aggregate_id if command.respond_to? :aggregate_id
19
+ self.organization_id = command.organization_id if command.respond_to? :organization_id
20
+ self.user_id = command.user_id if command.respond_to? :user_id
21
+ self.command_type = command.class.name
22
+ self.command_json = command.to_json.to_s
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,118 @@
1
+ require_relative 'transactions/no_transactions'
2
+
3
+ module Sequent
4
+ module Core
5
+
6
+ class CommandServiceConfiguration
7
+ attr_accessor :event_store,
8
+ :command_handler_classes,
9
+ :transaction_provider,
10
+ :filters
11
+
12
+ def initialize
13
+ @command_handler_classes = []
14
+ @transaction_provider = Sequent::Core::Transactions::NoTransactions.new
15
+ @filters = []
16
+ end
17
+
18
+ end
19
+
20
+ #
21
+ # Single point in the application where subclasses of Sequent::Core::BaseCommand
22
+ # are executed. This will initiate the entire flow of:
23
+ #
24
+ # * Validate command
25
+ # * Call correct Sequent::Core::BaseCommandHandler
26
+ # * CommandHandler decides which Sequent::Core::AggregateRoot (s) to call
27
+ # * Events are stored in the Sequent::Core::EventStore
28
+ # * Unit of Work is cleared
29
+ #
30
+ class CommandService
31
+
32
+ class << self
33
+ attr_accessor :configuration,
34
+ :instance
35
+ end
36
+
37
+ # Creates a new CommandService and overwrites all existing config.
38
+ # The new CommandService can be retrieved via the +CommandService.instance+ method.
39
+ #
40
+ # If you don't want a singleton you can always instantiate it yourself using the +CommandService.new+.
41
+ def self.configure
42
+ self.configuration = CommandServiceConfiguration.new
43
+ yield(configuration) if block_given?
44
+ self.instance = CommandService.new(configuration)
45
+ end
46
+
47
+ # +DefaultCommandServiceConfiguration+ Configuration class for the CommandService containing:
48
+ #
49
+ # +event_store+ The Sequent::Core::EventStore
50
+ # +command_handler_classes+ Array of BaseCommandHandler classes that need to handle commands
51
+ # +transaction_provider+ How to do transaction management. Defaults to Sequent::Core::Transactions::NoTransactions
52
+ # +filters+ List of filter that respond_to :execute(command). Can be useful to do extra checks (security and such).
53
+ def initialize(configuration = CommandServiceConfiguration.new)
54
+ @event_store = configuration.event_store
55
+ @repository = AggregateRepository.new(configuration.event_store)
56
+ @filters = configuration.filters
57
+ @transaction_provider = configuration.transaction_provider
58
+ @command_handlers = configuration.command_handler_classes.map { |handler| handler.new(@repository) }
59
+ end
60
+
61
+ # Executes the given commands in a single transactional block as implemented by the +transaction_provider+
62
+ #
63
+ # For each command:
64
+ #
65
+ # * All filters are executed. Any exception raised will rollback the transaction and propagate up
66
+ # * If the command is valid all +command_handlers+ that +handles_message?+ is invoked
67
+ # * The +repository+ commits the command and all uncommitted_events resulting from the command
68
+ def execute_commands(*commands)
69
+ begin
70
+ @transaction_provider.transactional do
71
+ commands.each do |command|
72
+ @filters.each { |filter| filter.execute(command) }
73
+
74
+ if command.valid?
75
+ @command_handlers.each do |command_handler|
76
+ command_handler.handle_message command if command_handler.handles_message? command
77
+ end
78
+ end
79
+
80
+ @repository.commit(command)
81
+ raise CommandNotValid.new(command) unless command.validation_errors.empty?
82
+ end
83
+ end
84
+ ensure
85
+ @repository.clear
86
+ end
87
+
88
+ end
89
+
90
+ def remove_event_handler(clazz)
91
+ @event_store.remove_event_handler(clazz)
92
+ end
93
+
94
+ end
95
+
96
+ # Raised when BaseCommand.valid? returns false
97
+ class CommandNotValid < ArgumentError
98
+
99
+ def initialize(command)
100
+ @command = command
101
+ msg = @command.respond_to?(:aggregate_id) ? " #{@command.aggregate_id}" : ""
102
+ super "Invalid command #{@command.class.to_s}#{msg}, errors: #{@command.validation_errors}"
103
+ end
104
+
105
+ def errors(prefix = nil)
106
+ @command.validation_errors(prefix)
107
+ end
108
+
109
+ def errors_with_command_prefix
110
+ errors(@command.class.to_s.underscore)
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+
118
+
@@ -0,0 +1,15 @@
1
+ require_relative 'helpers/helpers'
2
+ require_relative 'record_sessions/record_sessions'
3
+ require_relative 'transactions/transactions'
4
+ require_relative 'aggregate_repository'
5
+ require_relative 'aggregate_root'
6
+ require_relative 'base_command_handler'
7
+ require_relative 'command'
8
+ require_relative 'command_service'
9
+ require_relative 'event'
10
+ require_relative 'value_object'
11
+ require_relative 'base_event_handler'
12
+ require_relative 'event_store'
13
+ require_relative 'tenant_event_store'
14
+ require_relative 'event_record'
15
+ require_relative 'command_record'
@@ -0,0 +1,62 @@
1
+ require 'active_model'
2
+ require_relative 'helpers/string_support'
3
+ require_relative 'helpers/equal_support'
4
+ require_relative 'helpers/attribute_support'
5
+ require_relative 'helpers/copyable'
6
+
7
+ module Sequent
8
+ module Core
9
+ class Event
10
+ include Sequent::Core::Helpers::StringSupport,
11
+ Sequent::Core::Helpers::EqualSupport,
12
+ Sequent::Core::Helpers::AttributeSupport,
13
+ Sequent::Core::Helpers::Copyable,
14
+ ActiveModel::Serializers::JSON
15
+ attrs aggregate_id: String, sequence_number: Integer, created_at: DateTime
16
+ self.include_root_in_json = false
17
+
18
+ def initialize(args = {})
19
+ update_all_attributes args
20
+ raise "Missing aggregate_id" unless @aggregate_id
21
+ raise "Missing sequence_number" unless @sequence_number
22
+ @created_at ||= DateTime.now
23
+ end
24
+
25
+ def payload
26
+ result = {}
27
+ instance_variables.reject { |k| payload_variables.include?(k.to_s) }.each do |k|
28
+ result[k.to_s[1 .. -1].to_sym] = instance_variable_get(k)
29
+ end
30
+ result
31
+ end
32
+ protected
33
+ def payload_variables
34
+ %w{@aggregate_id @sequence_number @created_at @underscored}
35
+ end
36
+
37
+ end
38
+
39
+ class TenantEvent < Event
40
+
41
+ attrs organization_id: String
42
+
43
+ def initialize(args = {})
44
+ super
45
+ raise "Missing organization_id" unless @organization_id
46
+ end
47
+
48
+ protected
49
+ def payload_variables
50
+ super << "@organization_id"
51
+ end
52
+
53
+ end
54
+
55
+ class CreateEvent < TenantEvent
56
+
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+
@@ -0,0 +1,34 @@
1
+ require 'oj'
2
+ require 'active_record'
3
+
4
+ module Sequent
5
+ module Core
6
+ class EventRecord < ActiveRecord::Base
7
+
8
+ self.table_name = "event_records"
9
+
10
+ belongs_to :command_record
11
+
12
+ validates_presence_of :aggregate_id, :sequence_number, :event_type, :event_json
13
+ validates_numericality_of :sequence_number
14
+
15
+
16
+ def event
17
+ payload = Oj.strict_load(event_json)
18
+ Class.const_get(event_type.to_sym).deserialize_from_json(payload)
19
+ end
20
+
21
+ def event=(event)
22
+ self.aggregate_id = event.aggregate_id
23
+ self.sequence_number = event.sequence_number
24
+ self.organization_id = event.organization_id if event.respond_to?(:organization_id)
25
+ self.event_type = event.class.name
26
+ self.created_at = event.created_at
27
+ self.event_json = event.to_json.to_s
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
34
+