Class: Hecks::Ports::Persistence::AppendOnly

Inherits:
Object
  • Object
show all
Defined in:
lib/hecks/ports/persistence/append_only.rb

Overview

Makes append-before-projection a port invariant. Adapters retain control of their durable format, but every one must accept the same entry stream and materialize current state from it.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(adapter) ⇒ AppendOnly

Returns a new instance of AppendOnly.



22
23
24
25
26
27
28
# File 'lib/hecks/ports/persistence/append_only.rb', line 22

def initialize(adapter)
  @adapter = adapter
  required = %i[append project entries]
  missing = required.reject { |method| adapter.respond_to?(method) }
  raise Runtime::WiringError,
        "#{adapter.class} does not implement append-only persistence: #{missing.join(', ')}" unless missing.empty?
end

Instance Attribute Details

#adapterObject (readonly)

Returns the value of attribute adapter.



18
19
20
# File 'lib/hecks/ports/persistence/append_only.rb', line 18

def adapter
  @adapter
end

Instance Method Details

#aggregateObject



20
# File 'lib/hecks/ports/persistence/append_only.rb', line 20

def aggregate = @adapter.aggregate

#all(**opts) ⇒ Object



31
# File 'lib/hecks/ports/persistence/append_only.rb', line 31

def all(**opts) = @adapter.all(**opts)

#append(entry) ⇒ Object



64
# File 'lib/hecks/ports/persistence/append_only.rb', line 64

def append(entry) = @adapter.append(entry)

#atomic_put(instance, insert_only: false) ⇒ Object



74
75
76
77
78
79
80
81
82
# File 'lib/hecks/ports/persistence/append_only.rb', line 74

def atomic_put(instance, insert_only: false)
  unless capabilities.include?(:atomic_put) && @adapter.respond_to?(:atomic_put)
    raise Runtime::WiringError, "#{@adapter.class} advertises no atomic_put persistence capability"
  end

  entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
  status = @adapter.atomic_put(entry, insert_only: insert_only)
  Outcome.new(status: status, instance: instance)
end

#capabilitiesObject



35
36
37
38
39
# File 'lib/hecks/ports/persistence/append_only.rb', line 35

def capabilities
  return [] unless @adapter.respond_to?(:persistence_capabilities)

  Array(@adapter.persistence_capabilities).map(&:to_sym).freeze
end

#countObject



32
# File 'lib/hecks/ports/persistence/append_only.rb', line 32

def count = @adapter.count

#delete(id) ⇒ Object



84
85
86
87
88
89
90
91
# File 'lib/hecks/ports/persistence/append_only.rb', line 84

def delete(id)
  return false unless find(id)

  entry = Entry.new(operation: "delete", id: id.to_s, state: nil)
  append(entry)
  project(entry)
  true
end

#entriesObject



33
# File 'lib/hecks/ports/persistence/append_only.rb', line 33

def entries = @adapter.entries

#eventsObject

NOT an endless def events = ... if ... — that modifier binds to the WHOLE def, not just its body, so it evaluates against @adapter while @adapter is still nil (class-body time, before initialize ever runs) and silently skips defining the method at all. Found live: nothing in this codebase called AppendOnly#events before Memory got a reset! test that did.



53
54
55
# File 'lib/hecks/ports/persistence/append_only.rb', line 53

def events
  @adapter.events if @adapter.respond_to?(:events)
end

#find(id) ⇒ Object



30
# File 'lib/hecks/ports/persistence/append_only.rb', line 30

def find(id) = @adapter.find(id)

#project(entry) ⇒ Object



65
# File 'lib/hecks/ports/persistence/append_only.rb', line 65

def project(entry) = @adapter.project(entry)

#query_read_model(domain, model, args, bluebook = nil) ⇒ Object



94
95
96
97
98
# File 'lib/hecks/ports/persistence/append_only.rb', line 94

def query_read_model(domain, model, args, bluebook = nil)
  return unless @adapter.respond_to?(:query_read_model)

  @adapter.query_read_model(domain, model, args, bluebook)
end

#record_event(event) ⇒ Object



93
# File 'lib/hecks/ports/persistence/append_only.rb', line 93

def record_event(event) = @adapter.record_event(event) if

#recover!Object

An append is durable before a projection is attempted. Replaying the log restores a snapshot/table after a crash in that small window.



59
60
61
62
# File 'lib/hecks/ports/persistence/append_only.rb', line 59

def recover!
  entries.each { |entry| project(entry) }
  self
end

#reset!Object



41
42
43
44
45
# File 'lib/hecks/ports/persistence/append_only.rb', line 41

def reset!
  raise Runtime::WiringError, "append-only adapter cannot reset" unless @adapter.respond_to?(:reset!)

  @adapter.reset!
end

#save(instance) ⇒ Object



67
68
69
70
71
72
# File 'lib/hecks/ports/persistence/append_only.rb', line 67

def save(instance)
  entry = Entry.new(operation: "save", id: instance.id.to_s, state: instance.state.dup)
  append(entry)
  project(entry)
  instance
end