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
data/README.md
ADDED
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
# Lyra
|
|
2
|
+
|
|
3
|
+
**CRUD to Event Sourcing Transformation Engine**
|
|
4
|
+
|
|
5
|
+
*Part of the ORFEAS (Object-Relational to Event-Sourcing Architecture) Framework*
|
|
6
|
+
|
|
7
|
+
## Author
|
|
8
|
+
|
|
9
|
+
**Michail Pantelelis** (mpantel@aegean.gr)
|
|
10
|
+
PhD Candidate, University of the Aegean
|
|
11
|
+
Department of Information and Communication Systems Engineering
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
1. [Overview](#overview)
|
|
18
|
+
2. [The Problem](#the-problem)
|
|
19
|
+
3. [The Solution: ORFEAS Framework](#the-solution-orfeas-framework)
|
|
20
|
+
4. [Key Features](#key-features)
|
|
21
|
+
5. [Architecture](#architecture)
|
|
22
|
+
6. [Components](#components)
|
|
23
|
+
7. [Installation](#installation)
|
|
24
|
+
8. [Quick Start](#quick-start)
|
|
25
|
+
9. [Usage Guide](#usage-guide)
|
|
26
|
+
10. [Example Application](#example-application)
|
|
27
|
+
11. [Benefits](#benefits)
|
|
28
|
+
12. [Documentation](#documentation)
|
|
29
|
+
13. [Research Foundation](#research-foundation)
|
|
30
|
+
14. [Development Methodology](#development-methodology)
|
|
31
|
+
15. [Contributing](#contributing)
|
|
32
|
+
16. [Citation](#citation)
|
|
33
|
+
17. [Support](#support)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Overview
|
|
38
|
+
|
|
39
|
+
Lyra is a Rails engine that enables **gradual, non-intrusive transformation** of traditional CRUD (Create, Read, Update, Delete) operations into Event Sourcing architectures, with built-in privacy compliance capabilities. It is the practical implementation of the **ORFEAS (Object-Relational to Event-Sourcing Architecture)** framework, developed as part of a PhD thesis addressing the fundamental challenge of bridging 40+ years of ORM dominance with modern event-driven, GDPR-compliant application architectures.
|
|
40
|
+
|
|
41
|
+
### What Makes Lyra Unique?
|
|
42
|
+
|
|
43
|
+
- **Dual-Mode Operation**: Monitor existing applications non-intrusively, then gradually transform to full event sourcing
|
|
44
|
+
- **Formal Mathematical Foundation**: Built on Petri nets (P/T nets for verification, CPNs for advanced modeling) and matrix analysis for rigorous CRUD-to-event mapping
|
|
45
|
+
- **Privacy-First Design**: Integrated Privacy Attribute Matrix (PAM) for GDPR compliance
|
|
46
|
+
- **Zero-Downtime Migration**: Maintain CRUD as a safety net while transitioning to event sourcing
|
|
47
|
+
- **Research-Backed**: Grounded in peer-reviewed research on event sourcing patterns and privacy preservation
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## The Problem
|
|
52
|
+
|
|
53
|
+
Modern enterprise systems face a critical architectural challenge:
|
|
54
|
+
|
|
55
|
+
### The Legacy Dilemma
|
|
56
|
+
- **40+ Years of ORM Investment**: Decades of business logic encoded in Object-Relational Mapping systems
|
|
57
|
+
- **CRUD Limitations**: State-based systems lose behavioral history and temporal context
|
|
58
|
+
- **Privacy Requirements**: GDPR mandates comprehensive data lineage and processing transparency
|
|
59
|
+
- **Migration Risk**: Complete rewrites from CRUD to Event Sourcing are expensive and risky
|
|
60
|
+
|
|
61
|
+
### The Event Sourcing Promise
|
|
62
|
+
- **Complete Audit Trails**: Every state change recorded as an immutable event
|
|
63
|
+
- **Temporal Queries**: Query system state at any point in time
|
|
64
|
+
- **Behavioral Analysis**: Understand *how* and *why* state changes occurred
|
|
65
|
+
- **Microservices Alignment**: Natural fit for distributed, event-driven architectures
|
|
66
|
+
|
|
67
|
+
### The Gap
|
|
68
|
+
**There is no formal, proven method for gradually migrating CRUD systems to event sourcing while maintaining operational safety and privacy compliance.**
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## The Solution: ORFEAS Framework
|
|
73
|
+
|
|
74
|
+
The **ORFEAS (Object-Relational to Event-Sourcing Architecture)** framework provides:
|
|
75
|
+
|
|
76
|
+
1. **Formal Mathematical Models**: Petri nets map CRUD operations to event sequences with formal verification (P/T nets for structural proofs, CPNs optional for data modeling)
|
|
77
|
+
2. **Privacy Compliance**: Privacy Attribute Matrix (PAM) ensures GDPR compliance throughout the transformation
|
|
78
|
+
3. **Gradual Migration Path**: Two operational modes support safe, incremental transition
|
|
79
|
+
4. **Dual-View Analysis**: Compare CRUD and event-sourced views to verify correctness
|
|
80
|
+
5. **Practical Tooling**: Lyra implements ORFEAS as a production-ready Rails engine
|
|
81
|
+
|
|
82
|
+
### Theoretical Foundation
|
|
83
|
+
|
|
84
|
+
ORFEAS is built on three pillars:
|
|
85
|
+
|
|
86
|
+
1. **Petri Nets**: Model CRUD-to-event transformations (P/T nets for verification proofs; CPNs with tokens, guards, and arc expressions for advanced data modeling)
|
|
87
|
+
2. **Matrix Analysis**: Complementary linear algebra approach for causation and lineage tracking
|
|
88
|
+
3. **Privacy Attribute Matrix (PAM)**: DSL for declaring field-level privacy policies and transformations
|
|
89
|
+
|
|
90
|
+
[Read the complete theoretical foundation →](docs/ORFEAS_FRAMEWORK_OVERVIEW.md)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Key Features
|
|
95
|
+
|
|
96
|
+
### Operational Features
|
|
97
|
+
- ✅ **Non-intrusive Monitoring** - Works with existing Rails applications without code changes
|
|
98
|
+
- ✅ **Dual Operational Modes** - Monitor or hijack CRUD operations
|
|
99
|
+
- ✅ **Automatic Event Mapping** - CRUD operations automatically mapped to domain events
|
|
100
|
+
- ✅ **State Reconstruction** - Rebuild state from event streams
|
|
101
|
+
- ✅ **Dual-View Dashboard** - Compare CRUD state vs event-sourced state
|
|
102
|
+
|
|
103
|
+
### Technical Features
|
|
104
|
+
- ✅ **Rails Event Store Integration** - Built on proven event sourcing infrastructure
|
|
105
|
+
- ✅ **Pluggable Event Backends** - Support for custom event storage (Kafka, EventStoreDB)
|
|
106
|
+
- ✅ **Aggregate Support** - Custom domain aggregates for complex business logic
|
|
107
|
+
- ✅ **Command/Query Separation** - CQRS-ready architecture
|
|
108
|
+
- ✅ **Event Versioning** - Support for event schema evolution
|
|
109
|
+
|
|
110
|
+
### Privacy & Compliance
|
|
111
|
+
- ✅ **Privacy Attribute Matrix (PAM)** - Built-in PAM DSL for defining privacy policies
|
|
112
|
+
- ✅ **GDPR Compliance** - Purpose-based access control, consent management, retention policies
|
|
113
|
+
- ✅ **PII Detection & Transformation** - Automatic PII field identification and transformation
|
|
114
|
+
- ✅ **Audit Trails** - Complete lineage tracking for regulatory compliance
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Architecture
|
|
119
|
+
|
|
120
|
+
### High-Level Architecture
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
124
|
+
│ Rails Application │
|
|
125
|
+
├─────────────────────────────────────────────────────────────┤
|
|
126
|
+
│ │
|
|
127
|
+
│ ActiveRecord Models (with monitor_with_lyra) │
|
|
128
|
+
│ │ │
|
|
129
|
+
│ ▼ │
|
|
130
|
+
│ ┌──────────────────┐ │
|
|
131
|
+
│ │ CRUD Interceptor │ │
|
|
132
|
+
│ └────────┬─────────┘ │
|
|
133
|
+
│ │ │
|
|
134
|
+
│ ┌────┴────┐ │
|
|
135
|
+
│ │ │ │
|
|
136
|
+
│ MONITOR HIJACK │
|
|
137
|
+
│ MODE MODE │
|
|
138
|
+
│ │ │ │
|
|
139
|
+
│ ▼ ▼ │
|
|
140
|
+
│ ┌─────┐ ┌─────────┐ │
|
|
141
|
+
│ │ Log │ │ Command │ │
|
|
142
|
+
│ │Event│ │ Handler │ │
|
|
143
|
+
│ └──┬──┘ └────┬────┘ │
|
|
144
|
+
│ │ │ │
|
|
145
|
+
│ └─────┬─────┘ │
|
|
146
|
+
│ ▼ │
|
|
147
|
+
│ ┌─────────────────┐ │
|
|
148
|
+
│ │ Event Store │ │
|
|
149
|
+
│ │ (Rails Event │ │
|
|
150
|
+
│ │ Store) │ │
|
|
151
|
+
│ └─────────────────┘ │
|
|
152
|
+
│ │ │
|
|
153
|
+
│ ▼ │
|
|
154
|
+
│ ┌─────────────────┐ │
|
|
155
|
+
│ │ Aggregates & │ │
|
|
156
|
+
│ │ Projections │ │
|
|
157
|
+
│ └─────────────────┘ │
|
|
158
|
+
│ │
|
|
159
|
+
└─────────────────────────────────────────────────────────────┘
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Operational Modes
|
|
163
|
+
|
|
164
|
+
#### 1. Monitor Mode (Non-intrusive)
|
|
165
|
+
- Observes CRUD operations and logs them as domain events
|
|
166
|
+
- **No changes** to application behavior
|
|
167
|
+
- Perfect for **analysis and planning** migration
|
|
168
|
+
- Zero risk to production systems
|
|
169
|
+
|
|
170
|
+
#### 2. Hijack Mode (Transformative)
|
|
171
|
+
- Intercepts CRUD operations and routes through event sourcing
|
|
172
|
+
- Replaces traditional relational backend
|
|
173
|
+
- Maintains CRUD interface for **backward compatibility**
|
|
174
|
+
- Full event sourcing benefits
|
|
175
|
+
|
|
176
|
+
[See detailed architecture documentation →](docs/ARCHITECTURE.md)
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Components
|
|
181
|
+
|
|
182
|
+
### Lyra Engine (Core)
|
|
183
|
+
The main Rails engine providing:
|
|
184
|
+
- CRUD interception and monitoring
|
|
185
|
+
- Event mapping and publishing
|
|
186
|
+
- Dual-view analysis
|
|
187
|
+
- Command/Query handlers
|
|
188
|
+
- State reconstruction
|
|
189
|
+
|
|
190
|
+
### PetriFlow Gem
|
|
191
|
+
Complete Petri net and Colored Petri Net library:
|
|
192
|
+
- Core Petri net components (places, transitions, arcs)
|
|
193
|
+
- Colored extensions (guards, arc expressions, token types)
|
|
194
|
+
- Matrix analysis (CRUD-event mapping, causation, lineage)
|
|
195
|
+
- Visualization (GraphViz, Mermaid, ASCII)
|
|
196
|
+
- Formal verification (reachability, boundedness, liveness)
|
|
197
|
+
- Export functionality (PNML, CPN Tools, JSON, YAML)
|
|
198
|
+
|
|
199
|
+
[Read the PetriFlow documentation →](gems/petri_flow/README.md)
|
|
200
|
+
|
|
201
|
+
### PAM DSL Gem
|
|
202
|
+
Privacy Attribute Matrix Domain-Specific Language:
|
|
203
|
+
- Field-level PII classification with sensitivity levels
|
|
204
|
+
- Purpose-based access control aligned with GDPR
|
|
205
|
+
- Retention policies with field-level granularity
|
|
206
|
+
- Consent management with expiration tracking
|
|
207
|
+
- Data transformation for different contexts (display, logging, API)
|
|
208
|
+
|
|
209
|
+
[Read the PAM DSL documentation →](gems/pam_dsl/README.md)
|
|
210
|
+
|
|
211
|
+
### Monorepo Structure
|
|
212
|
+
|
|
213
|
+
This repository is organized as a monorepo:
|
|
214
|
+
- **`/` (root)** - Lyra Rails engine
|
|
215
|
+
- **`gems/petri_flow/`** - PetriFlow Petri net library
|
|
216
|
+
- **`gems/pam_dsl/`** - PAM DSL privacy policy language
|
|
217
|
+
- **`docs/`** - Theoretical documentation
|
|
218
|
+
- **`docs-site/`** - Jekyll documentation site
|
|
219
|
+
- **`examples/`** - Example applications
|
|
220
|
+
|
|
221
|
+
[See complete monorepo structure →](docs/MONOREPO.md)
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Installation
|
|
226
|
+
|
|
227
|
+
### Prerequisites
|
|
228
|
+
|
|
229
|
+
- Ruby 3.4.5+ (tested up to Ruby 4.0) and Rails 8.0+
|
|
230
|
+
- PostgreSQL 14+ (recommended for Rails Event Store)
|
|
231
|
+
- Bundler
|
|
232
|
+
|
|
233
|
+
### Add to Gemfile
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
gem 'lyra', path: 'path/to/lyra' # or from git/rubygems when published
|
|
237
|
+
gem 'pam_dsl', '~> 0.1.0' # Privacy Attribute Matrix DSL
|
|
238
|
+
gem 'petri_flow', '~> 0.1.0' # Petri net library
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Install Dependencies
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
bundle install
|
|
245
|
+
rails generate rails_event_store_active_record:migration
|
|
246
|
+
rails db:migrate
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Quick Start
|
|
252
|
+
|
|
253
|
+
### 1. Configure Lyra
|
|
254
|
+
|
|
255
|
+
Create `config/initializers/lyra.rb`:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
Lyra.configure do |config|
|
|
259
|
+
# Start in monitor mode (non-intrusive)
|
|
260
|
+
config.mode = :monitor
|
|
261
|
+
|
|
262
|
+
# Configure event store
|
|
263
|
+
config.event_backend = :rails_event_store
|
|
264
|
+
config.event_store = RailsEventStore::Client.new
|
|
265
|
+
|
|
266
|
+
# Optional: User tracking for audit trails (see User Tracking section below)
|
|
267
|
+
# config.metadata_proc = ->(record, operation) { { user_id: Current.user&.id } }
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 2. Monitor a Model
|
|
272
|
+
|
|
273
|
+
Add monitoring to your ActiveRecord models:
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
class Order < ApplicationRecord
|
|
277
|
+
# Enable Lyra monitoring
|
|
278
|
+
monitor_with_lyra
|
|
279
|
+
|
|
280
|
+
# Your existing code continues to work normally
|
|
281
|
+
validates :total, presence: true
|
|
282
|
+
belongs_to :customer
|
|
283
|
+
end
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 3. Observe Events
|
|
287
|
+
|
|
288
|
+
All CRUD operations are now logged as events:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
order = Order.create!(total: 100, customer: customer)
|
|
292
|
+
# => Publishes OrderCreated event
|
|
293
|
+
|
|
294
|
+
order.update!(total: 150)
|
|
295
|
+
# => Publishes OrderUpdated event
|
|
296
|
+
|
|
297
|
+
order.destroy!
|
|
298
|
+
# => Publishes OrderDestroyed event
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 4. Analyze State
|
|
302
|
+
|
|
303
|
+
Compare CRUD vs event-sourced views:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
comparison = Lyra::DualView.new(Order, order.id).compare
|
|
307
|
+
|
|
308
|
+
puts comparison[:differences]
|
|
309
|
+
# => { no_differences: true }
|
|
310
|
+
|
|
311
|
+
# View complete audit trail
|
|
312
|
+
audit = Lyra::DualView.new(Order, order.id).audit_trail
|
|
313
|
+
# => [{ timestamp: ..., operation: :created, changes: {...} }, ...]
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
[Continue to full usage guide →](#usage-guide)
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Usage Guide
|
|
321
|
+
|
|
322
|
+
### Basic Model Monitoring
|
|
323
|
+
|
|
324
|
+
Add monitoring to any ActiveRecord model:
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
class Order < ApplicationRecord
|
|
328
|
+
monitor_with_lyra
|
|
329
|
+
|
|
330
|
+
validates :total, presence: true
|
|
331
|
+
belongs_to :customer
|
|
332
|
+
end
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Privacy Policies with PAM DSL
|
|
336
|
+
|
|
337
|
+
Define privacy policies for your models:
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
# config/privacy_policies.rb
|
|
341
|
+
PamDsl.define_policy :order_system do
|
|
342
|
+
# Define PII fields
|
|
343
|
+
field :email, type: :email, sensitivity: :internal do
|
|
344
|
+
allow_for :order_processing, :communication
|
|
345
|
+
transform :display { |v| "#{v[0]}***@#{v.split('@').last}" }
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
field :credit_card, type: :credit_card, sensitivity: :restricted do
|
|
349
|
+
allow_for :payment_processing
|
|
350
|
+
transform :display { |v| "****-****-****-#{v[-4..]}" }
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Define processing purposes
|
|
354
|
+
purpose :order_processing do
|
|
355
|
+
basis :contract
|
|
356
|
+
requires :email
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
purpose :marketing do
|
|
360
|
+
basis :consent
|
|
361
|
+
requires :email
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Configure retention
|
|
365
|
+
retention do
|
|
366
|
+
for_model 'Order' do
|
|
367
|
+
keep_for 7.years
|
|
368
|
+
on_expiry :anonymize
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Apply to model
|
|
374
|
+
class Order < ApplicationRecord
|
|
375
|
+
monitor_with_lyra privacy_policy: :order_system
|
|
376
|
+
end
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
[See complete PAM DSL guide →](gems/pam_dsl/docs/PAM_DSL_INTEGRATION.md)
|
|
380
|
+
|
|
381
|
+
### Schema Validation (Strict Mode)
|
|
382
|
+
|
|
383
|
+
Lyra can enforce schema consistency to prevent silent breaking changes in production:
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
# config/initializers/lyra.rb
|
|
387
|
+
Lyra.configure do |config|
|
|
388
|
+
config.mode = :monitor
|
|
389
|
+
|
|
390
|
+
# Enable strict schema validation (recommended for production)
|
|
391
|
+
config.strict_schema = Rails.env.production?
|
|
392
|
+
|
|
393
|
+
# Custom schema storage path (optional, defaults to db/lyra_schemas/)
|
|
394
|
+
config.schema_path = Rails.root.join('db/lyra_schemas')
|
|
395
|
+
|
|
396
|
+
config.monitor_model User
|
|
397
|
+
config.monitor_model Order
|
|
398
|
+
end
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
#### Schema Management Rake Tasks
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
# Generate initial schema from monitored models
|
|
405
|
+
rake lyra:schema:create
|
|
406
|
+
|
|
407
|
+
# Check for schema changes without updating
|
|
408
|
+
rake lyra:schema:verify
|
|
409
|
+
|
|
410
|
+
# Create new schema version (after migrations)
|
|
411
|
+
rake lyra:schema:update
|
|
412
|
+
|
|
413
|
+
# Display model → event mappings
|
|
414
|
+
rake lyra:schema:report
|
|
415
|
+
|
|
416
|
+
# Show schema version history
|
|
417
|
+
rake lyra:schema:history
|
|
418
|
+
|
|
419
|
+
# Compare two schema versions
|
|
420
|
+
rake lyra:schema:diff[1,2]
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### Schema Validation Workflow
|
|
424
|
+
|
|
425
|
+
1. **Development**: Run `rake lyra:schema:create` to generate initial schema
|
|
426
|
+
2. **After migrations**: Run `rake lyra:schema:verify` to detect changes
|
|
427
|
+
3. **If changes detected**: Run `rake lyra:schema:update` to create new version
|
|
428
|
+
4. **Production**: With `strict_schema = true`, app fails to start if schema changes
|
|
429
|
+
|
|
430
|
+
#### Schema Change Severity Levels
|
|
431
|
+
|
|
432
|
+
| Severity | Changes | Impact |
|
|
433
|
+
|----------|---------|--------|
|
|
434
|
+
| **BREAKING** | model_removed, column_removed, column_type_changed | Requires new schema version |
|
|
435
|
+
| **WARNING** | column_nullable_changed, pii_field_added | Review recommended |
|
|
436
|
+
| **INFO** | model_added, column_added | Documentation update |
|
|
437
|
+
|
|
438
|
+
### User Tracking (metadata_proc)
|
|
439
|
+
|
|
440
|
+
Lyra can capture custom metadata with each event, such as the current user, for audit trails and GDPR compliance.
|
|
441
|
+
|
|
442
|
+
#### Configuration
|
|
443
|
+
|
|
444
|
+
```ruby
|
|
445
|
+
# config/initializers/lyra.rb
|
|
446
|
+
Lyra.configure do |config|
|
|
447
|
+
config.mode = :monitor
|
|
448
|
+
config.event_backend = :rails_event_store
|
|
449
|
+
|
|
450
|
+
# Custom metadata proc - called for every event
|
|
451
|
+
# Signature: ->(record, operation) { Hash }
|
|
452
|
+
config.metadata_proc = lambda do |record, operation|
|
|
453
|
+
{
|
|
454
|
+
user_id: Current.user&.id,
|
|
455
|
+
user_email: Current.user&.email,
|
|
456
|
+
ip_address: Current.request&.remote_ip,
|
|
457
|
+
source: 'my_app'
|
|
458
|
+
}
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### Using with Rails CurrentAttributes
|
|
464
|
+
|
|
465
|
+
Rails 5.2+ provides `CurrentAttributes` for request-scoped state:
|
|
466
|
+
|
|
467
|
+
```ruby
|
|
468
|
+
# app/models/current.rb
|
|
469
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
470
|
+
attribute :user, :request_id, :request
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# app/controllers/application_controller.rb
|
|
474
|
+
class ApplicationController < ActionController::Base
|
|
475
|
+
before_action :set_current_attributes
|
|
476
|
+
|
|
477
|
+
private
|
|
478
|
+
|
|
479
|
+
def set_current_attributes
|
|
480
|
+
Current.user = current_user
|
|
481
|
+
Current.request_id = request.request_id
|
|
482
|
+
Current.request = request
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### Using with Devise/Warden
|
|
488
|
+
|
|
489
|
+
For Devise-based authentication:
|
|
490
|
+
|
|
491
|
+
```ruby
|
|
492
|
+
config.metadata_proc = lambda do |record, operation|
|
|
493
|
+
user = if defined?(Warden) && Thread.current[:request_env]
|
|
494
|
+
Warden::Proxy.new(Thread.current[:request_env], Warden::Manager.new(nil)).user
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
{ user_id: user&.id, user_email: user&.email }
|
|
498
|
+
end
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
#### Using with Rails Engines (Solidus, Spree, etc.)
|
|
502
|
+
|
|
503
|
+
Many Rails Engines provide their own `Current` class:
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
config.metadata_proc = lambda do |record, operation|
|
|
507
|
+
user_id = nil
|
|
508
|
+
|
|
509
|
+
# Try standard Current
|
|
510
|
+
user_id ||= Current.user&.id if defined?(Current)
|
|
511
|
+
|
|
512
|
+
# Try Spree/Solidus Current
|
|
513
|
+
user_id ||= Spree::Current.user&.id if defined?(Spree::Current)
|
|
514
|
+
|
|
515
|
+
{ user_id: user_id }
|
|
516
|
+
end
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### Metadata in Events
|
|
520
|
+
|
|
521
|
+
The custom metadata is merged with Lyra's built-in metadata:
|
|
522
|
+
|
|
523
|
+
```ruby
|
|
524
|
+
event = Lyra.event_store.read.last
|
|
525
|
+
event.metadata
|
|
526
|
+
# => {
|
|
527
|
+
# user_id: 123,
|
|
528
|
+
# user_email: "admin@example.com",
|
|
529
|
+
# ip_address: "127.0.0.1",
|
|
530
|
+
# source: "my_app",
|
|
531
|
+
# request_id: "abc-123", # Built-in from Current
|
|
532
|
+
# correlation_id: "corr-456", # Built-in from Correlation context
|
|
533
|
+
# causation_id: "cause-789" # Built-in from Causation context
|
|
534
|
+
# }
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Advanced Configuration
|
|
538
|
+
|
|
539
|
+
Use custom event names and aggregates:
|
|
540
|
+
|
|
541
|
+
```ruby
|
|
542
|
+
class Order < ApplicationRecord
|
|
543
|
+
monitor_with_lyra(
|
|
544
|
+
event_prefix: 'Order',
|
|
545
|
+
aggregate_class: OrderAggregate
|
|
546
|
+
)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
class OrderAggregate < Lyra::Aggregate
|
|
550
|
+
def total
|
|
551
|
+
get_state(:total)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
private
|
|
555
|
+
|
|
556
|
+
def apply_order_created(event)
|
|
557
|
+
@id = event.model_id
|
|
558
|
+
set_state(:total, event.attributes['total'])
|
|
559
|
+
set_state(:status, 'pending')
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def apply_order_updated(event)
|
|
563
|
+
event.changes.each { |k, (old, new)| set_state(k.to_sym, new) }
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Event Capture Behavior
|
|
569
|
+
|
|
570
|
+
Lyra uses ActiveRecord `after_commit` callbacks to capture events. This means **all** operations that go through ActiveRecord are captured, regardless of source:
|
|
571
|
+
|
|
572
|
+
| Source | Captured |
|
|
573
|
+
|--------|----------|
|
|
574
|
+
| Web requests | ✓ |
|
|
575
|
+
| Rails console (`rails c`) | ✓ |
|
|
576
|
+
| Background jobs (Sidekiq, etc.) | ✓ |
|
|
577
|
+
| Rake tasks | ✓ |
|
|
578
|
+
| Rails runner scripts | ✓ |
|
|
579
|
+
| Seeds (`db:seed`) | ✓ |
|
|
580
|
+
|
|
581
|
+
**Operations NOT captured** (bypass ActiveRecord callbacks):
|
|
582
|
+
|
|
583
|
+
| Operation | Captured | Alternative |
|
|
584
|
+
|-----------|----------|-------------|
|
|
585
|
+
| `update_columns` / `update_column` | ✗ | Use `update!` |
|
|
586
|
+
| `update_all` / `delete_all` | ✗ | Iterate with `find_each` |
|
|
587
|
+
| `insert_all` / `upsert_all` | ✗ | Use `create!` |
|
|
588
|
+
| Raw SQL (`connection.execute`) | ✗ | Use ActiveRecord methods |
|
|
589
|
+
| `touch` with no callbacks | ✗ | Use `update!` |
|
|
590
|
+
|
|
591
|
+
### Strict Data Access Mode
|
|
592
|
+
|
|
593
|
+
To prevent inconsistencies between CRUD state and event store, enable strict mode to raise errors on callback-bypassing operations:
|
|
594
|
+
|
|
595
|
+
```ruby
|
|
596
|
+
# config/initializers/lyra.rb
|
|
597
|
+
Lyra.configure do |config|
|
|
598
|
+
config.strict_data_access = true # Raises on update_columns, etc.
|
|
599
|
+
# Or only in specific environments:
|
|
600
|
+
config.strict_data_access = Rails.env.development? || Rails.env.test?
|
|
601
|
+
end
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
When enabled, monitored models will raise `Lyra::StrictDataAccessViolation` if you attempt:
|
|
605
|
+
|
|
606
|
+
| Operation | Alternative | Scope |
|
|
607
|
+
|-----------|-------------|-------|
|
|
608
|
+
| `update_columns` | `update!` | Instance |
|
|
609
|
+
| `update_column` | `update!` | Instance |
|
|
610
|
+
| `delete` | `destroy` | Instance |
|
|
611
|
+
| `update_all` | `find_each { \|r\| r.update!(...) }` | Relation |
|
|
612
|
+
| `delete_all` | `find_each(&:destroy)` | Relation |
|
|
613
|
+
| `insert_all` | `records.each { \|attrs\| create!(attrs) }` | Class |
|
|
614
|
+
| `upsert_all` | `find_or_create_by!(...).update!(...)` | Class |
|
|
615
|
+
|
|
616
|
+
```ruby
|
|
617
|
+
user.update_columns(name: "New")
|
|
618
|
+
# => raises Lyra::StrictDataAccessViolation:
|
|
619
|
+
# "update_columns bypasses callbacks and won't be captured by Lyra.
|
|
620
|
+
# Use update! instead. Disable strict_data_access mode if this is intentional."
|
|
621
|
+
|
|
622
|
+
Registration.where(status: "pending").update_all(status: "expired")
|
|
623
|
+
# => raises Lyra::StrictDataAccessViolation:
|
|
624
|
+
# "update_all bypasses callbacks. Use find_each { |r| r.update!(...) } instead."
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### Bypassing Strict Mode
|
|
628
|
+
|
|
629
|
+
For legitimate bulk operations in migrations, seeds, or admin tasks, use `Lyra.without_strict_access`:
|
|
630
|
+
|
|
631
|
+
```ruby
|
|
632
|
+
# Temporarily bypass strict mode for bulk operations
|
|
633
|
+
Lyra.without_strict_access do
|
|
634
|
+
User.where(active: false).delete_all # No error raised
|
|
635
|
+
end
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Note:** Rails association operations (like `dependent: :nullify`) are automatically allowed since they're internal framework operations.
|
|
639
|
+
|
|
640
|
+
### Dual View Analysis
|
|
641
|
+
|
|
642
|
+
Compare CRUD state with event-sourced state:
|
|
643
|
+
|
|
644
|
+
```ruby
|
|
645
|
+
# Single record comparison
|
|
646
|
+
comparison = Lyra::DualView.new(Order, order_id).compare
|
|
647
|
+
|
|
648
|
+
puts comparison[:crud_view]
|
|
649
|
+
# => { exists: true, attributes: {...} }
|
|
650
|
+
|
|
651
|
+
puts comparison[:event_sourced_view]
|
|
652
|
+
# => { exists: true, state: {...}, events_count: 5 }
|
|
653
|
+
|
|
654
|
+
puts comparison[:differences]
|
|
655
|
+
# => { no_differences: true } or differences hash
|
|
656
|
+
|
|
657
|
+
# Audit trail
|
|
658
|
+
audit = Lyra::DualView.new(Order, order_id).audit_trail
|
|
659
|
+
# => Array of all operations with timestamps and changes
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Batch Analysis
|
|
663
|
+
|
|
664
|
+
Find discrepancies across all records:
|
|
665
|
+
|
|
666
|
+
```ruby
|
|
667
|
+
discrepancies = Lyra::DualView.find_discrepancies(Order)
|
|
668
|
+
# => Returns all orders where CRUD state != Event-sourced state
|
|
669
|
+
|
|
670
|
+
analysis = Lyra::StateAnalyzer.analyze(Order, order_id)
|
|
671
|
+
puts analysis[:recommendations]
|
|
672
|
+
# => Actionable recommendations based on state comparison
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Switching to Hijack Mode
|
|
676
|
+
|
|
677
|
+
After analyzing in monitor mode, switch to full event sourcing:
|
|
678
|
+
|
|
679
|
+
```ruby
|
|
680
|
+
# In config/initializers/lyra.rb
|
|
681
|
+
Lyra.configure do |config|
|
|
682
|
+
config.enable_hijack!
|
|
683
|
+
end
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Now CRUD operations are intercepted and routed through event sourcing:
|
|
687
|
+
|
|
688
|
+
```ruby
|
|
689
|
+
order = Order.create!(total: 100, customer: customer)
|
|
690
|
+
# => CreateCommand processed
|
|
691
|
+
# => OrderCreated event published
|
|
692
|
+
# => Aggregate updated
|
|
693
|
+
# => Database record created with event-sourced ID
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Dashboard
|
|
697
|
+
|
|
698
|
+
Lyra provides a web dashboard for monitoring and analyzing event-sourced data.
|
|
699
|
+
|
|
700
|
+
#### Mounting the Dashboard
|
|
701
|
+
|
|
702
|
+
Add to your `config/routes.rb`:
|
|
703
|
+
|
|
704
|
+
```ruby
|
|
705
|
+
Rails.application.routes.draw do
|
|
706
|
+
mount Lyra::Engine, at: "/lyra"
|
|
707
|
+
# ... rest of your routes
|
|
708
|
+
end
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
#### Available Routes
|
|
712
|
+
|
|
713
|
+
| Route | Description |
|
|
714
|
+
|-------|-------------|
|
|
715
|
+
| `/lyra/dashboard` | Main dashboard with monitored models overview |
|
|
716
|
+
| `/lyra/dashboard/model/:class` | Model-specific overview (e.g., `/lyra/dashboard/model/Order`) |
|
|
717
|
+
| `/lyra/dashboard/compare/:class/:id` | Dual View - Compare CRUD state vs Event-sourced state |
|
|
718
|
+
| `/lyra/dashboard/discrepancies/:class` | List records with state discrepancies |
|
|
719
|
+
|
|
720
|
+
**Event Flow Routes:**
|
|
721
|
+
|
|
722
|
+
| Route | Description |
|
|
723
|
+
|-------|-------------|
|
|
724
|
+
| `/lyra/flow/timeline` | Global event timeline |
|
|
725
|
+
| `/lyra/flow/event_chain/:class/:id` | Event chain for a specific record |
|
|
726
|
+
| `/lyra/flow/crud_mapping` | CRUD operation to event type mapping |
|
|
727
|
+
| `/lyra/flow/correlation/:correlation_id` | Events by correlation ID |
|
|
728
|
+
| `/lyra/flow/user_actions/:user_id` | Events by user |
|
|
729
|
+
|
|
730
|
+
**Visualization Routes:**
|
|
731
|
+
|
|
732
|
+
| Route | Description |
|
|
733
|
+
|-------|-------------|
|
|
734
|
+
| `/lyra/visualizations/event_graph` | Interactive event graph (HTML + Mermaid) |
|
|
735
|
+
| `/lyra/visualizations/event_graph.json` | Event graph data with filters |
|
|
736
|
+
| `/lyra/visualizations/entity_graph/:class/:id.json` | Entity lifecycle graph |
|
|
737
|
+
| `/lyra/visualizations/heatmap` | Activity heatmap view |
|
|
738
|
+
| `/lyra/visualizations/heatmap.json` | Heatmap data for time period |
|
|
739
|
+
| `/lyra/visualizations/event_list.json` | List of entities with event counts |
|
|
740
|
+
|
|
741
|
+
**Event Graph Features:**
|
|
742
|
+
|
|
743
|
+
The event graph provides an interactive visualization of entity lifecycles:
|
|
744
|
+
|
|
745
|
+
- **Entity Picker**: Filter by model class, select specific entities to view
|
|
746
|
+
- **Changed Fields**: Each event node displays which fields were modified (📝 prefix)
|
|
747
|
+
- **Link Types**:
|
|
748
|
+
- Solid lines → Same entity lifecycle (chronological order)
|
|
749
|
+
- Dashed lines → Correlated events (same transaction/request)
|
|
750
|
+
- **Node Details**: Includes operation type, timestamp, and changed field names
|
|
751
|
+
|
|
752
|
+
Example node display:
|
|
753
|
+
```
|
|
754
|
+
┌─────────────────────┐
|
|
755
|
+
│ UPDATED │
|
|
756
|
+
│ 14:30:45 │
|
|
757
|
+
│ 📝 status, amount │
|
|
758
|
+
└─────────────────────┘
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
Timestamp fields (`*_at`) are automatically excluded from the changed fields display.
|
|
762
|
+
|
|
763
|
+
**Schema Management Routes:**
|
|
764
|
+
|
|
765
|
+
| Route | Description |
|
|
766
|
+
|-------|-------------|
|
|
767
|
+
| `/lyra/dashboard/schema` | Current schema with pending changes |
|
|
768
|
+
| `/lyra/dashboard/schema/history` | Schema version history |
|
|
769
|
+
| `/lyra/dashboard/schema/:version` | View specific schema version |
|
|
770
|
+
|
|
771
|
+
**Formal Verification Routes (requires PetriFlow):**
|
|
772
|
+
|
|
773
|
+
| Route | Description |
|
|
774
|
+
|-------|-------------|
|
|
775
|
+
| `/lyra/verification` | Verification dashboard |
|
|
776
|
+
| `/lyra/verification.json` | Verification results (JSON) |
|
|
777
|
+
|
|
778
|
+
**Privacy & GDPR Routes:**
|
|
779
|
+
|
|
780
|
+
| Route | Description |
|
|
781
|
+
|-------|-------------|
|
|
782
|
+
| `/lyra/privacy/pii_detection` | Automatic PII field detection |
|
|
783
|
+
| `/lyra/privacy/gdpr_report/:type/:id` | GDPR Article 15 compliant report |
|
|
784
|
+
| `/lyra/privacy/subject/:type/:id` | View all data for a subject |
|
|
785
|
+
|
|
786
|
+
#### Securing Dashboard Access
|
|
787
|
+
|
|
788
|
+
**IMPORTANT**: The Lyra dashboard exposes sensitive data including PII fields, event history, and audit trails. Always restrict access in production.
|
|
789
|
+
|
|
790
|
+
##### Option 1: Authentication Constraint (Recommended)
|
|
791
|
+
|
|
792
|
+
```ruby
|
|
793
|
+
# config/routes.rb
|
|
794
|
+
Rails.application.routes.draw do
|
|
795
|
+
# Restrict to authenticated admin users
|
|
796
|
+
authenticate :user, ->(u) { u.admin? } do
|
|
797
|
+
mount Lyra::Engine, at: "/lyra"
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
##### Option 2: Basic HTTP Authentication
|
|
803
|
+
|
|
804
|
+
```ruby
|
|
805
|
+
# config/routes.rb
|
|
806
|
+
Rails.application.routes.draw do
|
|
807
|
+
mount Lyra::Engine, at: "/lyra", constraints: ->(req) {
|
|
808
|
+
Rack::Auth::Basic::Request.new(req.env).provided? &&
|
|
809
|
+
Rack::Auth::Basic::Request.new(req.env).credentials ==
|
|
810
|
+
[ENV['LYRA_USER'], ENV['LYRA_PASSWORD']]
|
|
811
|
+
}
|
|
812
|
+
end
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
##### Option 3: IP Whitelist
|
|
816
|
+
|
|
817
|
+
```ruby
|
|
818
|
+
# config/routes.rb
|
|
819
|
+
Rails.application.routes.draw do
|
|
820
|
+
constraints ->(req) { ['127.0.0.1', '::1'].include?(req.remote_ip) } do
|
|
821
|
+
mount Lyra::Engine, at: "/lyra"
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
##### Option 4: Custom Middleware
|
|
827
|
+
|
|
828
|
+
```ruby
|
|
829
|
+
# lib/lyra_auth_middleware.rb
|
|
830
|
+
class LyraAuthMiddleware
|
|
831
|
+
def initialize(app)
|
|
832
|
+
@app = app
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
def call(env)
|
|
836
|
+
if env['PATH_INFO'].start_with?('/lyra')
|
|
837
|
+
# Your authentication logic here
|
|
838
|
+
return [403, {}, ['Forbidden']] unless authorized?(env)
|
|
839
|
+
end
|
|
840
|
+
@app.call(env)
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
private
|
|
844
|
+
|
|
845
|
+
def authorized?(env)
|
|
846
|
+
# Implement your authorization logic
|
|
847
|
+
env['warden']&.user&.admin?
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
# config/application.rb
|
|
852
|
+
config.middleware.use LyraAuthMiddleware
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
##### Option 5: Disable in Production
|
|
856
|
+
|
|
857
|
+
```ruby
|
|
858
|
+
# config/routes.rb
|
|
859
|
+
Rails.application.routes.draw do
|
|
860
|
+
unless Rails.env.production?
|
|
861
|
+
mount Lyra::Engine, at: "/lyra"
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
#### Environment-Based Configuration
|
|
867
|
+
|
|
868
|
+
```ruby
|
|
869
|
+
# config/routes.rb
|
|
870
|
+
Rails.application.routes.draw do
|
|
871
|
+
case Rails.env
|
|
872
|
+
when 'development'
|
|
873
|
+
# Open access in development
|
|
874
|
+
mount Lyra::Engine, at: "/lyra"
|
|
875
|
+
when 'staging'
|
|
876
|
+
# Basic auth in staging
|
|
877
|
+
mount Lyra::Engine, at: "/lyra", constraints: LyraBasicAuth
|
|
878
|
+
when 'production'
|
|
879
|
+
# Full authentication in production
|
|
880
|
+
authenticate :user, ->(u) { u.admin? } do
|
|
881
|
+
mount Lyra::Engine, at: "/lyra"
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
### Custom Event Mappers
|
|
888
|
+
|
|
889
|
+
Create custom event mapping logic:
|
|
890
|
+
|
|
891
|
+
```ruby
|
|
892
|
+
class OrderEventMapper < Lyra::EventMapper
|
|
893
|
+
def event_data
|
|
894
|
+
super.merge(
|
|
895
|
+
business_context: {
|
|
896
|
+
total: data[:attributes]['total'],
|
|
897
|
+
items_count: data[:attributes]['items_count']
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
Lyra::EventMapper.register_mapper(Order, OrderEventMapper)
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
### State Reconstruction
|
|
907
|
+
|
|
908
|
+
Rebuild current state from events:
|
|
909
|
+
|
|
910
|
+
```ruby
|
|
911
|
+
# Using projection
|
|
912
|
+
state = Lyra::StateProjection.rebuild_state(Order, order_id)
|
|
913
|
+
# => { total: 150, status: "confirmed", ... }
|
|
914
|
+
|
|
915
|
+
# Using aggregate
|
|
916
|
+
aggregate = OrderAggregate.load(order_id)
|
|
917
|
+
aggregate.total # => 150
|
|
918
|
+
aggregate.status # => "confirmed"
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
### Pluggable Backends
|
|
922
|
+
|
|
923
|
+
Implement custom event storage:
|
|
924
|
+
|
|
925
|
+
```ruby
|
|
926
|
+
class MyEventStore < Lyra::CustomEventStoreAdapter
|
|
927
|
+
def publish(event, stream_name:)
|
|
928
|
+
# Custom implementation (e.g., Kafka, EventStoreDB)
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
def read_stream(stream_name)
|
|
932
|
+
# Custom implementation
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
Lyra.configure do |config|
|
|
937
|
+
config.event_backend = :custom
|
|
938
|
+
config.event_store = MyEventStore.new
|
|
939
|
+
end
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
---
|
|
943
|
+
|
|
944
|
+
## Example Applications
|
|
945
|
+
|
|
946
|
+
### Aegean E-Pay Testbed (Comprehensive)
|
|
947
|
+
|
|
948
|
+
A full-featured university payment system testbed is included in `examples/aegean_epay_testbed/`. This real-world example demonstrates:
|
|
949
|
+
|
|
950
|
+
- **CRUD Operations** - Registrations, payments, refunds with automatic event generation
|
|
951
|
+
- **Dual-View Comparison** - Compare CRUD state vs event-sourced state
|
|
952
|
+
- **Privacy Policy Enforcement** - PAM DSL integration for GDPR compliance
|
|
953
|
+
- **Audit Trail Generation** - Complete history from immutable events
|
|
954
|
+
- **State Machine Workflows** - Refund request lifecycle with events
|
|
955
|
+
- **Performance Benchmarking** - Compare overhead with/without Lyra
|
|
956
|
+
|
|
957
|
+
#### Running the Testbed
|
|
958
|
+
|
|
959
|
+
```bash
|
|
960
|
+
cd examples/aegean_epay_testbed
|
|
961
|
+
bundle install
|
|
962
|
+
rails db:create db:migrate db:seed
|
|
963
|
+
|
|
964
|
+
# Run integration tests
|
|
965
|
+
ruby test_lyra_integration.rb
|
|
966
|
+
|
|
967
|
+
# Or with performance benchmarks
|
|
968
|
+
ruby test_lyra_integration.rb --performance
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
[Explore the Aegean E-Pay testbed →](examples/aegean_epay_testbed/README.md)
|
|
972
|
+
|
|
973
|
+
### Blog App (Getting Started)
|
|
974
|
+
|
|
975
|
+
A simpler example for learning Lyra basics is in `examples/blog_app/`:
|
|
976
|
+
|
|
977
|
+
```bash
|
|
978
|
+
cd examples/blog_app
|
|
979
|
+
bundle install
|
|
980
|
+
rails db:create db:migrate db:seed
|
|
981
|
+
rails console
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
[Explore the blog example →](examples/blog_app/README.md)
|
|
985
|
+
|
|
986
|
+
---
|
|
987
|
+
|
|
988
|
+
## Benefits
|
|
989
|
+
|
|
990
|
+
### For Research
|
|
991
|
+
- **Orthogonal Analysis**: Compare static vs. dynamic views of system state
|
|
992
|
+
- **Behavioral Analysis**: Understand system evolution over time
|
|
993
|
+
- **Migration Patterns**: Study CRUD-to-ES transformation strategies
|
|
994
|
+
- **Formal Verification**: Prove correctness using Petri net theory
|
|
995
|
+
- **Privacy Compliance**: Research privacy-preserving event sourcing patterns
|
|
996
|
+
|
|
997
|
+
### For Development
|
|
998
|
+
- **Zero Downtime Migration**: Gradually transition to event sourcing
|
|
999
|
+
- **Audit Trail**: Complete history of all state changes
|
|
1000
|
+
- **Temporal Queries**: Query state at any point in time
|
|
1001
|
+
- **Debugging**: Replay events to understand issues
|
|
1002
|
+
- **CQRS Support**: Natural separation of commands and queries
|
|
1003
|
+
|
|
1004
|
+
### For Operations
|
|
1005
|
+
- **Non-intrusive**: Deploy without code changes
|
|
1006
|
+
- **Rollback Safety**: Keep CRUD as safety net during transition
|
|
1007
|
+
- **Real-time Monitoring**: Compare CRUD vs event-sourced state
|
|
1008
|
+
- **Validation**: Verify event sourcing correctness before full migration
|
|
1009
|
+
- **Performance Analysis**: Measure overhead before committing
|
|
1010
|
+
|
|
1011
|
+
---
|
|
1012
|
+
|
|
1013
|
+
## Documentation
|
|
1014
|
+
|
|
1015
|
+
### Core Documentation
|
|
1016
|
+
- **[Getting Started Guide](docs/GETTING_STARTED.md)** - Installation and first steps
|
|
1017
|
+
- **[Architecture Overview](docs/ARCHITECTURE.md)** - System design and components
|
|
1018
|
+
- **[Monorepo Structure](docs/MONOREPO.md)** - Repository organization
|
|
1019
|
+
|
|
1020
|
+
### Theoretical Foundation
|
|
1021
|
+
- **[ORFEAS Framework Overview](docs/ORFEAS_FRAMEWORK_OVERVIEW.md)** - Complete framework description
|
|
1022
|
+
- **[Petri Nets Model](gems/petri_flow/docs/THEORETICAL_MODEL_PETRI_NETS.md)** - P/T nets for verification, CPNs for data modeling
|
|
1023
|
+
- **[Matrix Analysis Model](gems/petri_flow/docs/THEORETICAL_MODEL_MATRICES.md)** - Linear algebra approach
|
|
1024
|
+
|
|
1025
|
+
### Privacy & Compliance
|
|
1026
|
+
- **[PAM DSL Integration](gems/pam_dsl/docs/PAM_DSL_INTEGRATION.md)** - Privacy policy DSL guide
|
|
1027
|
+
- **[Privacy Compliance](docs/PRIVACY_COMPLIANCE.md)** - GDPR compliance details
|
|
1028
|
+
|
|
1029
|
+
### Components
|
|
1030
|
+
- **[PetriFlow Export](gems/petri_flow/docs/PETRIFLOW_EXPORT.md)** - Export formats and integration
|
|
1031
|
+
- **[PetriFlow Gem](gems/petri_flow/README.md)** - Petri net library documentation
|
|
1032
|
+
- **[PAM DSL Gem](gems/pam_dsl/README.md)** - Privacy DSL documentation
|
|
1033
|
+
|
|
1034
|
+
### Development
|
|
1035
|
+
- **[Testing Guide](docs/TESTING.md)** - Testing strategy and setup
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
## Research Foundation
|
|
1040
|
+
|
|
1041
|
+
ORFEAS and Lyra are grounded in peer-reviewed research:
|
|
1042
|
+
|
|
1043
|
+
### Published Papers
|
|
1044
|
+
|
|
1045
|
+
**Pantelelis, M., & Kalloniatis, C. (2022).** *Mapping CRUD to Events: Towards an object to event-sourcing framework.*
|
|
1046
|
+
26th Pan-Hellenic Conference on Informatics (PCI 2022).
|
|
1047
|
+
DOI: [10.1145/3575879.3576006](https://doi.org/10.1145/3575879.3576006)
|
|
1048
|
+
|
|
1049
|
+
### Research Areas
|
|
1050
|
+
|
|
1051
|
+
- **Event Sourcing Patterns**: Formal models for CRUD-to-event transformation
|
|
1052
|
+
- **Privacy-Preserving Systems**: GDPR compliance in event-driven architectures
|
|
1053
|
+
- **Petri Net Theory**: P/T nets for workflow verification, CPNs for advanced data modeling
|
|
1054
|
+
- **Matrix Analysis**: Linear algebra approaches to causation and lineage
|
|
1055
|
+
- **Software Architecture**: Gradual migration strategies for legacy systems
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
## Development Methodology
|
|
1060
|
+
|
|
1061
|
+
This proof-of-concept was developed using **AI-assisted code generation** to accelerate implementation while maintaining focus on theoretical contributions.
|
|
1062
|
+
|
|
1063
|
+
### AI Tools Used
|
|
1064
|
+
|
|
1065
|
+
- **Claude Code** (Anthropic's agentic coding tool) - Primary development assistant for code implementation, testing, and documentation
|
|
1066
|
+
- **Claude** (Anthropic) - For architectural discussions and design decisions
|
|
1067
|
+
|
|
1068
|
+
**Important**: All architectural decisions, design patterns, and theoretical foundations were specified by the researcher. AI assistance was used for:
|
|
1069
|
+
- Code implementation following defined specifications
|
|
1070
|
+
- Test generation based on requirements
|
|
1071
|
+
- Documentation formatting and organization
|
|
1072
|
+
- Code review and refactoring
|
|
1073
|
+
|
|
1074
|
+
This methodology enabled rapid prototyping while ensuring the theoretical rigor required for academic research.
|
|
1075
|
+
|
|
1076
|
+
---
|
|
1077
|
+
|
|
1078
|
+
## Contributing
|
|
1079
|
+
|
|
1080
|
+
Lyra is research software for the ORFEAS framework. Contributions and feedback are welcome!
|
|
1081
|
+
|
|
1082
|
+
### Ways to Contribute
|
|
1083
|
+
|
|
1084
|
+
- **Bug Reports**: Submit issues on GitHub
|
|
1085
|
+
- **Feature Requests**: Suggest improvements or new features
|
|
1086
|
+
- **Research Collaboration**: Collaborate on research extensions
|
|
1087
|
+
- **Documentation**: Improve documentation and examples
|
|
1088
|
+
- **Testing**: Add test cases and improve coverage
|
|
1089
|
+
|
|
1090
|
+
### Development Setup
|
|
1091
|
+
|
|
1092
|
+
```bash
|
|
1093
|
+
# Clone the repository
|
|
1094
|
+
git clone https://github.com/mpantel/lyra-engine.git lyra
|
|
1095
|
+
cd lyra
|
|
1096
|
+
|
|
1097
|
+
# Install dependencies
|
|
1098
|
+
bundle install
|
|
1099
|
+
|
|
1100
|
+
# Run tests
|
|
1101
|
+
rake test
|
|
1102
|
+
|
|
1103
|
+
# Build gems
|
|
1104
|
+
cd gems/petri_flow && rake build
|
|
1105
|
+
cd gems/pam_dsl && rake build
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
---
|
|
1109
|
+
|
|
1110
|
+
## Citation
|
|
1111
|
+
|
|
1112
|
+
If you use this software in academic research, please cite:
|
|
1113
|
+
|
|
1114
|
+
### Software Citation
|
|
1115
|
+
|
|
1116
|
+
```bibtex
|
|
1117
|
+
@software{lyra2026,
|
|
1118
|
+
title={Lyra: CRUD to Event Sourcing Transformation Engine},
|
|
1119
|
+
author={Pantelelis, Michail},
|
|
1120
|
+
year={2026},
|
|
1121
|
+
note={Part of ORFEAS Framework},
|
|
1122
|
+
url={https://github.com/mpantel/lyra-engine}
|
|
1123
|
+
}
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
### Research Paper Citation
|
|
1127
|
+
|
|
1128
|
+
```bibtex
|
|
1129
|
+
@inproceedings{pantelelis2022mapping,
|
|
1130
|
+
title={Mapping CRUD to Events: Towards an object to event-sourcing framework},
|
|
1131
|
+
author={Pantelelis, Michail and Kalloniatis, Christos},
|
|
1132
|
+
booktitle={26th Pan-Hellenic Conference on Informatics (PCI 2022)},
|
|
1133
|
+
year={2022},
|
|
1134
|
+
doi={10.1145/3575879.3576006}
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
### References
|
|
1139
|
+
|
|
1140
|
+
- [Rails Event Store](https://railseventstore.org/) - Event Store implementation
|
|
1141
|
+
- [Event Sourcing Pattern](https://martinfowler.com/eaaDev/EventSourcing.html) - Martin Fowler
|
|
1142
|
+
- [CQRS](https://martinfowler.com/bliki/CQRS.html) - Command Query Responsibility Segregation
|
|
1143
|
+
- [Petri Net Theory](http://www.informatik.uni-hamburg.de/TGI/PetriNets/) - Formal foundation
|
|
1144
|
+
- [GDPR Compliance](https://gdpr.eu/) - Privacy regulation
|
|
1145
|
+
|
|
1146
|
+
---
|
|
1147
|
+
|
|
1148
|
+
## Support
|
|
1149
|
+
|
|
1150
|
+
For questions, issues, and collaboration:
|
|
1151
|
+
|
|
1152
|
+
- **Email**: mpantel@aegean.gr
|
|
1153
|
+
- **GitHub Issues**: [Repository Issues](https://github.com/mpantel/lyra-engine/issues)
|
|
1154
|
+
- **Institution**: University of the Aegean, Department of Information and Communication Systems Engineering
|
|
1155
|
+
|
|
1156
|
+
---
|
|
1157
|
+
|
|
1158
|
+
## License
|
|
1159
|
+
|
|
1160
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
1161
|
+
|
|
1162
|
+
---
|
|
1163
|
+
|
|
1164
|
+
**Built with Ruby, Petri Net Theory, and Formal Methods**
|
|
1165
|
+
**Part of the ORFEAS PhD Research Project**
|