sandthorn_driver_sequel 1.1.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/Guardfile +7 -0
- data/lib/sandthorn_driver_sequel/access/aggregate_access.rb +50 -0
- data/lib/sandthorn_driver_sequel/access/event_access.rb +81 -0
- data/lib/sandthorn_driver_sequel/access/snapshot_access.rb +87 -0
- data/lib/sandthorn_driver_sequel/access.rb +20 -0
- data/lib/sandthorn_driver_sequel/errors.rb +47 -5
- data/lib/sandthorn_driver_sequel/event_query.rb +90 -0
- data/lib/sandthorn_driver_sequel/event_store.rb +90 -153
- data/lib/sandthorn_driver_sequel/event_store_context.rb +1 -0
- data/lib/sandthorn_driver_sequel/migration.rb +9 -1
- data/lib/sandthorn_driver_sequel/old_event_store.rb +228 -0
- data/lib/sandthorn_driver_sequel/sequel_driver.rb +8 -25
- data/lib/sandthorn_driver_sequel/storage.rb +46 -0
- data/lib/sandthorn_driver_sequel/utilities/array.rb +13 -0
- data/lib/sandthorn_driver_sequel/utilities.rb +1 -0
- data/lib/sandthorn_driver_sequel/version.rb +1 -1
- data/lib/sandthorn_driver_sequel/wrappers/event_wrapper.rb +12 -0
- data/lib/sandthorn_driver_sequel/wrappers/snapshot_wrapper.rb +11 -0
- data/lib/sandthorn_driver_sequel/wrappers.rb +2 -0
- data/lib/sandthorn_driver_sequel.rb +5 -0
- data/sandthorn_driver_sequel.gemspec +2 -2
- data/spec/aggregate_access_spec.rb +97 -0
- data/spec/asking_for_aggregates_to_snapshot_spec.rb +7 -4
- data/spec/driver_interface_spec.rb +23 -40
- data/spec/event_access_spec.rb +96 -0
- data/spec/event_store_with_context_spec.rb +4 -4
- data/spec/get_events_spec.rb +20 -13
- data/spec/migration_specifying_domain_spec.rb +10 -10
- data/spec/saving_events_spec.rb +42 -39
- data/spec/saving_snapshot_spec.rb +7 -7
- data/spec/snapshot_access_spec.rb +119 -0
- data/spec/spec_helper.rb +0 -4
- data/spec/storage_spec.rb +66 -0
- metadata +39 -18
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6df7059570ed2a75376a49b5568cde3dcda5abef
         | 
| 4 | 
            +
              data.tar.gz: d0df6fa0c71d2388f31897fcb4c1918ecbe6bdd2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: cff7f24db48fe2ec230c54526f3231fff0c2f7cd94ed12b585807cfa06084cba5a7c4d7d63b670816844ba8b5f6a3114a6d755bb0b16b6cb2e3336ea9c68236c
         | 
| 7 | 
            +
              data.tar.gz: 2592b3457a5c030517d968302aa4c716af3cd58fa3080c1378960a066a40fbf28000e963ab22d75c0fbf600acd3b4f75799ceea63d3a01f1c7d90d59927b5ea4
         | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/Guardfile
    ADDED
    
    
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module SandthornDriverSequel
         | 
| 2 | 
            +
              class AggregateAccess < Access::Base
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def find_or_register(aggregate_id, aggregate_type)
         | 
| 5 | 
            +
                  if aggregate = find_by_aggregate_id(aggregate_id)
         | 
| 6 | 
            +
                    aggregate
         | 
| 7 | 
            +
                  else
         | 
| 8 | 
            +
                    register_aggregate(aggregate_id, aggregate_type)
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # Create a database row for an aggregate.
         | 
| 13 | 
            +
                # Return the row.
         | 
| 14 | 
            +
                def register_aggregate(aggregate_id, aggregate_type)
         | 
| 15 | 
            +
                  id = storage.aggregates.insert(aggregate_id: aggregate_id, aggregate_type: aggregate_type.to_s)
         | 
| 16 | 
            +
                  find(id)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # Find by database table id.
         | 
| 20 | 
            +
                def find(id)
         | 
| 21 | 
            +
                  storage.aggregates[id]
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def find_by_aggregate_id(aggregate_id)
         | 
| 25 | 
            +
                  storage.aggregates.first(aggregate_id: aggregate_id)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                # Throws an error if the aggregate isn't registered.
         | 
| 29 | 
            +
                def find_by_aggregate_id!(aggregate_id)
         | 
| 30 | 
            +
                  aggregate = find_by_aggregate_id(aggregate_id)
         | 
| 31 | 
            +
                  raise Errors::NoAggregateError, aggregate_id unless aggregate
         | 
