star_gates 0.0.0 → 0.2.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/.dictate.toml +19 -0
- data/.pre-commit-config.yaml +20 -0
- data/CHANGELOG.md +60 -3
- data/LICENSE.txt +23 -16
- data/README.md +182 -16
- data/Rakefile +14 -4
- data/lib/star_gates/adapters/base_adapter.rb +65 -0
- data/lib/star_gates/adapters/memory_adapter.rb +93 -0
- data/lib/star_gates/adapters/redis_adapter.rb +119 -0
- data/lib/star_gates/application_star_gate.rb +398 -0
- data/lib/star_gates/circuit.rb +171 -0
- data/lib/star_gates/configuration.rb +172 -0
- data/lib/star_gates/dsl.rb +71 -2
- data/lib/star_gates/errors.rb +8 -0
- data/lib/star_gates/event_store/event_store.rb +79 -0
- data/lib/star_gates/event_store/memory_adapter.rb +78 -0
- data/lib/star_gates/events/base_event.rb +45 -0
- data/lib/star_gates/events/feature_events.rb +97 -0
- data/lib/star_gates/feature.rb +426 -0
- data/lib/star_gates/flags.rb +153 -0
- data/lib/star_gates/gate.rb +100 -0
- data/lib/star_gates/hexagonal/feature_aggregate.rb +250 -0
- data/lib/star_gates/hexagonal/feature_repository.rb +67 -0
- data/lib/star_gates/hexagonal/feature_service.rb +121 -0
- data/lib/star_gates/jsonrpc_rails_adapter.rb +251 -0
- data/lib/star_gates/projections/feature_projection.rb +224 -0
- data/lib/star_gates/registry.rb +58 -0
- data/lib/star_gates/telemetry/opentelemetry_adapter.rb +252 -0
- data/lib/star_gates/telemetry/prometheus_adapter.rb +183 -0
- data/lib/star_gates/telemetry/telemetry_adapter.rb +160 -0
- data/lib/star_gates/version.rb +1 -1
- data/lib/star_gates.rb +308 -8
- metadata +95 -14
- data/sig/star_gates.rbs +0 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StarGates
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :instrumentation, :telemetry_adapter, :default_timeout, :default_retry_count,
|
|
6
|
+
:circuit_breaker_threshold, :cache_ttl, :environment, :feature_defaults,
|
|
7
|
+
:enable_performance_monitoring, :log_level, :error_reporting
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@instrumentation = true
|
|
11
|
+
@telemetry_adapter = detect_default_telemetry_adapter
|
|
12
|
+
@default_timeout = 5.0 # seconds
|
|
13
|
+
@default_retry_count = 3
|
|
14
|
+
@circuit_breaker_threshold = 5
|
|
15
|
+
@cache_ttl = 300 # 5 minutes
|
|
16
|
+
@environment = detect_environment
|
|
17
|
+
@feature_defaults = {}
|
|
18
|
+
@enable_performance_monitoring = true
|
|
19
|
+
@log_level = :info
|
|
20
|
+
@error_reporting = true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Environment-specific configurations
|
|
24
|
+
def development?
|
|
25
|
+
environment == :development
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test?
|
|
29
|
+
environment == :test
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def production?
|
|
33
|
+
environment == :production
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def staging?
|
|
37
|
+
environment == :staging
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Feature-specific defaults
|
|
41
|
+
def feature_config_for(feature_name)
|
|
42
|
+
base_config = {
|
|
43
|
+
timeout: default_timeout,
|
|
44
|
+
retry_count: default_retry_count,
|
|
45
|
+
circuit_breaker_threshold: circuit_breaker_threshold,
|
|
46
|
+
cache_ttl: cache_ttl,
|
|
47
|
+
instrumentation: instrumentation,
|
|
48
|
+
performance_monitoring: enable_performance_monitoring
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Merge with feature-specific overrides
|
|
52
|
+
feature_specific = feature_defaults[feature_name.to_sym] || {}
|
|
53
|
+
base_config.merge(feature_specific)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Configure feature-specific defaults
|
|
57
|
+
def configure_feature(feature_name, config = {})
|
|
58
|
+
feature_defaults[feature_name.to_sym] = config
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Load from YAML file (Rails-style)
|
|
62
|
+
def load_from_file(file_path)
|
|
63
|
+
return unless File.exist?(file_path)
|
|
64
|
+
|
|
65
|
+
require 'yaml'
|
|
66
|
+
# Use safe_load_file to prevent arbitrary code execution
|
|
67
|
+
config_data = YAML.safe_load_file(
|
|
68
|
+
file_path,
|
|
69
|
+
permitted_classes: [Symbol, Time, Date],
|
|
70
|
+
aliases: true
|
|
71
|
+
)
|
|
72
|
+
env_config = config_data[environment.to_s] || {}
|
|
73
|
+
|
|
74
|
+
env_config.each do |key, value|
|
|
75
|
+
setter_method = "#{key}="
|
|
76
|
+
if respond_to?(setter_method)
|
|
77
|
+
send(setter_method, value)
|
|
78
|
+
elsif key == 'features'
|
|
79
|
+
# Handle feature-specific configurations
|
|
80
|
+
value.each { |feature_name, feature_config| configure_feature(feature_name, feature_config) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Environment-aware defaults
|
|
86
|
+
def apply_environment_defaults!
|
|
87
|
+
case environment
|
|
88
|
+
when :development
|
|
89
|
+
@log_level = :debug
|
|
90
|
+
@default_timeout = 10.0 # More generous timeout for dev
|
|
91
|
+
@telemetry_adapter = :memory
|
|
92
|
+
when :test
|
|
93
|
+
@instrumentation = false
|
|
94
|
+
@telemetry_adapter = :memory
|
|
95
|
+
@default_timeout = 1.0 # Fast tests
|
|
96
|
+
@enable_performance_monitoring = false
|
|
97
|
+
when :production
|
|
98
|
+
@log_level = :warn
|
|
99
|
+
@default_timeout = 2.0 # Strict timeout for prod
|
|
100
|
+
@telemetry_adapter = :prometheus
|
|
101
|
+
@error_reporting = true
|
|
102
|
+
when :staging
|
|
103
|
+
@log_level = :info
|
|
104
|
+
@default_timeout = 5.0
|
|
105
|
+
@telemetry_adapter = :prometheus
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Convert to hash for serialization
|
|
110
|
+
def to_h
|
|
111
|
+
{
|
|
112
|
+
instrumentation: instrumentation,
|
|
113
|
+
telemetry_adapter: telemetry_adapter,
|
|
114
|
+
default_timeout: default_timeout,
|
|
115
|
+
default_retry_count: default_retry_count,
|
|
116
|
+
circuit_breaker_threshold: circuit_breaker_threshold,
|
|
117
|
+
cache_ttl: cache_ttl,
|
|
118
|
+
environment: environment,
|
|
119
|
+
enable_performance_monitoring: enable_performance_monitoring,
|
|
120
|
+
log_level: log_level,
|
|
121
|
+
error_reporting: error_reporting,
|
|
122
|
+
feature_defaults: feature_defaults
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def detect_environment
|
|
129
|
+
if defined?(Rails) && Rails.respond_to?(:env)
|
|
130
|
+
Rails.env.to_sym
|
|
131
|
+
elsif ENV['RACK_ENV']
|
|
132
|
+
ENV['RACK_ENV'].to_sym
|
|
133
|
+
elsif ENV['RAILS_ENV']
|
|
134
|
+
ENV['RAILS_ENV'].to_sym
|
|
135
|
+
elsif ENV['RUBY_ENV']
|
|
136
|
+
ENV['RUBY_ENV'].to_sym
|
|
137
|
+
else
|
|
138
|
+
:development
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def detect_default_telemetry_adapter
|
|
143
|
+
return :prometheus if defined?(Prometheus)
|
|
144
|
+
return :opentelemetry if defined?(OpenTelemetry)
|
|
145
|
+
|
|
146
|
+
:memory
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Global configuration instance
|
|
151
|
+
def self.configuration
|
|
152
|
+
@configuration ||= Configuration.new.tap(&:apply_environment_defaults!)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def self.configure
|
|
156
|
+
yield(configuration) if block_given?
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Load configuration from file
|
|
160
|
+
def self.load_config!(file_path = nil)
|
|
161
|
+
file_path ||= default_config_file_path
|
|
162
|
+
configuration.load_from_file(file_path) if file_path
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def self.default_config_file_path
|
|
166
|
+
if defined?(Rails)
|
|
167
|
+
Rails.root.join('config', 'star_gates.yml')
|
|
168
|
+
else
|
|
169
|
+
File.join(Dir.pwd, 'config', 'star_gates.yml')
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/star_gates/dsl.rb
CHANGED
|
@@ -2,6 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
module StarGates
|
|
4
4
|
module DSL
|
|
5
|
-
|
|
5
|
+
class GateBuilder
|
|
6
|
+
attr_reader :name, :feature_config, :circuit_config, :run_block, :fallback_block
|
|
7
|
+
|
|
8
|
+
def initialize(name)
|
|
9
|
+
@name = name
|
|
10
|
+
@feature_config = {}
|
|
11
|
+
@circuit_config = {}
|
|
12
|
+
@run_block = nil
|
|
13
|
+
@fallback_block = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def feature(rollout: 0, group: nil)
|
|
17
|
+
@feature_config = { rollout: rollout, group: group }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def circuit(threshold: 5, within: 30.seconds, reset_after: 1.minute, retry_config: nil)
|
|
21
|
+
@circuit_config = {
|
|
22
|
+
threshold: threshold,
|
|
23
|
+
within: within,
|
|
24
|
+
reset_after: reset_after,
|
|
25
|
+
retry_config: retry_config
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def run(&block)
|
|
30
|
+
@run_block = block
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fallback(&block)
|
|
34
|
+
@fallback_block = block
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build
|
|
38
|
+
feature_instance = build_feature if feature_config.any?
|
|
39
|
+
circuit_instance = build_circuit(feature_instance) if circuit_config.any?
|
|
40
|
+
|
|
41
|
+
StarGates::Gate.new(
|
|
42
|
+
name,
|
|
43
|
+
feature: feature_instance,
|
|
44
|
+
circuit: circuit_instance,
|
|
45
|
+
run_block: run_block,
|
|
46
|
+
fallback_block: fallback_block
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def build_feature
|
|
53
|
+
StarGates::Feature.new(**feature_config)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_circuit(feature)
|
|
57
|
+
# Wire circuit to feature for self-healing behavior
|
|
58
|
+
StarGates::Circuit.new(**circuit_config, feature: feature)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class DefineContext
|
|
63
|
+
def initialize(registry)
|
|
64
|
+
@registry = registry
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def gate(name, &)
|
|
68
|
+
builder = GateBuilder.new(name)
|
|
69
|
+
builder.instance_eval(&) if block_given?
|
|
70
|
+
gate_instance = builder.build
|
|
71
|
+
@registry.register(name, gate_instance)
|
|
72
|
+
gate_instance
|
|
73
|
+
end
|
|
74
|
+
end
|
|
6
75
|
end
|
|
7
|
-
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StarGates
|
|
4
|
+
module EventStore
|
|
5
|
+
class EventStore
|
|
6
|
+
def initialize(adapter: nil)
|
|
7
|
+
@adapter = adapter || MemoryAdapter.new
|
|
8
|
+
@subscribers = Hash.new { |h, k| h[k] = [] }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Append events to the store
|
|
12
|
+
def append(aggregate_id, events, expected_version: nil)
|
|
13
|
+
events = Array(events)
|
|
14
|
+
return if events.empty?
|
|
15
|
+
|
|
16
|
+
# Validate expected version for optimistic concurrency
|
|
17
|
+
if expected_version
|
|
18
|
+
current_version = @adapter.get_version(aggregate_id)
|
|
19
|
+
if current_version != expected_version
|
|
20
|
+
raise ConcurrencyError, "Expected version #{expected_version}, got #{current_version}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Store events
|
|
25
|
+
@adapter.append(aggregate_id, events)
|
|
26
|
+
|
|
27
|
+
# Notify subscribers
|
|
28
|
+
events.each do |event|
|
|
29
|
+
notify_subscribers(event)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
events.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get events for an aggregate
|
|
36
|
+
def get_events(aggregate_id, from_version: 0)
|
|
37
|
+
@adapter.get_events(aggregate_id, from_version: from_version)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get all events from a specific time
|
|
41
|
+
def get_events_from(timestamp, limit: 100)
|
|
42
|
+
@adapter.get_events_from(timestamp, limit: limit)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get events by type
|
|
46
|
+
def get_events_by_type(event_type, limit: 100)
|
|
47
|
+
@adapter.get_events_by_type(event_type, limit: limit)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Subscribe to events
|
|
51
|
+
def subscribe(event_type = nil, &block)
|
|
52
|
+
key = event_type || :all
|
|
53
|
+
@subscribers[key] << block
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get current version of aggregate
|
|
57
|
+
def get_version(aggregate_id)
|
|
58
|
+
@adapter.get_version(aggregate_id)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Replay all events to rebuild projections
|
|
62
|
+
def replay_all(&)
|
|
63
|
+
@adapter.replay_all(&)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def notify_subscribers(event)
|
|
69
|
+
# Notify specific event type subscribers
|
|
70
|
+
@subscribers[event.event_type].each { |subscriber| subscriber.call(event) }
|
|
71
|
+
|
|
72
|
+
# Notify all event subscribers
|
|
73
|
+
@subscribers[:all].each { |subscriber| subscriber.call(event) }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class ConcurrencyError < StandardError; end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
|
|
5
|
+
module StarGates
|
|
6
|
+
module EventStore
|
|
7
|
+
class MemoryAdapter
|
|
8
|
+
def initialize
|
|
9
|
+
@events = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
|
10
|
+
@global_events = Concurrent::Array.new
|
|
11
|
+
@versions = Concurrent::Hash.new(0)
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def append(aggregate_id, events)
|
|
16
|
+
@mutex.synchronize do
|
|
17
|
+
events.each do |event|
|
|
18
|
+
# Increment version
|
|
19
|
+
@versions[aggregate_id] += 1
|
|
20
|
+
versioned_event = event.dup.tap do |e|
|
|
21
|
+
e.instance_variable_set(:@version, @versions[aggregate_id])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Store by aggregate
|
|
25
|
+
@events[aggregate_id] << versioned_event
|
|
26
|
+
|
|
27
|
+
# Store globally for cross-aggregate queries
|
|
28
|
+
@global_events << versioned_event
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_events(aggregate_id, from_version: 0)
|
|
34
|
+
@events[aggregate_id].select { |event| event.version > from_version }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get_events_from(timestamp, limit: 100)
|
|
38
|
+
@global_events
|
|
39
|
+
.select { |event| event.occurred_at >= timestamp }
|
|
40
|
+
.sort_by(&:occurred_at)
|
|
41
|
+
.reverse
|
|
42
|
+
.first(limit)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def get_events_by_type(event_type, limit: 100)
|
|
46
|
+
@global_events
|
|
47
|
+
.select { |event| event.event_type == event_type }
|
|
48
|
+
.sort_by(&:occurred_at)
|
|
49
|
+
.reverse
|
|
50
|
+
.first(limit)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def get_version(aggregate_id)
|
|
54
|
+
@versions[aggregate_id]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def replay_all(&)
|
|
58
|
+
@global_events.sort_by(&:occurred_at).each(&)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def clear!
|
|
62
|
+
@mutex.synchronize do
|
|
63
|
+
@events.clear
|
|
64
|
+
@global_events.clear
|
|
65
|
+
@versions.clear
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def size
|
|
70
|
+
@global_events.size
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def aggregate_count
|
|
74
|
+
@events.size
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module StarGates
|
|
6
|
+
module Events
|
|
7
|
+
class BaseEvent
|
|
8
|
+
attr_reader :event_id, :aggregate_id, :event_type, :data, :metadata, :occurred_at, :version
|
|
9
|
+
|
|
10
|
+
def initialize(aggregate_id:, data: {}, metadata: {}, version: 1)
|
|
11
|
+
@event_id = SecureRandom.uuid
|
|
12
|
+
@aggregate_id = aggregate_id
|
|
13
|
+
@event_type = self.class.name.split('::').last
|
|
14
|
+
@data = data.freeze
|
|
15
|
+
@metadata = metadata.merge(
|
|
16
|
+
source: 'star_gates',
|
|
17
|
+
correlation_id: Thread.current[:correlation_id] || SecureRandom.uuid
|
|
18
|
+
).freeze
|
|
19
|
+
@occurred_at = Time.now.utc
|
|
20
|
+
@version = version
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_h
|
|
25
|
+
{
|
|
26
|
+
event_id: event_id,
|
|
27
|
+
aggregate_id: aggregate_id,
|
|
28
|
+
event_type: event_type,
|
|
29
|
+
data: data,
|
|
30
|
+
metadata: metadata,
|
|
31
|
+
occurred_at: occurred_at,
|
|
32
|
+
version: version
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ==(other)
|
|
37
|
+
other.is_a?(self.class) && event_id == other.event_id
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def hash
|
|
41
|
+
event_id.hash
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_event'
|
|
4
|
+
|
|
5
|
+
module StarGates
|
|
6
|
+
module Events
|
|
7
|
+
# Feature lifecycle events
|
|
8
|
+
class FeatureCreated < BaseEvent; end
|
|
9
|
+
|
|
10
|
+
class FeatureStateChanged < BaseEvent
|
|
11
|
+
def initialize(aggregate_id:, from_state:, to_state:, event_name:, context: {}, **opts)
|
|
12
|
+
super(
|
|
13
|
+
aggregate_id: aggregate_id,
|
|
14
|
+
data: {
|
|
15
|
+
from_state: from_state,
|
|
16
|
+
to_state: to_state,
|
|
17
|
+
event_name: event_name,
|
|
18
|
+
context: context
|
|
19
|
+
},
|
|
20
|
+
**opts
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def from_state = data[:from_state]
|
|
25
|
+
def to_state = data[:to_state]
|
|
26
|
+
def event_name = data[:event_name]
|
|
27
|
+
def context = data[:context]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class FeatureConfigurationChanged < BaseEvent
|
|
31
|
+
def initialize(aggregate_id:, configuration_changes:, **opts)
|
|
32
|
+
super(
|
|
33
|
+
aggregate_id: aggregate_id,
|
|
34
|
+
data: { configuration_changes: configuration_changes },
|
|
35
|
+
**opts
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def configuration_changes = data[:configuration_changes]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class FeatureExecuted < BaseEvent
|
|
43
|
+
def initialize(aggregate_id:, context:, result:, execution_time:, **opts)
|
|
44
|
+
super(
|
|
45
|
+
aggregate_id: aggregate_id,
|
|
46
|
+
data: {
|
|
47
|
+
context: context,
|
|
48
|
+
result: result,
|
|
49
|
+
execution_time: execution_time
|
|
50
|
+
},
|
|
51
|
+
**opts
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def context = data[:context]
|
|
56
|
+
def result = data[:result]
|
|
57
|
+
def execution_time = data[:execution_time]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class FeatureEvaluated < BaseEvent
|
|
61
|
+
def initialize(aggregate_id:, context:, eligible:, reason:, **opts)
|
|
62
|
+
super(
|
|
63
|
+
aggregate_id: aggregate_id,
|
|
64
|
+
data: {
|
|
65
|
+
context: context,
|
|
66
|
+
eligible: eligible,
|
|
67
|
+
reason: reason
|
|
68
|
+
},
|
|
69
|
+
**opts
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def context = data[:context]
|
|
74
|
+
def eligible = data[:eligible]
|
|
75
|
+
def reason = data[:reason]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# System-level events
|
|
79
|
+
class GateRegistered < BaseEvent
|
|
80
|
+
def initialize(aggregate_id:, gate_name:, feature_class:, **opts)
|
|
81
|
+
super(
|
|
82
|
+
aggregate_id: aggregate_id,
|
|
83
|
+
data: {
|
|
84
|
+
gate_name: gate_name,
|
|
85
|
+
feature_class: feature_class
|
|
86
|
+
},
|
|
87
|
+
**opts
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def gate_name = data[:gate_name]
|
|
92
|
+
def feature_class = data[:feature_class]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class SystemReset < BaseEvent; end
|
|
96
|
+
end
|
|
97
|
+
end
|