orfeas_lyra 0.6.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 +7 -0
- data/CHANGELOG.md +222 -0
- data/LICENSE +21 -0
- data/README.md +1165 -0
- data/Rakefile +728 -0
- data/app/controllers/lyra/application_controller.rb +23 -0
- data/app/controllers/lyra/dashboard_controller.rb +624 -0
- data/app/controllers/lyra/flow_controller.rb +224 -0
- data/app/controllers/lyra/privacy_controller.rb +182 -0
- data/app/views/lyra/dashboard/audit_trail.html.erb +324 -0
- data/app/views/lyra/dashboard/discrepancies.html.erb +125 -0
- data/app/views/lyra/dashboard/event_graph_view.html.erb +525 -0
- data/app/views/lyra/dashboard/heatmap_view.html.erb +155 -0
- data/app/views/lyra/dashboard/index.html.erb +119 -0
- data/app/views/lyra/dashboard/model_overview.html.erb +115 -0
- data/app/views/lyra/dashboard/projections.html.erb +302 -0
- data/app/views/lyra/dashboard/schema.html.erb +283 -0
- data/app/views/lyra/dashboard/schema_history.html.erb +78 -0
- data/app/views/lyra/dashboard/schema_version.html.erb +340 -0
- data/app/views/lyra/dashboard/verification.html.erb +370 -0
- data/app/views/lyra/flow/crud_mapping.html.erb +125 -0
- data/app/views/lyra/flow/timeline.html.erb +260 -0
- data/app/views/lyra/privacy/pii_detection.html.erb +148 -0
- data/app/views/lyra/privacy/policy.html.erb +188 -0
- data/app/workflows/es_async_mode_workflow.rb +80 -0
- data/app/workflows/es_sync_mode_workflow.rb +64 -0
- data/app/workflows/hijack_mode_workflow.rb +54 -0
- data/app/workflows/lifecycle_workflow.rb +43 -0
- data/app/workflows/monitor_mode_workflow.rb +39 -0
- data/config/privacy_policies.rb +273 -0
- data/config/routes.rb +48 -0
- data/lib/lyra/aggregate.rb +131 -0
- data/lib/lyra/associations/event_aware.rb +225 -0
- data/lib/lyra/command.rb +81 -0
- data/lib/lyra/command_handler.rb +155 -0
- data/lib/lyra/configuration.rb +124 -0
- data/lib/lyra/consistency/read_your_writes.rb +91 -0
- data/lib/lyra/correlation.rb +144 -0
- data/lib/lyra/dual_view.rb +231 -0
- data/lib/lyra/engine.rb +67 -0
- data/lib/lyra/event.rb +71 -0
- data/lib/lyra/event_analyzer.rb +135 -0
- data/lib/lyra/event_flow.rb +449 -0
- data/lib/lyra/event_mapper.rb +106 -0
- data/lib/lyra/event_store_adapter.rb +72 -0
- data/lib/lyra/id_generator.rb +137 -0
- data/lib/lyra/interceptors/association_interceptor.rb +169 -0
- data/lib/lyra/interceptors/crud_interceptor.rb +543 -0
- data/lib/lyra/privacy/gdpr_compliance.rb +161 -0
- data/lib/lyra/privacy/pii_detector.rb +85 -0
- data/lib/lyra/privacy/pii_masker.rb +66 -0
- data/lib/lyra/privacy/policy_integration.rb +253 -0
- data/lib/lyra/projection.rb +94 -0
- data/lib/lyra/projections/async_projection_job.rb +63 -0
- data/lib/lyra/projections/cached_projection.rb +322 -0
- data/lib/lyra/projections/cached_relation.rb +757 -0
- data/lib/lyra/projections/event_store_reader.rb +127 -0
- data/lib/lyra/projections/model_projection.rb +143 -0
- data/lib/lyra/schema/diff.rb +331 -0
- data/lib/lyra/schema/event_class_registrar.rb +63 -0
- data/lib/lyra/schema/generator.rb +190 -0
- data/lib/lyra/schema/reporter.rb +188 -0
- data/lib/lyra/schema/store.rb +156 -0
- data/lib/lyra/schema/validator.rb +100 -0
- data/lib/lyra/strict_data_access.rb +363 -0
- data/lib/lyra/verification/crud_lifecycle_workflow.rb +456 -0
- data/lib/lyra/verification/workflow_generator.rb +540 -0
- data/lib/lyra/version.rb +3 -0
- data/lib/lyra/visualization/activity_heatmap.rb +215 -0
- data/lib/lyra/visualization/event_graph.rb +310 -0
- data/lib/lyra/visualization/timeline.rb +398 -0
- data/lib/lyra.rb +150 -0
- data/lib/tasks/dist.rake +391 -0
- data/lib/tasks/gems.rake +185 -0
- data/lib/tasks/lyra_schema.rake +231 -0
- data/lib/tasks/lyra_workflows.rake +452 -0
- data/lib/tasks/public_release.rake +351 -0
- data/lib/tasks/stats.rake +175 -0
- data/lib/tasks/testbed.rake +479 -0
- data/lib/tasks/version.rake +159 -0
- metadata +221 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lyra
|
|
4
|
+
module Verification
|
|
5
|
+
# Generates Petri net workflows by introspecting Lyra's actual implementation.
|
|
6
|
+
# Uses metaprogramming to analyze callbacks, modes, and model configurations.
|
|
7
|
+
class WorkflowGenerator
|
|
8
|
+
attr_reader :analysis, :options
|
|
9
|
+
|
|
10
|
+
AVAILABLE_MODES = [:monitor, :hijack, :es_sync, :es_async].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@options = options
|
|
14
|
+
@analysis = {
|
|
15
|
+
modes: [],
|
|
16
|
+
callbacks: {},
|
|
17
|
+
models: [],
|
|
18
|
+
event_types: []
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Analyze Lyra implementation and generate workflow
|
|
23
|
+
# @param mode [Symbol, nil] Specific mode to generate workflow for, or nil for all
|
|
24
|
+
def generate!(mode: nil)
|
|
25
|
+
analyze_modes
|
|
26
|
+
analyze_callbacks
|
|
27
|
+
analyze_monitored_models
|
|
28
|
+
analyze_event_types
|
|
29
|
+
|
|
30
|
+
result = {
|
|
31
|
+
lifecycle_workflow: generate_lifecycle_workflow,
|
|
32
|
+
analysis: @analysis
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if mode
|
|
36
|
+
# Generate workflow for specific mode
|
|
37
|
+
validate_mode!(mode)
|
|
38
|
+
result[:mode_workflow] = generate_workflow_for_mode(mode)
|
|
39
|
+
else
|
|
40
|
+
# Generate workflows for all modes
|
|
41
|
+
result[:mode_workflows] = generate_all_mode_workflows
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def validate_mode!(mode)
|
|
50
|
+
unless AVAILABLE_MODES.include?(mode.to_sym)
|
|
51
|
+
raise ArgumentError, "Invalid mode: #{mode}. Available modes: #{AVAILABLE_MODES.join(', ')}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Analyze available Lyra modes from configuration
|
|
56
|
+
def analyze_modes
|
|
57
|
+
@analysis[:modes] = []
|
|
58
|
+
|
|
59
|
+
# Check which modes are defined in Lyra
|
|
60
|
+
if Lyra.respond_to?(:config)
|
|
61
|
+
config = Lyra.config
|
|
62
|
+
|
|
63
|
+
# Introspect the mode from config
|
|
64
|
+
if config.respond_to?(:mode)
|
|
65
|
+
current_mode = config.mode
|
|
66
|
+
@analysis[:current_mode] = current_mode
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Define all possible modes based on Lyra's design
|
|
71
|
+
@analysis[:modes] = [:monitor, :hijack, :es_sync, :es_async, :disabled]
|
|
72
|
+
|
|
73
|
+
# Check which mode methods exist
|
|
74
|
+
@analysis[:mode_methods] = {
|
|
75
|
+
monitor: Lyra.respond_to?(:monitor_mode?),
|
|
76
|
+
hijack: Lyra.respond_to?(:hijack_mode?),
|
|
77
|
+
event_sourcing: Lyra.respond_to?(:event_sourcing_mode?)
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Analyze ActiveRecord callbacks used by Lyra
|
|
82
|
+
def analyze_callbacks
|
|
83
|
+
@analysis[:callbacks] = {
|
|
84
|
+
create: [],
|
|
85
|
+
update: [],
|
|
86
|
+
destroy: []
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Introspect the Lyra::Monitorable module for callback definitions
|
|
90
|
+
if defined?(Lyra::Monitorable)
|
|
91
|
+
monitorable = Lyra::Monitorable
|
|
92
|
+
|
|
93
|
+
# Check instance methods for callback patterns
|
|
94
|
+
if monitorable.respond_to?(:instance_methods)
|
|
95
|
+
methods = monitorable.instance_methods(false)
|
|
96
|
+
|
|
97
|
+
methods.each do |method_name|
|
|
98
|
+
name = method_name.to_s
|
|
99
|
+
if name.include?('create')
|
|
100
|
+
@analysis[:callbacks][:create] << method_name
|
|
101
|
+
elsif name.include?('update')
|
|
102
|
+
@analysis[:callbacks][:update] << method_name
|
|
103
|
+
elsif name.include?('destroy')
|
|
104
|
+
@analysis[:callbacks][:destroy] << method_name
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Check for callback registrations in ClassMethods
|
|
110
|
+
if monitorable.const_defined?(:ClassMethods)
|
|
111
|
+
class_methods = monitorable::ClassMethods.instance_methods(false)
|
|
112
|
+
@analysis[:class_methods] = class_methods
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Analyze the actual callback hooks from ActiveSupport
|
|
117
|
+
@analysis[:callback_hooks] = extract_callback_hooks
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Extract callback hook information from Lyra source
|
|
121
|
+
def extract_callback_hooks
|
|
122
|
+
hooks = { before: [], after: [] }
|
|
123
|
+
|
|
124
|
+
# Find Lyra gem root from the loaded gem spec
|
|
125
|
+
lyra_root = Gem.loaded_specs['lyra']&.gem_dir
|
|
126
|
+
lyra_root ||= File.expand_path('../../../..', __FILE__)
|
|
127
|
+
|
|
128
|
+
# Look for callback definitions in Monitorable
|
|
129
|
+
monitorable_file = File.join(lyra_root, 'lib', 'lyra', 'monitorable.rb')
|
|
130
|
+
|
|
131
|
+
if File.exist?(monitorable_file)
|
|
132
|
+
content = File.read(monitorable_file)
|
|
133
|
+
|
|
134
|
+
# Find after_* callbacks
|
|
135
|
+
content.scan(/after_(create|update|destroy|commit|save)\s+:(\w+)/) do |type, method|
|
|
136
|
+
hooks[:after] << { type: type, method: method }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Find before_* callbacks
|
|
140
|
+
content.scan(/before_(create|update|destroy|save)\s+:(\w+)/) do |type, method|
|
|
141
|
+
hooks[:before] << { type: type, method: method }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
hooks
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Analyze monitored models
|
|
149
|
+
def analyze_monitored_models
|
|
150
|
+
@analysis[:models] = []
|
|
151
|
+
|
|
152
|
+
if Lyra.respond_to?(:config) && Lyra.config.respond_to?(:monitored_models)
|
|
153
|
+
Lyra.config.monitored_models.each do |model_class|
|
|
154
|
+
model_info = {
|
|
155
|
+
name: model_class.name,
|
|
156
|
+
table_name: model_class.respond_to?(:table_name) ? model_class.table_name : nil,
|
|
157
|
+
callbacks: extract_model_callbacks(model_class)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Get Lyra-specific configuration
|
|
161
|
+
if Lyra.config.respond_to?(:model_config)
|
|
162
|
+
config = Lyra.config.model_config(model_class)
|
|
163
|
+
model_info[:event_prefix] = config.event_prefix if config.respond_to?(:event_prefix)
|
|
164
|
+
model_info[:excluded_attributes] = config.excluded_attributes if config.respond_to?(:excluded_attributes)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
@analysis[:models] << model_info
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Extract callbacks registered on a specific model
|
|
173
|
+
def extract_model_callbacks(model_class)
|
|
174
|
+
callbacks = {}
|
|
175
|
+
|
|
176
|
+
[:create, :update, :destroy, :save, :commit].each do |type|
|
|
177
|
+
[:before, :after, :around].each do |timing|
|
|
178
|
+
callback_name = "#{timing}_#{type}"
|
|
179
|
+
if model_class.respond_to?("_#{callback_name}_callbacks")
|
|
180
|
+
chain = model_class.send("_#{callback_name}_callbacks")
|
|
181
|
+
callbacks["#{timing}_#{type}"] = chain.map { |cb| cb.filter.to_s } if chain.any?
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
callbacks
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Analyze event types published by Lyra
|
|
190
|
+
def analyze_event_types
|
|
191
|
+
@analysis[:event_types] = []
|
|
192
|
+
|
|
193
|
+
# Check for event class definitions
|
|
194
|
+
if defined?(Lyra::Events)
|
|
195
|
+
Lyra::Events.constants.each do |const|
|
|
196
|
+
event_class = Lyra::Events.const_get(const)
|
|
197
|
+
if event_class.is_a?(Class)
|
|
198
|
+
@analysis[:event_types] << {
|
|
199
|
+
name: const.to_s,
|
|
200
|
+
class: event_class.name,
|
|
201
|
+
attributes: event_class.respond_to?(:attribute_names) ? event_class.attribute_names : []
|
|
202
|
+
}
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Infer from CRUD operations
|
|
208
|
+
@analysis[:crud_events] = [:Created, :Updated, :Destroyed]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Generate lifecycle workflow from analysis
|
|
212
|
+
def generate_lifecycle_workflow
|
|
213
|
+
return nil unless defined?(PetriFlow)
|
|
214
|
+
|
|
215
|
+
places = [:nonexistent, :created, :persisted, :updated, :destroyed, :deleted]
|
|
216
|
+
transitions = []
|
|
217
|
+
|
|
218
|
+
# Generate transitions based on analyzed callbacks
|
|
219
|
+
hooks = @analysis[:callback_hooks]
|
|
220
|
+
|
|
221
|
+
# CREATE flow
|
|
222
|
+
transitions << {
|
|
223
|
+
name: :create,
|
|
224
|
+
from: :nonexistent,
|
|
225
|
+
to: :created,
|
|
226
|
+
trigger: hooks[:after].find { |h| h[:type] == 'create' }&.dig(:method) || 'after_create'
|
|
227
|
+
}
|
|
228
|
+
transitions << {
|
|
229
|
+
name: :emit_created_event,
|
|
230
|
+
from: :created,
|
|
231
|
+
to: :persisted,
|
|
232
|
+
trigger: 'publish Created event'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# UPDATE flow (cycle)
|
|
236
|
+
transitions << {
|
|
237
|
+
name: :update,
|
|
238
|
+
from: :persisted,
|
|
239
|
+
to: :updated,
|
|
240
|
+
trigger: hooks[:after].find { |h| h[:type] == 'update' }&.dig(:method) || 'after_update'
|
|
241
|
+
}
|
|
242
|
+
transitions << {
|
|
243
|
+
name: :emit_updated_event,
|
|
244
|
+
from: :updated,
|
|
245
|
+
to: :persisted,
|
|
246
|
+
trigger: 'publish Updated event'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# DESTROY flow
|
|
250
|
+
transitions << {
|
|
251
|
+
name: :destroy,
|
|
252
|
+
from: :persisted,
|
|
253
|
+
to: :destroyed,
|
|
254
|
+
trigger: hooks[:after].find { |h| h[:type] == 'destroy' }&.dig(:method) || 'after_destroy'
|
|
255
|
+
}
|
|
256
|
+
transitions << {
|
|
257
|
+
name: :emit_destroyed_event,
|
|
258
|
+
from: :destroyed,
|
|
259
|
+
to: :deleted,
|
|
260
|
+
trigger: 'publish Destroyed event'
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
{
|
|
264
|
+
name: "CRUD Entity Lifecycle (Generated)",
|
|
265
|
+
places: places,
|
|
266
|
+
initial_place: :nonexistent,
|
|
267
|
+
terminal_places: [:deleted],
|
|
268
|
+
transitions: transitions,
|
|
269
|
+
generated_at: Time.current,
|
|
270
|
+
source: "Lyra::Verification::WorkflowGenerator"
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Generate mode workflow from analysis
|
|
275
|
+
def generate_mode_workflow
|
|
276
|
+
return nil unless defined?(PetriFlow)
|
|
277
|
+
|
|
278
|
+
modes = @analysis[:modes].reject { |m| m == :disabled }
|
|
279
|
+
|
|
280
|
+
places = [:idle, :operation_requested]
|
|
281
|
+
transitions = []
|
|
282
|
+
|
|
283
|
+
# Add places for each mode
|
|
284
|
+
modes.each do |mode|
|
|
285
|
+
places << "#{mode}_processing".to_sym
|
|
286
|
+
end
|
|
287
|
+
places += [:event_published, :completed]
|
|
288
|
+
|
|
289
|
+
# Initial transition
|
|
290
|
+
transitions << {
|
|
291
|
+
name: :request_operation,
|
|
292
|
+
from: :idle,
|
|
293
|
+
to: :operation_requested,
|
|
294
|
+
trigger: 'CRUD operation initiated'
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Mode-specific transitions
|
|
298
|
+
modes.each do |mode|
|
|
299
|
+
processing_place = "#{mode}_processing".to_sym
|
|
300
|
+
|
|
301
|
+
transitions << {
|
|
302
|
+
name: "select_#{mode}_mode".to_sym,
|
|
303
|
+
from: :operation_requested,
|
|
304
|
+
to: processing_place,
|
|
305
|
+
trigger: "Lyra.config.mode == :#{mode}"
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
transitions << {
|
|
309
|
+
name: "#{mode}_complete".to_sym,
|
|
310
|
+
from: processing_place,
|
|
311
|
+
to: :event_published,
|
|
312
|
+
trigger: "#{mode} mode processing complete"
|
|
313
|
+
}
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Final transition
|
|
317
|
+
transitions << {
|
|
318
|
+
name: :finalize,
|
|
319
|
+
from: :event_published,
|
|
320
|
+
to: :completed,
|
|
321
|
+
trigger: 'Event stored'
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
{
|
|
325
|
+
name: "Lyra Mode Selection (Generated)",
|
|
326
|
+
places: places,
|
|
327
|
+
initial_place: :idle,
|
|
328
|
+
terminal_places: [:completed],
|
|
329
|
+
transitions: transitions,
|
|
330
|
+
generated_at: Time.current,
|
|
331
|
+
source: "Lyra::Verification::WorkflowGenerator"
|
|
332
|
+
}
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Generate workflows for all available modes
|
|
336
|
+
def generate_all_mode_workflows
|
|
337
|
+
return {} unless defined?(PetriFlow)
|
|
338
|
+
|
|
339
|
+
AVAILABLE_MODES.each_with_object({}) do |mode, workflows|
|
|
340
|
+
workflows[mode] = generate_workflow_for_mode(mode)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Generate workflow for a specific mode
|
|
345
|
+
def generate_workflow_for_mode(mode)
|
|
346
|
+
return nil unless defined?(PetriFlow)
|
|
347
|
+
|
|
348
|
+
case mode.to_sym
|
|
349
|
+
when :monitor
|
|
350
|
+
generate_monitor_workflow
|
|
351
|
+
when :hijack
|
|
352
|
+
generate_hijack_workflow
|
|
353
|
+
when :es_sync
|
|
354
|
+
generate_es_sync_workflow
|
|
355
|
+
when :es_async
|
|
356
|
+
generate_es_async_workflow
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Monitor Mode: Passive observation of CRUD operations
|
|
361
|
+
# CRUD executes normally, events published after the fact
|
|
362
|
+
def generate_monitor_workflow
|
|
363
|
+
{
|
|
364
|
+
name: "Monitor Mode Workflow",
|
|
365
|
+
description: "Passively observes CRUD operations without modification",
|
|
366
|
+
places: [
|
|
367
|
+
:idle,
|
|
368
|
+
:crud_executing,
|
|
369
|
+
:crud_completed,
|
|
370
|
+
:event_building,
|
|
371
|
+
:event_publishing,
|
|
372
|
+
:completed
|
|
373
|
+
],
|
|
374
|
+
initial_place: :idle,
|
|
375
|
+
terminal_places: [:completed],
|
|
376
|
+
transitions: [
|
|
377
|
+
{ name: :receive_crud, from: :idle, to: :crud_executing,
|
|
378
|
+
trigger: "ActiveRecord callback triggered" },
|
|
379
|
+
{ name: :crud_success, from: :crud_executing, to: :crud_completed,
|
|
380
|
+
trigger: "CRUD operation completes successfully" },
|
|
381
|
+
{ name: :build_event, from: :crud_completed, to: :event_building,
|
|
382
|
+
trigger: "Extract changes from model" },
|
|
383
|
+
{ name: :publish_event, from: :event_building, to: :event_publishing,
|
|
384
|
+
trigger: "Lyra::Event.publish" },
|
|
385
|
+
{ name: :store_event, from: :event_publishing, to: :completed,
|
|
386
|
+
trigger: "RailsEventStore.publish" }
|
|
387
|
+
],
|
|
388
|
+
generated_at: Time.current,
|
|
389
|
+
source: "Lyra::Verification::WorkflowGenerator"
|
|
390
|
+
}
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Hijack Mode: Intercepts CRUD, converts to command/event flow
|
|
394
|
+
# Original CRUD is prevented, replaced with event-sourced operation
|
|
395
|
+
def generate_hijack_workflow
|
|
396
|
+
{
|
|
397
|
+
name: "Hijack Mode Workflow",
|
|
398
|
+
description: "Intercepts CRUD operations and converts to event sourcing",
|
|
399
|
+
places: [
|
|
400
|
+
:idle,
|
|
401
|
+
:crud_intercepted,
|
|
402
|
+
:command_created,
|
|
403
|
+
:command_validating,
|
|
404
|
+
:command_valid,
|
|
405
|
+
:event_created,
|
|
406
|
+
:event_stored,
|
|
407
|
+
:projecting,
|
|
408
|
+
:completed
|
|
409
|
+
],
|
|
410
|
+
initial_place: :idle,
|
|
411
|
+
terminal_places: [:completed],
|
|
412
|
+
transitions: [
|
|
413
|
+
{ name: :intercept_crud, from: :idle, to: :crud_intercepted,
|
|
414
|
+
trigger: "before_* callback intercepts operation" },
|
|
415
|
+
{ name: :create_command, from: :crud_intercepted, to: :command_created,
|
|
416
|
+
trigger: "Convert CRUD to Lyra::Command" },
|
|
417
|
+
{ name: :validate_command, from: :command_created, to: :command_validating,
|
|
418
|
+
trigger: "CommandHandler.validate" },
|
|
419
|
+
{ name: :command_passes, from: :command_validating, to: :command_valid,
|
|
420
|
+
trigger: "Validation passes" },
|
|
421
|
+
{ name: :emit_event, from: :command_valid, to: :event_created,
|
|
422
|
+
trigger: "CommandHandler.execute creates event" },
|
|
423
|
+
{ name: :store_event, from: :event_created, to: :event_stored,
|
|
424
|
+
trigger: "RailsEventStore.publish" },
|
|
425
|
+
{ name: :project_state, from: :event_stored, to: :projecting,
|
|
426
|
+
trigger: "Projection.apply(event)" },
|
|
427
|
+
{ name: :projection_complete, from: :projecting, to: :completed,
|
|
428
|
+
trigger: "Model state updated from event" }
|
|
429
|
+
],
|
|
430
|
+
generated_at: Time.current,
|
|
431
|
+
source: "Lyra::Verification::WorkflowGenerator"
|
|
432
|
+
}
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# ES Sync Mode: Full event sourcing with synchronous projection
|
|
436
|
+
# Blocks until projection is complete
|
|
437
|
+
def generate_es_sync_workflow
|
|
438
|
+
{
|
|
439
|
+
name: "Event Sourcing Sync Mode Workflow",
|
|
440
|
+
description: "Full event sourcing with synchronous (blocking) projection",
|
|
441
|
+
places: [
|
|
442
|
+
:idle,
|
|
443
|
+
:command_received,
|
|
444
|
+
:aggregate_loading,
|
|
445
|
+
:aggregate_loaded,
|
|
446
|
+
:command_applying,
|
|
447
|
+
:events_generated,
|
|
448
|
+
:events_storing,
|
|
449
|
+
:events_stored,
|
|
450
|
+
:projecting_sync,
|
|
451
|
+
:projection_complete,
|
|
452
|
+
:completed
|
|
453
|
+
],
|
|
454
|
+
initial_place: :idle,
|
|
455
|
+
terminal_places: [:completed],
|
|
456
|
+
transitions: [
|
|
457
|
+
{ name: :receive_command, from: :idle, to: :command_received,
|
|
458
|
+
trigger: "CommandHandler receives command" },
|
|
459
|
+
{ name: :load_aggregate, from: :command_received, to: :aggregate_loading,
|
|
460
|
+
trigger: "Load aggregate from event stream" },
|
|
461
|
+
{ name: :aggregate_ready, from: :aggregate_loading, to: :aggregate_loaded,
|
|
462
|
+
trigger: "Aggregate hydrated from events" },
|
|
463
|
+
{ name: :apply_command, from: :aggregate_loaded, to: :command_applying,
|
|
464
|
+
trigger: "Aggregate.apply(command)" },
|
|
465
|
+
{ name: :generate_events, from: :command_applying, to: :events_generated,
|
|
466
|
+
trigger: "Domain events created" },
|
|
467
|
+
{ name: :store_events, from: :events_generated, to: :events_storing,
|
|
468
|
+
trigger: "EventStore.append_to_stream" },
|
|
469
|
+
{ name: :events_persisted, from: :events_storing, to: :events_stored,
|
|
470
|
+
trigger: "Events committed to store" },
|
|
471
|
+
{ name: :project_sync, from: :events_stored, to: :projecting_sync,
|
|
472
|
+
trigger: "ModelProjection.apply (synchronous)" },
|
|
473
|
+
{ name: :sync_complete, from: :projecting_sync, to: :projection_complete,
|
|
474
|
+
trigger: "Read model updated" },
|
|
475
|
+
{ name: :finalize, from: :projection_complete, to: :completed,
|
|
476
|
+
trigger: "Response returned to caller" }
|
|
477
|
+
],
|
|
478
|
+
generated_at: Time.current,
|
|
479
|
+
source: "Lyra::Verification::WorkflowGenerator"
|
|
480
|
+
}
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# ES Async Mode: Full event sourcing with asynchronous projection
|
|
484
|
+
# Returns immediately, projection happens in background
|
|
485
|
+
#
|
|
486
|
+
# Uses Petri net FORK pattern to model true parallelism:
|
|
487
|
+
# After events are stored, a single fork transition places tokens in
|
|
488
|
+
# BOTH response_returned AND job_processing simultaneously.
|
|
489
|
+
def generate_es_async_workflow
|
|
490
|
+
{
|
|
491
|
+
name: "Event Sourcing Async Mode Workflow",
|
|
492
|
+
description: "Full event sourcing with asynchronous (non-blocking) projection",
|
|
493
|
+
places: [
|
|
494
|
+
:idle,
|
|
495
|
+
:command_received,
|
|
496
|
+
:aggregate_loading,
|
|
497
|
+
:aggregate_loaded,
|
|
498
|
+
:command_applying,
|
|
499
|
+
:events_generated,
|
|
500
|
+
:events_storing,
|
|
501
|
+
:events_stored,
|
|
502
|
+
:response_returned, # Terminal: caller gets response immediately
|
|
503
|
+
:job_processing, # Background job starts
|
|
504
|
+
:projecting_async,
|
|
505
|
+
:projection_complete # Terminal: projection eventually completes
|
|
506
|
+
],
|
|
507
|
+
initial_place: :idle,
|
|
508
|
+
terminal_places: [:response_returned, :projection_complete],
|
|
509
|
+
transitions: [
|
|
510
|
+
{ name: :receive_command, from: :idle, to: :command_received,
|
|
511
|
+
trigger: "CommandHandler receives command" },
|
|
512
|
+
{ name: :load_aggregate, from: :command_received, to: :aggregate_loading,
|
|
513
|
+
trigger: "Load aggregate from event stream" },
|
|
514
|
+
{ name: :aggregate_ready, from: :aggregate_loading, to: :aggregate_loaded,
|
|
515
|
+
trigger: "Aggregate hydrated from events" },
|
|
516
|
+
{ name: :apply_command, from: :aggregate_loaded, to: :command_applying,
|
|
517
|
+
trigger: "Aggregate.apply(command)" },
|
|
518
|
+
{ name: :generate_events, from: :command_applying, to: :events_generated,
|
|
519
|
+
trigger: "Domain events created" },
|
|
520
|
+
{ name: :store_events, from: :events_generated, to: :events_storing,
|
|
521
|
+
trigger: "EventStore.append_to_stream" },
|
|
522
|
+
{ name: :events_persisted, from: :events_storing, to: :events_stored,
|
|
523
|
+
trigger: "Events committed to store" },
|
|
524
|
+
# FORK: Parallel split - produces tokens in TWO places simultaneously
|
|
525
|
+
# Models async behavior: response returns AND background job starts
|
|
526
|
+
{ name: :async_fork, from: :events_stored, to: [:response_returned, :job_processing],
|
|
527
|
+
trigger: "Enqueue job & return response (parallel)" },
|
|
528
|
+
# Background projection flow (runs independently after fork)
|
|
529
|
+
{ name: :project_async, from: :job_processing, to: :projecting_async,
|
|
530
|
+
trigger: "ModelProjection.apply (async)" },
|
|
531
|
+
{ name: :async_complete, from: :projecting_async, to: :projection_complete,
|
|
532
|
+
trigger: "Read model eventually consistent" }
|
|
533
|
+
],
|
|
534
|
+
generated_at: Time.current,
|
|
535
|
+
source: "Lyra::Verification::WorkflowGenerator"
|
|
536
|
+
}
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
end
|
data/lib/lyra/version.rb
ADDED