| 32 | 
            +
                  aggregate
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def aggregate_types
         | 
| 36 | 
            +
                  storage.aggregates.select(:aggregate_type).distinct.select_map(:aggregate_type)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                # Returns aggregate ids.
         | 
| 40 | 
            +
                # @param aggregate_type, optional,
         | 
| 41 | 
            +
                def aggregate_ids(aggregate_type: nil)
         | 
| 42 | 
            +
                  aggs = storage.aggregates
         | 
| 43 | 
            +
                  if aggregate_type
         | 
| 44 | 
            +
                    aggs = aggs.where(aggregate_type: aggregate_type.to_s)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                  aggs.order(:id).select_map(:aggregate_id)
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,81 @@ | |
| 1 | 
            +
            module SandthornDriverSequel
         | 
| 2 | 
            +
              class EventAccess < Access::Base
         | 
| 3 | 
            +
                # = EventAccess
         | 
| 4 | 
            +
                # Reads and writes events.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def store_events(aggregate, events)
         | 
| 7 | 
            +
                  events = Utilities.array_wrap(events)
         | 
| 8 | 
            +
                  timestamp = Time.now.utc
         | 
| 9 | 
            +
                  events.each do |event|
         | 
| 10 | 
            +
                    store_event(aggregate, timestamp, event)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                  aggregate.save
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def find_events_by_aggregate_id(aggregate_id)
         | 
| 16 | 
            +
                  aggregate_version = Sequel.qualify(storage.events_table_name, :aggregate_version)
         | 
| 17 | 
            +
                  wrap(storage.events
         | 
| 18 | 
            +
                    .join(storage.aggregates, id: :aggregate_table_id)
         | 
| 19 | 
            +
                    .where(aggregate_id: aggregate_id)
         | 
| 20 | 
            +
                    .select(
         | 
| 21 | 
            +
                      :sequence_number,
         | 
| 22 | 
            +
                      :aggregate_id,
         | 
| 23 | 
            +
                      :aggregate_table_id,
         | 
| 24 | 
            +
                      aggregate_version,
         | 
| 25 | 
            +
                      :event_name,
         | 
| 26 | 
            +
                      :event_data,
         | 
| 27 | 
            +
                      :timestamp)
         | 
| 28 | 
            +
                    .all)
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # Returns events that occurred after the given snapshot
         | 
| 32 | 
            +
                def after_snapshot(snapshot)
         | 
| 33 | 
            +
                  _aggregate_version = snapshot.aggregate_version
         | 
| 34 | 
            +
                  aggregate_table_id = snapshot.aggregate_table_id
         | 
| 35 | 
            +
                  wrap(storage.events
         | 
| 36 | 
            +
                    .where(aggregate_table_id: aggregate_table_id)
         | 
| 37 | 
            +
                    .where { aggregate_version > _aggregate_version }.all)
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def get_events(*args)
         | 
| 41 | 
            +
                  query_builder = EventQuery.new(storage)
         | 
| 42 | 
            +
                  query_builder.build(*args)
         | 
| 43 | 
            +
                  wrap(query_builder.events)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                private
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def wrap(arg)
         | 
| 49 | 
            +
                  events = Utilities.array_wrap(arg)
         | 
| 50 | 
            +
                  events.map { |e| EventWrapper.new(e.values) }
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def build_event_data(aggregate, timestamp, event)
         | 
| 54 | 
            +
                  {
         | 
| 55 | 
            +
                      aggregate_table_id: aggregate.id,
         | 
| 56 | 
            +
                      aggregate_version: event.aggregate_version,
         | 
| 57 | 
            +
                      event_name: event.event_name,
         | 
| 58 | 
            +
                      event_data: event.event_data,
         | 
| 59 | 
            +
                      timestamp: timestamp
         | 
| 60 | 
            +
                  }
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def check_versions!(aggregate, event)
         | 
| 64 | 
            +
                  version = aggregate.aggregate_version
         | 
| 65 | 
            +
                  if version != event[:aggregate_version]
         | 
| 66 | 
            +
                    raise Errors::ConcurrencyError.new(event, aggregate)
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                rescue TypeError
         | 
| 69 | 
            +
                  raise Errors::EventFormatError, "Event has wrong format: #{event.inspect}"
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def store_event(aggregate, timestamp, event)
         | 
| 73 | 
            +
                  event = EventWrapper.new(event)
         | 
| 74 | 
            +
                  aggregate.aggregate_version += 1
         | 
| 75 | 
            +
                  check_versions!(aggregate, event)
         | 
| 76 | 
            +
                  data = build_event_data(aggregate, timestamp, event)
         | 
| 77 | 
            +
                  storage.events.insert(data)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
            end
         | 
| @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            module SandthornDriverSequel
         | 
| 2 | 
            +
              class SnapshotAccess < Access::Base
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def find_by_aggregate_id(aggregate_id)
         | 
| 5 | 
            +
                  aggregate = aggregates.find_by_aggregate_id(aggregate_id)
         | 
| 6 | 
            +
                  storage.snapshots.first(aggregate_table_id: aggregate.id)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def find(snapshot_id)
         | 
| 10 | 
            +
                  storage.snapshots[snapshot_id]
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def record_snapshot(aggregate_id, snapshot_data)
         | 
| 14 | 
            +
                  aggregate = aggregates.find_by_aggregate_id!(aggregate_id)
         | 
| 15 | 
            +
                  previous_snapshot = find_by_aggregate_id(aggregate_id)
         | 
| 16 | 
            +
                  if perform_snapshot?(aggregate, previous_snapshot)
         | 
| 17 | 
            +
                    perform_snapshot(aggregate, previous_snapshot, snapshot_data)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def obsolete(aggregate_types: [], max_event_distance: 100)
         | 
| 22 | 
            +
                  aggregate_types.map!(&:to_s)
         | 
| 23 | 
            +
                  snapshot_version = Sequel.qualify(storage.snapshots_table_name, :aggregate_version)
         | 
| 24 | 
            +
                  aggregate_version = Sequel.qualify(storage.aggregates_table_name, :aggregate_version)
         | 
| 25 | 
            +
                  query = storage.aggregates.left_outer_join(storage.snapshots, aggregate_table_id: :id)
         | 
| 26 | 
            +
                  query = query.select { (aggregate_version - snapshot_version).as(distance) }
         | 
| 27 | 
            +
                  query = query.select_append(:aggregate_id, :aggregate_type)
         | 
| 28 | 
            +
                  query = query.where { (aggregate_version - coalesce(snapshot_version, 0)) > max_event_distance }
         | 
| 29 | 
            +
                  if aggregate_types.any?
         | 
| 30 | 
            +
                    query = query.where(aggregate_type: aggregate_types)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                  query.all
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                private
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def aggregates
         | 
| 38 | 
            +
                  @aggregates ||= AggregateAccess.new(storage)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def perform_snapshot?(aggregate, snapshot)
         | 
| 42 | 
            +
                  return true if snapshot.nil?
         | 
| 43 | 
            +
                  snapshot = SnapshotWrapper.new(snapshot)
         | 
| 44 | 
            +
                  aggregate.aggregate_version > snapshot.aggregate_version
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def perform_snapshot(aggregate, snapshot, snapshot_data)
         | 
| 48 | 
            +
                  check_snapshot_version!(aggregate, snapshot_data)
         | 
| 49 | 
            +
                  if valid_snapshot?(snapshot)
         | 
| 50 | 
            +
                    update_snapshot(snapshot, snapshot_data)
         | 
| 51 | 
            +
                  else
         | 
| 52 | 
            +
                    insert_snapshot(aggregate, snapshot_data)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def insert_snapshot(aggregate, snapshot_data)
         | 
| 57 | 
            +
                  data = build_snapshot(snapshot_data)
         | 
| 58 | 
            +
                  data[:aggregate_table_id] = aggregate.id
         | 
| 59 | 
            +
                  storage.snapshots.insert(data)
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def build_snapshot(snapshot_data)
         | 
| 63 | 
            +
                  snapshot_data = SnapshotWrapper.new(snapshot_data)
         | 
| 64 | 
            +
                  {
         | 
| 65 | 
            +
                      snapshot_data:      snapshot_data.data,
         | 
| 66 | 
            +
                      aggregate_version:  snapshot_data.aggregate_version
         | 
| 67 | 
            +
                  }
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def valid_snapshot?(snapshot)
         | 
| 71 | 
            +
                  snapshot && snapshot[:snapshot_data]
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def update_snapshot(snapshot, snapshot_data)
         | 
| 75 | 
            +
                  data = build_snapshot(snapshot_data)
         | 
| 76 | 
            +
                  storage.snapshots.where(id: snapshot.id).update(data)
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def check_snapshot_version!(aggregate, snapshot_data)
         | 
| 80 | 
            +
                  snapshot = SnapshotWrapper.new(snapshot_data)
         | 
| 81 | 
            +
                  if aggregate.aggregate_version < snapshot.aggregate_version
         | 
| 82 | 
            +
                    raise Errors::WrongSnapshotVersionError.new(aggregate, snapshot.aggregate_version)
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module SandthornDriverSequel
         | 
| 2 | 
            +
              module Access
         | 
| 3 | 
            +
                class Base
         | 
| 4 | 
            +
                  # = Access::Base
         | 
| 5 | 
            +
                  # Inheriting classes use +storage+ to provide access to a
         | 
| 6 | 
            +
                  # particular database model/table.
         | 
| 7 | 
            +
                  def initialize(storage)
         | 
| 8 | 
            +
                    @storage = storage
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  private
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  attr_reader :storage
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            require "sandthorn_driver_sequel/access/aggregate_access"
         | 
| 19 | 
            +
            require "sandthorn_driver_sequel/access/event_access"
         | 
| 20 | 
            +
            require "sandthorn_driver_sequel/access/snapshot_access"
         | 
| @@ -1,7 +1,49 @@ | |
| 1 1 | 
             
            module SandthornDriverSequel::Errors
         | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 2 | 
            +
              Error = Class.new(StandardError)
         | 
| 3 | 
            +
              InternalError = Class.new(Error)
         | 
| 4 | 
            +
              NoAggregateError = Class.new(Error)
         | 
| 5 | 
            +
              EventFormatError = Class.new(Error)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class ConcurrencyError < Error
         | 
| 8 | 
            +
                attr_reader :event, :aggregate
         | 
| 9 | 
            +
                def initialize(event, aggregate)
         | 
| 10 | 
            +
                  @event = event
         | 
| 11 | 
            +
                  @aggregate = aggregate
         | 
| 12 | 
            +
                  super(create_message)
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def create_message
         | 
| 16 | 
            +
                  "#{aggregate.aggregate_type} with id #{aggregate.aggregate_id}: " +
         | 
| 17 | 
            +
                  "expected event with version #{aggregate.aggregate_version}, but got #{event.aggregate_version}"
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              class WrongAggregateVersionError < Error;
         | 
| 22 | 
            +
                def initialize(aggregate, version)
         | 
| 23 | 
            +
                  @aggregate = aggregate
         | 
| 24 | 
            +
                  @version = version
         | 
| 25 | 
            +
                  super(create_message)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def create_message
         | 
| 29 | 
            +
                  "#{@aggregate[:aggregate_type]} with id #{@aggregate[:aggregate_id]}" +
         | 
| 30 | 
            +
                  " should be at version #{@version}" +
         | 
| 31 | 
            +
                  " but was #{@aggregate[:aggregate_version]} in the event store."
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              class WrongSnapshotVersionError < Error
         | 
| 36 | 
            +
                attr_reader :aggregate, :version
         | 
| 37 | 
            +
                def initialize(aggregate, version)
         | 
| 38 | 
            +
                  @aggregate = aggregate
         | 
| 39 | 
            +
                  @version = version
         | 
| 40 | 
            +
                  super(create_message)
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def create_message
         | 
| 44 | 
            +
                  "#{aggregate[:aggregate_type]} with id #{aggregate[:aggregate_id]}: tried to save snapshot with version "+
         | 
| 45 | 
            +
                  "#{version}, but current version is at #{aggregate[:aggregate_version]}"
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 7 49 | 
             
            end    
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            module SandthornDriverSequel
         | 
| 2 | 
            +
              class EventQuery
         | 
| 3 | 
            +
                def initialize(storage)
         | 
| 4 | 
            +
                  @storage = storage
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def build(
         | 
| 8 | 
            +
                  aggregate_types: [],
         | 
| 9 | 
            +
                  take: 0,
         | 
| 10 | 
            +
                  after_sequence_number: 0,
         | 
| 11 | 
            +
                  include_events: [],
         | 
| 12 | 
            +
                  exclude_events: [])
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  aggregate_types.map!(&:to_s)
         | 
| 15 | 
            +
                  include_events.map!(&:to_s)
         | 
| 16 | 
            +
                  exclude_events.map!(&:to_s)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  query = storage.events
         | 
| 19 | 
            +
                  query = add_aggregate_types(query, aggregate_types)
         | 
| 20 | 
            +
                  query = add_sequence_number(query, after_sequence_number)
         | 
| 21 | 
            +
                  query = add_included_events(query, include_events)
         | 
| 22 | 
            +
                  query = add_excluded_events(query, exclude_events)
         | 
| 23 | 
            +
                  query = add_select(query)
         | 
| 24 | 
            +
                  query = add_limit(query, take)
         | 
| 25 | 
            +
                  @query = query.order(:sequence_number)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def events
         | 
| 29 | 
            +
                  @query.all
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                private
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                attr_reader :storage
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def add_limit(query, take)
         | 
| 37 | 
            +
                  if take > 0
         | 
| 38 | 
            +
                    query.limit(take)
         | 
| 39 | 
            +
                  else
         | 
| 40 | 
            +
                    query
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def add_select(query)
         | 
| 45 | 
            +
                  query.select(*select_columns)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def select_columns
         | 
| 49 | 
            +
                  rel = Sequel.qualify(storage.events_table_name, :aggregate_version)
         | 
| 50 | 
            +
                  [
         | 
| 51 | 
            +
                    :aggregate_type,
         | 
| 52 | 
            +
                    rel,
         | 
| 53 | 
            +
                    :aggregate_id,
         | 
| 54 | 
            +
                    :sequence_number,
         | 
| 55 | 
            +
                    :event_name,
         | 
| 56 | 
            +
                    :event_data,
         | 
| 57 | 
            +
                    :timestamp
         | 
| 58 | 
            +
                  ]
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def add_excluded_events(query, exclude_events)
         | 
| 62 | 
            +
                  if exclude_events.any?
         | 
| 63 | 
            +
                    query.exclude(event_name: exclude_events)
         | 
| 64 | 
            +
                  else
         | 
| 65 | 
            +
                    query
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def add_included_events(query, include_events)
         | 
| 70 | 
            +
                  if include_events.any?
         | 
| 71 | 
            +
                    query.where(event_name: include_events)
         | 
| 72 | 
            +
                  else
         | 
| 73 | 
            +
                    query
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def add_sequence_number(query, after_sequence_number)
         | 
| 78 | 
            +
                  query.where { sequence_number > after_sequence_number }
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def add_aggregate_types(query, aggregate_types)
         | 
| 82 | 
            +
                  if aggregate_types.any?
         | 
| 83 | 
            +
                    query.join(storage.aggregates, id: :aggregate_table_id, aggregate_type: aggregate_types)
         | 
| 84 | 
            +
                  else
         | 
| 85 | 
            +
                    query.join(storage.aggregates, id: :aggregate_table_id)
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
            end
         | 
| @@ -1,201 +1,138 @@ | |
| 1 | 
            -
            require 'sandthorn_driver_sequel/sequel_driver'
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            module SandthornDriverSequel
         | 
| 4 2 | 
             
              class EventStore
         | 
| 5 3 | 
             
                include EventStoreContext
         | 
| 4 | 
            +
             | 
| 6 5 | 
             
                attr_reader :driver, :context, :url
         | 
| 6 | 
            +
             | 
| 7 7 | 
             
                def initialize url: nil, context: nil
         | 
| 8 8 | 
             
                  @driver = SequelDriver.new url: url
         | 
| 9 9 | 
             
                  @context = context
         | 
| 10 10 | 
             
                  @url = url
         | 
| 11 11 | 
             
                end
         | 
| 12 12 |  | 
| 13 | 
            -
                def save_events  | 
| 14 | 
            -
                  current_aggregate_version = originating_aggregate_version
         | 
| 15 | 
            -
                  aggregate_type = class_name.first.to_s
         | 
| 13 | 
            +
                def save_events events, aggregate_id, class_name
         | 
| 16 14 | 
             
                  driver.execute_in_transaction do |db|
         | 
| 17 | 
            -
                     | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
                     | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
                     | 
| 28 | 
            -
                    timestamp = Time.now.utc
         | 
| 29 | 
            -
                    aggregate_events.each do |event|
         | 
| 30 | 
            -
                      current_aggregate_version += 1
         | 
| 31 | 
            -
                      if current_aggregate_version != event[:aggregate_version]
         | 
| 32 | 
            -
                        error_message = "#{aggregate_type} with id #{aggregate_id}: expected event with version #{current_aggregate_version}, but got #{event[:aggregate_version]}"
         | 
| 33 | 
            -
                        raise SandthornDriverSequel::Errors::ConcurrencyError.new(error_message)
         | 
| 34 | 
            -
                      end
         | 
| 35 | 
            -
                      to_insert = ({aggregate_table_id: pk_id, aggregate_version: event[:aggregate_version], event_name: event[:event_name], event_data: event[:event_data], timestamp: timestamp})
         | 
| 36 | 
            -
                      db[events_table_name].insert(to_insert)
         | 
| 37 | 
            -
                    end
         | 
| 38 | 
            -
                    db[aggregates_table_name].where(id: pk_id).update(aggregate_version: current_aggregate_version)
         | 
| 15 | 
            +
                    aggregates = get_aggregate_access(db)
         | 
| 16 | 
            +
                    event_access = get_event_access(db)
         | 
| 17 | 
            +
                    aggregate = aggregates.find_or_register(aggregate_id, class_name)
         | 
| 18 | 
            +
                    event_access.store_events(aggregate, events)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def get_aggregate_events(aggregate_id)
         | 
| 23 | 
            +
                  driver.execute do |db|
         | 
| 24 | 
            +
                    events = get_event_access(db)
         | 
| 25 | 
            +
                    events.find_events_by_aggregate_id(aggregate_id)
         | 
| 39 26 | 
             
                  end
         | 
| 40 27 | 
             
                end
         | 
| 41 28 |  | 
| 42 | 
            -
                def save_snapshot aggregate_snapshot, aggregate_id | 
| 43 | 
            -
                  #ar_snapshot.event_name = snapshot[:event_name]
         | 
| 44 | 
            -
                  #ar_snapshot.event_data = snapshot[:event_data]
         | 
| 45 | 
            -
                  #ar_snapshot.aggregate_version = snapshot[:aggregate_version]
         | 
| 46 | 
            -
                  #ar_snapshot.aggregate_id = aggregate_id
         | 
| 29 | 
            +
                def save_snapshot aggregate_snapshot, aggregate_id
         | 
| 47 30 | 
             
                  driver.execute_in_transaction do |db|
         | 
| 48 | 
            -
                     | 
| 49 | 
            -
                     | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 31 | 
            +
                    snapshot_access = get_snapshot_access(db)
         | 
| 32 | 
            +
                    snapshot_access.record_snapshot(aggregate_id, aggregate_snapshot)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # If the aggregate has a snapshot, return events starting from the snapshots.
         | 
| 37 | 
            +
                # Otherwise, return all events.
         | 
| 38 | 
            +
                # TODO: needs a better name
         | 
| 39 | 
            +
                def get_aggregate_events_from_snapshot(aggregate_id)
         | 
| 40 | 
            +
                  driver.execute do |db|
         | 
| 41 | 
            +
                    snapshots = get_snapshot_access(db)
         | 
| 42 | 
            +
                    event_access = get_event_access(db)
         | 
| 43 | 
            +
                    snapshot = snapshots.find_by_aggregate_id(aggregate_id)
         | 
| 44 | 
            +
                    if snapshot
         | 
| 45 | 
            +
                      events = event_access.after_snapshot(snapshot)
         | 
| 46 | 
            +
                      snapshot_event = build_snapshot_event(snapshot)
         | 
| 47 | 
            +
                      events.unshift(snapshot_event)
         | 
| 60 48 | 
             
                    else
         | 
| 61 | 
            -
                       | 
| 62 | 
            -
                      db[snapshots_table_name].where(aggregate_table_id: pk_id).update(to_update)
         | 
| 49 | 
            +
                      event_access.find_events_by_aggregate_id(aggregate_id)
         | 
| 63 50 | 
             
                    end
         | 
| 64 51 | 
             
                  end
         | 
| 65 52 | 
             
                end
         | 
| 66 53 |  | 
| 67 | 
            -
                def  | 
| 68 | 
            -
                   | 
| 69 | 
            -
             | 
| 54 | 
            +
                def build_snapshot_event(snapshot)
         | 
| 55 | 
            +
                  {
         | 
| 56 | 
            +
                      aggregate_version: snapshot[:aggregate_version],
         | 
| 57 | 
            +
                      event_data: snapshot[:snapshot_data],
         | 
| 58 | 
            +
                      event_name: "aggregate_set_from_snapshot"
         | 
| 59 | 
            +
                  }
         | 
| 70 60 | 
             
                end
         | 
| 71 61 |  | 
| 72 62 | 
             
                def get_aggregate aggregate_id, *class_name
         | 
| 73 | 
            -
                   | 
| 74 | 
            -
                   | 
| 75 | 
            -
                  after_aggregate_version = snapshot[:aggregate_version] unless snapshot.nil?
         | 
| 76 | 
            -
                  events = aggregate_events after_aggregate_version: after_aggregate_version, aggregate_id: aggregate_id
         | 
| 77 | 
            -
                  unless snapshot.nil?
         | 
| 78 | 
            -
                    snap_event = snapshot
         | 
| 79 | 
            -
                    snap_event[:event_name] = "aggregate_set_from_snapshot"
         | 
| 80 | 
            -
                    events = events.unshift(snap_event)
         | 
| 81 | 
            -
                  end
         | 
| 82 | 
            -
                  events
         | 
| 63 | 
            +
                  warn(":get_aggregate is deprecated. Use :get_aggregate_events_from_snapshot")
         | 
| 64 | 
            +
                  get_aggregate_events_from_snapshot(aggregate_id)
         | 
| 83 65 | 
             
                end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 66 | 
            +
             | 
| 67 | 
            +
                def get_aggregate_ids(aggregate_type: nil)
         | 
| 86 68 | 
             
                  driver.execute do |db|
         | 
| 87 | 
            -
                     | 
| 69 | 
            +
                    access = get_aggregate_access(db)
         | 
| 70 | 
            +
                    access.aggregate_ids(aggregate_type: aggregate_type)
         | 
| 88 71 | 
             
                  end
         | 
| 89 72 | 
             
                end
         | 
| 90 73 |  | 
| 91 | 
            -
                def  | 
| 92 | 
            -
                   | 
| 93 | 
            -
             | 
| 94 | 
            -
                  end
         | 
| 74 | 
            +
                def get_aggregate_list_by_typename(type)
         | 
| 75 | 
            +
                  warn(":get_aggregate_list_by_typenames is deprecated. Use :get_aggregate_ids")
         | 
| 76 | 
            +
                  get_aggregate_ids(type: type)
         | 
| 95 77 | 
             
                end
         | 
| 96 78 |  | 
| 97 | 
            -
                def  | 
| 98 | 
            -
                  aggregate_type = class_name.first.to_s
         | 
| 79 | 
            +
                def get_all_types
         | 
| 99 80 | 
             
                  driver.execute do |db|
         | 
| 100 | 
            -
                     | 
| 101 | 
            -
                     | 
| 102 | 
            -
                    return nil if snap.nil?
         | 
| 103 | 
            -
                    return {aggregate_version: snap[:aggregate_version], event_data: snap[:snapshot_data]}
         | 
| 81 | 
            +
                    access = get_aggregate_access(db)
         | 
| 82 | 
            +
                    access.aggregate_types
         | 
| 104 83 | 
             
                  end
         | 
| 105 84 | 
             
                end
         | 
| 106 85 |  | 
| 107 | 
            -
                def  | 
| 108 | 
            -
                  take = args.fetch(:take, 0)
         | 
| 109 | 
            -
                  aggregate_type = class_name.to_s
         | 
| 86 | 
            +
                def get_snapshot aggregate_id
         | 
| 110 87 | 
             
                  driver.execute do |db|
         | 
| 111 | 
            -
                     | 
| 112 | 
            -
                     | 
| 113 | 
            -
                     | 
| 114 | 
            -
                    query = query.select(:aggregate_type, rel, :aggregate_id, :sequence_number, :event_name, :event_data, :timestamp)
         | 
| 115 | 
            -
                    query = query.limit(take) if take > 0
         | 
| 116 | 
            -
                    return query.order(:sequence_number).all
         | 
| 88 | 
            +
                    snapshots = get_snapshot_access(db)
         | 
| 89 | 
            +
                    snapshot = snapshots.find_by_aggregate_id(aggregate_id)
         | 
| 90 | 
            +
                    transform_snapshot(snapshot)
         | 
| 117 91 | 
             
                  end
         | 
| 118 92 | 
             
                end
         | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
                  exclude_events = exclude_events.map { |e| e.to_s  }
         | 
| 122 | 
            -
                  aggregate_types = aggregate_types.map { |e| e.to_s  }
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                def get_events(*args)
         | 
| 123 95 | 
             
                  driver.execute do |db|
         | 
| 124 | 
            -
                     | 
| 125 | 
            -
             | 
| 126 | 
            -
                    else
         | 
| 127 | 
            -
                      query = db[events_table_name].join(aggregates_table_name, id: :aggregate_table_id, aggregate_type: aggregate_types)
         | 
| 128 | 
            -
                    end
         | 
| 129 | 
            -
                    query = query.where{sequence_number > after_sequence_number}
         | 
| 130 | 
            -
                    unless include_events.empty?
         | 
| 131 | 
            -
                      query = query.where(event_name: include_events)
         | 
| 132 | 
            -
                    end
         | 
| 133 | 
            -
                    unless exclude_events.empty?
         | 
| 134 | 
            -
                      query = query.exclude(event_name: exclude_events)
         | 
| 135 | 
            -
                    end
         | 
| 136 | 
            -
                    rel = "#{events_table_name}__aggregate_version".to_sym
         | 
| 137 | 
            -
                    query = query.select(:aggregate_type, rel, :aggregate_id, :sequence_number, :event_name, :event_data, :timestamp)
         | 
| 138 | 
            -
                    query = query.limit(take) if take > 0
         | 
| 139 | 
            -
                    return query.order(:sequence_number).all
         | 
| 96 | 
            +
                    event_access = get_event_access(db)
         | 
| 97 | 
            +
                    event_access.get_events(*args)
         | 
| 140 98 | 
             
                  end
         | 
| 141 99 | 
             
                end
         | 
| 142 | 
            -
             | 
| 100 | 
            +
             | 
| 101 | 
            +
                def get_new_events_after_event_id_matching_classname event_id, class_name, take: 0
         | 
| 102 | 
            +
                  get_events(after_sequence_number: event_id, aggregate_types: Utilities.array_wrap(class_name), take: take)
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                def obsolete_snapshots(*args)
         | 
| 143 106 | 
             
                  driver.execute do |db|
         | 
| 144 | 
            -
                     | 
| 145 | 
            -
                     | 
| 146 | 
            -
                    query_select = eval("lambda{(#{aggr_rel} - coalesce(#{rel},0)).as(distance)}")
         | 
| 147 | 
            -
                    query = db[aggregates_table_name].left_outer_join(snapshots_table_name, aggregate_table_id: :id)
         | 
| 148 | 
            -
                    query = query.select &query_select
         | 
| 149 | 
            -
                    query = query.select_append(:aggregate_id, :aggregate_type)
         | 
| 150 | 
            -
                    query_where = eval("lambda{(#{aggr_rel} - coalesce(#{rel},0)) > max_event_distance}")
         | 
| 151 | 
            -
                    query = query.where &query_where 
         | 
| 152 | 
            -
                    unless class_names.empty?
         | 
| 153 | 
            -
                      class_names.map! {|c|c.to_s}
         | 
| 154 | 
            -
                      query = query.where(aggregate_type: class_names)
         | 
| 155 | 
            -
                    end
         | 
| 156 | 
            -
                    query.all
         | 
| 107 | 
            +
                    snapshots = get_snapshot_access(db)
         | 
| 108 | 
            +
                    snapshots.obsolete(*args)
         | 
| 157 109 | 
             
                  end
         | 
| 158 110 | 
             
                end
         | 
| 111 | 
            +
             | 
| 159 112 | 
             
                private
         | 
| 160 113 |  | 
| 161 | 
            -
                def  | 
| 162 | 
            -
                  
         | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
                   | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
                   | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
                   | 
| 174 | 
            -
                  #     rel = "#{events_table_name}__aggregate_version".to_sym
         | 
| 175 | 
            -
                  #     where_proc = eval("lambda{ #{rel} > after_aggregate_version }")
         | 
| 176 | 
            -
                  #     driver.execute do |db|
         | 
| 177 | 
            -
                  #       query = db[events_table_name].join(aggregates_table_name, id: :aggregate_table_id, aggregate_id: aggregate_id)
         | 
| 178 | 
            -
                  #       query = query.where &where_proc
         | 
| 179 | 
            -
                  #       result = query.select(rel, :aggregate_id, :sequence_number, :event_name, :event_data, :timestamp).order(:sequence_number).all
         | 
| 180 | 
            -
                  #     end
         | 
| 181 | 
            -
                  #   }
         | 
| 182 | 
            -
             | 
| 183 | 
            -
                  # end
         | 
| 184 | 
            -
                  # result
         | 
| 185 | 
            -
                end
         | 
| 186 | 
            -
                def get_current_aggregate_from_aggregates_table aggregate_id, aggregate_type, db
         | 
| 187 | 
            -
                  aggregate_type = aggregate_type.to_s
         | 
| 188 | 
            -
                  current_aggregate = db[aggregates_table_name].where(aggregate_id: aggregate_id)
         | 
| 189 | 
            -
                  if current_aggregate.empty?
         | 
| 190 | 
            -
                    error_message = "#{aggregate_type} with id #{aggregate_id} was not found in the eventstore."
         | 
| 191 | 
            -
                    raise SandthornDriverSequel::Errors::NoAggregateError.new(error_message)
         | 
| 192 | 
            -
                  end
         | 
| 193 | 
            -
                  current_aggregate.first
         | 
| 114 | 
            +
                def transform_snapshot(snapshot)
         | 
| 115 | 
            +
                  {
         | 
| 116 | 
            +
                      aggregate_version: snapshot.aggregate_version,
         | 
| 117 | 
            +
                      event_data: snapshot.snapshot_data
         | 
| 118 | 
            +
                  }
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                def get_aggregate_access(db)
         | 
| 122 | 
            +
                  AggregateAccess.new(storage(db))
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                def get_event_access(db)
         | 
| 126 | 
            +
                  EventAccess.new(storage(db))
         | 
| 194 127 | 
             
                end
         | 
| 195 | 
            -
             | 
| 196 | 
            -
             | 
| 197 | 
            -
                   | 
| 198 | 
            -
                  snap.first
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                def get_snapshot_access(db)
         | 
| 130 | 
            +
                  SnapshotAccess.new(storage(db))
         | 
| 199 131 | 
             
                end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                def storage(db)
         | 
| 134 | 
            +
                  Storage.new(db, context)
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 200 137 | 
             
              end
         | 
| 201 | 
            -
            end
         | 
| 138 | 
            +
            end
         |