e11y 0.1.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.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +69 -0
  4. data/CHANGELOG.md +26 -0
  5. data/CODE_OF_CONDUCT.md +64 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +179 -0
  8. data/Rakefile +37 -0
  9. data/benchmarks/run_all.rb +33 -0
  10. data/config/README.md +83 -0
  11. data/config/loki-local-config.yaml +35 -0
  12. data/config/prometheus.yml +15 -0
  13. data/docker-compose.yml +78 -0
  14. data/docs/00-ICP-AND-TIMELINE.md +483 -0
  15. data/docs/01-SCALE-REQUIREMENTS.md +858 -0
  16. data/docs/ADR-001-architecture.md +2617 -0
  17. data/docs/ADR-002-metrics-yabeda.md +1395 -0
  18. data/docs/ADR-003-slo-observability.md +3337 -0
  19. data/docs/ADR-004-adapter-architecture.md +2385 -0
  20. data/docs/ADR-005-tracing-context.md +1372 -0
  21. data/docs/ADR-006-security-compliance.md +4143 -0
  22. data/docs/ADR-007-opentelemetry-integration.md +1385 -0
  23. data/docs/ADR-008-rails-integration.md +1911 -0
  24. data/docs/ADR-009-cost-optimization.md +2993 -0
  25. data/docs/ADR-010-developer-experience.md +2166 -0
  26. data/docs/ADR-011-testing-strategy.md +1836 -0
  27. data/docs/ADR-012-event-evolution.md +958 -0
  28. data/docs/ADR-013-reliability-error-handling.md +2750 -0
  29. data/docs/ADR-014-event-driven-slo.md +1533 -0
  30. data/docs/ADR-015-middleware-order.md +1061 -0
  31. data/docs/ADR-016-self-monitoring-slo.md +1234 -0
  32. data/docs/API-REFERENCE-L28.md +914 -0
  33. data/docs/COMPREHENSIVE-CONFIGURATION.md +2366 -0
  34. data/docs/IMPLEMENTATION_NOTES.md +2804 -0
  35. data/docs/IMPLEMENTATION_PLAN.md +1971 -0
  36. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +586 -0
  37. data/docs/PLAN.md +148 -0
  38. data/docs/QUICK-START.md +934 -0
  39. data/docs/README.md +296 -0
  40. data/docs/design/00-memory-optimization.md +593 -0
  41. data/docs/guides/MIGRATION-L27-L28.md +692 -0
  42. data/docs/guides/PERFORMANCE-BENCHMARKS.md +434 -0
  43. data/docs/guides/README.md +44 -0
  44. data/docs/prd/01-overview-vision.md +440 -0
  45. data/docs/use_cases/README.md +119 -0
  46. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +813 -0
  47. data/docs/use_cases/UC-002-business-event-tracking.md +1953 -0
  48. data/docs/use_cases/UC-003-pattern-based-metrics.md +1627 -0
  49. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +728 -0
  50. data/docs/use_cases/UC-005-sentry-integration.md +759 -0
  51. data/docs/use_cases/UC-006-trace-context-management.md +905 -0
  52. data/docs/use_cases/UC-007-pii-filtering.md +2648 -0
  53. data/docs/use_cases/UC-008-opentelemetry-integration.md +1153 -0
  54. data/docs/use_cases/UC-009-multi-service-tracing.md +1043 -0
  55. data/docs/use_cases/UC-010-background-job-tracking.md +1018 -0
  56. data/docs/use_cases/UC-011-rate-limiting.md +1906 -0
  57. data/docs/use_cases/UC-012-audit-trail.md +2301 -0
  58. data/docs/use_cases/UC-013-high-cardinality-protection.md +2127 -0
  59. data/docs/use_cases/UC-014-adaptive-sampling.md +1940 -0
  60. data/docs/use_cases/UC-015-cost-optimization.md +735 -0
  61. data/docs/use_cases/UC-016-rails-logger-migration.md +785 -0
  62. data/docs/use_cases/UC-017-local-development.md +867 -0
  63. data/docs/use_cases/UC-018-testing-events.md +1081 -0
  64. data/docs/use_cases/UC-019-tiered-storage-migration.md +562 -0
  65. data/docs/use_cases/UC-020-event-versioning.md +708 -0
  66. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +956 -0
  67. data/docs/use_cases/UC-022-event-registry.md +648 -0
  68. data/docs/use_cases/backlog.md +226 -0
  69. data/e11y.gemspec +76 -0
  70. data/lib/e11y/adapters/adaptive_batcher.rb +207 -0
  71. data/lib/e11y/adapters/audit_encrypted.rb +239 -0
  72. data/lib/e11y/adapters/base.rb +580 -0
  73. data/lib/e11y/adapters/file.rb +224 -0
  74. data/lib/e11y/adapters/in_memory.rb +216 -0
  75. data/lib/e11y/adapters/loki.rb +333 -0
  76. data/lib/e11y/adapters/otel_logs.rb +203 -0
  77. data/lib/e11y/adapters/registry.rb +141 -0
  78. data/lib/e11y/adapters/sentry.rb +230 -0
  79. data/lib/e11y/adapters/stdout.rb +108 -0
  80. data/lib/e11y/adapters/yabeda.rb +370 -0
  81. data/lib/e11y/buffers/adaptive_buffer.rb +339 -0
  82. data/lib/e11y/buffers/base_buffer.rb +40 -0
  83. data/lib/e11y/buffers/request_scoped_buffer.rb +246 -0
  84. data/lib/e11y/buffers/ring_buffer.rb +267 -0
  85. data/lib/e11y/buffers.rb +14 -0
  86. data/lib/e11y/console.rb +122 -0
  87. data/lib/e11y/current.rb +48 -0
  88. data/lib/e11y/event/base.rb +894 -0
  89. data/lib/e11y/event/value_sampling_config.rb +84 -0
  90. data/lib/e11y/events/base_audit_event.rb +43 -0
  91. data/lib/e11y/events/base_payment_event.rb +33 -0
  92. data/lib/e11y/events/rails/cache/delete.rb +21 -0
  93. data/lib/e11y/events/rails/cache/read.rb +23 -0
  94. data/lib/e11y/events/rails/cache/write.rb +22 -0
  95. data/lib/e11y/events/rails/database/query.rb +45 -0
  96. data/lib/e11y/events/rails/http/redirect.rb +21 -0
  97. data/lib/e11y/events/rails/http/request.rb +26 -0
  98. data/lib/e11y/events/rails/http/send_file.rb +21 -0
  99. data/lib/e11y/events/rails/http/start_processing.rb +26 -0
  100. data/lib/e11y/events/rails/job/completed.rb +22 -0
  101. data/lib/e11y/events/rails/job/enqueued.rb +22 -0
  102. data/lib/e11y/events/rails/job/failed.rb +22 -0
  103. data/lib/e11y/events/rails/job/scheduled.rb +23 -0
  104. data/lib/e11y/events/rails/job/started.rb +22 -0
  105. data/lib/e11y/events/rails/log.rb +56 -0
  106. data/lib/e11y/events/rails/view/render.rb +23 -0
  107. data/lib/e11y/events.rb +18 -0
  108. data/lib/e11y/instruments/active_job.rb +201 -0
  109. data/lib/e11y/instruments/rails_instrumentation.rb +141 -0
  110. data/lib/e11y/instruments/sidekiq.rb +175 -0
  111. data/lib/e11y/logger/bridge.rb +205 -0
  112. data/lib/e11y/metrics/cardinality_protection.rb +172 -0
  113. data/lib/e11y/metrics/cardinality_tracker.rb +134 -0
  114. data/lib/e11y/metrics/registry.rb +234 -0
  115. data/lib/e11y/metrics/relabeling.rb +226 -0
  116. data/lib/e11y/metrics.rb +102 -0
  117. data/lib/e11y/middleware/audit_signing.rb +174 -0
  118. data/lib/e11y/middleware/base.rb +140 -0
  119. data/lib/e11y/middleware/event_slo.rb +167 -0
  120. data/lib/e11y/middleware/pii_filter.rb +266 -0
  121. data/lib/e11y/middleware/pii_filtering.rb +280 -0
  122. data/lib/e11y/middleware/rate_limiting.rb +214 -0
  123. data/lib/e11y/middleware/request.rb +163 -0
  124. data/lib/e11y/middleware/routing.rb +157 -0
  125. data/lib/e11y/middleware/sampling.rb +254 -0
  126. data/lib/e11y/middleware/slo.rb +168 -0
  127. data/lib/e11y/middleware/trace_context.rb +131 -0
  128. data/lib/e11y/middleware/validation.rb +118 -0
  129. data/lib/e11y/middleware/versioning.rb +132 -0
  130. data/lib/e11y/middleware.rb +12 -0
  131. data/lib/e11y/pii/patterns.rb +90 -0
  132. data/lib/e11y/pii.rb +13 -0
  133. data/lib/e11y/pipeline/builder.rb +155 -0
  134. data/lib/e11y/pipeline/zone_validator.rb +110 -0
  135. data/lib/e11y/pipeline.rb +12 -0
  136. data/lib/e11y/presets/audit_event.rb +65 -0
  137. data/lib/e11y/presets/debug_event.rb +34 -0
  138. data/lib/e11y/presets/high_value_event.rb +51 -0
  139. data/lib/e11y/presets.rb +19 -0
  140. data/lib/e11y/railtie.rb +138 -0
  141. data/lib/e11y/reliability/circuit_breaker.rb +216 -0
  142. data/lib/e11y/reliability/dlq/file_storage.rb +277 -0
  143. data/lib/e11y/reliability/dlq/filter.rb +117 -0
  144. data/lib/e11y/reliability/retry_handler.rb +207 -0
  145. data/lib/e11y/reliability/retry_rate_limiter.rb +117 -0
  146. data/lib/e11y/sampling/error_spike_detector.rb +225 -0
  147. data/lib/e11y/sampling/load_monitor.rb +161 -0
  148. data/lib/e11y/sampling/stratified_tracker.rb +92 -0
  149. data/lib/e11y/sampling/value_extractor.rb +82 -0
  150. data/lib/e11y/self_monitoring/buffer_monitor.rb +79 -0
  151. data/lib/e11y/self_monitoring/performance_monitor.rb +97 -0
  152. data/lib/e11y/self_monitoring/reliability_monitor.rb +146 -0
  153. data/lib/e11y/slo/event_driven.rb +150 -0
  154. data/lib/e11y/slo/tracker.rb +119 -0
  155. data/lib/e11y/version.rb +9 -0
  156. data/lib/e11y.rb +283 -0
  157. metadata +452 -0
@@ -0,0 +1,708 @@
1
+ # UC-020: Event Versioning & Schema Evolution
2
+
3
+ **Status:** Core Feature (MVP)
4
+ **Complexity:** Intermediate
5
+ **Setup Time:** 15-30 minutes
6
+ **Target Users:** Backend Developers, API Designers, Platform Engineers
7
+
8
+ ---
9
+
10
+ ## 📋 Overview
11
+
12
+ ### Problem Statement
13
+
14
+ **Current Pain Points:**
15
+
16
+ 1. **Breaking schema changes break production**
17
+ - Add required field → old code crashes
18
+ - Remove field → downstream consumers fail
19
+ - Rename field → data loss
20
+
21
+ 2. **No backward compatibility**
22
+ - Can't deploy new event schema without coordinating all consumers
23
+ - Microservices must upgrade simultaneously (impossible!)
24
+ - Rollback is dangerous (data already sent with new schema)
25
+
26
+ 3. **No schema evolution strategy**
27
+ - How to deprecate old fields?
28
+ - How to support multiple versions simultaneously?
29
+ - How to migrate consumers gradually?
30
+
31
+ ### E11y Solution
32
+
33
+ **Event Versioning with Backward Compatibility:**
34
+
35
+ - Events have explicit version numbers
36
+ - Multiple versions can coexist
37
+ - Automatic version detection from payload
38
+ - Gradual migration path
39
+
40
+ **Result:** Safe schema evolution without breaking production.
41
+
42
+ ---
43
+
44
+ ## 🎯 Use Case Scenarios
45
+
46
+ ### Scenario 1: Adding Required Field (Breaking Change)
47
+
48
+ **Problem:** Need to add required `currency` field to `OrderPaid` event.
49
+
50
+ **Without versioning (BREAKS PRODUCTION!):**
51
+ ```ruby
52
+ class OrderPaid < E11y::Event::Base
53
+ schema do
54
+ required(:order_id).filled(:string)
55
+ required(:amount).filled(:decimal)
56
+ required(:currency).filled(:string) # ← NEW! Breaks old code!
57
+ end
58
+ end
59
+
60
+ # Old code (deployed):
61
+ OrderPaid.track(order_id: '123', amount: 99.99)
62
+ # ❌ ValidationError: currency is required
63
+ ```
64
+
65
+ **With versioning (SAFE!):**
66
+ ```ruby
67
+ # V1: Original version (no version suffix!)
68
+ class OrderPaid < E11y::Event::Base
69
+ version 1 # Optional for v1, but recommended
70
+ event_name 'order.paid'
71
+
72
+ schema do
73
+ required(:order_id).filled(:string)
74
+ required(:amount).filled(:decimal)
75
+ # No currency (backward compatible)
76
+ end
77
+ end
78
+
79
+ # V2: New version with currency (suffix V2)
80
+ class OrderPaidV2 < E11y::Event::Base
81
+ version 2
82
+ event_name 'order.paid'
83
+
84
+ schema do
85
+ required(:order_id).filled(:string)
86
+ required(:amount).filled(:decimal)
87
+ required(:currency).filled(:string) # ← New required field
88
+ end
89
+ end
90
+
91
+ # Old code (still deployed):
92
+ OrderPaid.track(order_id: '123', amount: 99.99)
93
+ # ✅ Works! Sends version: 1
94
+
95
+ # New code (gradual rollout):
96
+ OrderPaidV2.track(order_id: '123', amount: 99.99, currency: 'USD')
97
+ # ✅ Works! Sends version: 2
98
+
99
+ # Downstream consumers can handle both versions!
100
+ ```
101
+
102
+ ---
103
+
104
+ ### Scenario 2: Renaming Field (Breaking Change)
105
+
106
+ **Problem:** Need to rename `user_id` → `customer_id` for consistency.
107
+
108
+ ```ruby
109
+ # V1: Original version (no suffix)
110
+ class UserSignup < E11y::Event::Base
111
+ version 1
112
+ event_name 'user.signup'
113
+
114
+ schema do
115
+ required(:user_id).filled(:string) # ← Old name
116
+ required(:email).filled(:string)
117
+ end
118
+
119
+ # Optional: Auto-map to V2 format
120
+ def to_v2
121
+ UserSignupV2.new(
122
+ customer_id: payload[:user_id], # Map old → new
123
+ email: payload[:email]
124
+ )
125
+ end
126
+ end
127
+
128
+ # V2: New field name (V2 suffix)
129
+ class UserSignupV2 < E11y::Event::Base
130
+ version 2
131
+ event_name 'user.signup'
132
+
133
+ schema do
134
+ required(:customer_id).filled(:string) # ← Renamed
135
+ required(:email).filled(:string)
136
+ end
137
+ end
138
+
139
+ # Migration path:
140
+ # 1. Deploy V2 event class (both versions coexist)
141
+ # 2. Update tracking calls gradually (service by service)
142
+ # 3. Monitor: no more V1 events for 30 days
143
+ # 4. Deprecate V1 class
144
+ ```
145
+
146
+ ---
147
+
148
+ ### Scenario 3: Removing Field (Breaking Change)
149
+
150
+ **Problem:** Remove sensitive field that shouldn't have been logged.
151
+
152
+ ```ruby
153
+ # V1: Old version with sensitive field (DEPRECATED)
154
+ class PaymentProcessed < E11y::Event::Base
155
+ version 1
156
+ event_name 'payment.processed'
157
+ deprecated true # Mark as deprecated
158
+ deprecation_warning 'Use PaymentProcessedV2. V1 will be removed 2026-06-01'
159
+
160
+ schema do
161
+ required(:transaction_id).filled(:string)
162
+ required(:amount).filled(:decimal)
163
+ optional(:card_number).filled(:string) # ← SECURITY ISSUE!
164
+ end
165
+
166
+ # Emit deprecation warning
167
+ after_track do |event|
168
+ Rails.logger.warn "DEPRECATED: PaymentProcessed (v1). Use PaymentProcessedV2."
169
+ end
170
+ end
171
+
172
+ # V2: Removed sensitive field (V2 suffix)
173
+ class PaymentProcessedV2 < E11y::Event::Base
174
+ version 2
175
+ event_name 'payment.processed'
176
+
177
+ schema do
178
+ required(:transaction_id).filled(:string)
179
+ required(:amount).filled(:decimal)
180
+ # card_number REMOVED (was security issue!)
181
+ end
182
+ end
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 🏗️ Architecture
188
+
189
+ > **Implementation:** See [ADR-012: Event Evolution](../ADR-012-event-evolution.md) for complete versioning architecture, including [Section 3: Naming Convention](../ADR-012-event-evolution.md#3-naming-convention), [Section 4: Version in Payload](../ADR-012-event-evolution.md#4-version-in-payload), [Section 6: Event Registry Integration](../ADR-012-event-evolution.md#6-event-registry-integration), and [Section 7: Migration Strategy](../ADR-012-event-evolution.md#7-migration-strategy).
190
+
191
+ ### Version Management
192
+
193
+ ```
194
+ ┌─────────────────────────────────────────────────────────────────┐
195
+ │ Event Registry (tracks all versions) │
196
+ │ │
197
+ │ event_name: 'order.paid' │
198
+ │ ├─ V1: OrderPaidV1 │
199
+ │ ├─ V2: OrderPaidV2 (current) │
200
+ │ └─ V3: OrderPaidV3 (future) │
201
+ │ │
202
+ │ current_version: 2 │
203
+ │ deprecated_versions: [1] │
204
+ └─────────────────────────────────────────────────────────────────┘
205
+
206
+ ┌─────────────────────────────────────────────────────────────────┐
207
+ │ Event Payload (includes version) │
208
+ │ │
209
+ │ { │
210
+ │ "@timestamp": "2026-01-12T10:30:00Z", │
211
+ │ "event_name": "order.paid", │
212
+ │ "event_version": 2, ← Version included! │
213
+ │ "payload": { │
214
+ │ "order_id": "123", │
215
+ │ "amount": 99.99, │
216
+ │ "currency": "USD" ← New field in V2 │
217
+ │ } │
218
+ │ } │
219
+ └─────────────────────────────────────────────────────────────────┘
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 🔧 Configuration
225
+
226
+ ### Basic Setup
227
+
228
+ ```ruby
229
+ # config/initializers/e11y.rb
230
+ E11y.configure do |config|
231
+ config.versioning do
232
+ enabled true
233
+
234
+ # Include version in event payload
235
+ include_version_in_payload true
236
+ version_field :event_version # Field name
237
+
238
+ # Deprecation warnings
239
+ warn_on_deprecated_version true
240
+ deprecation_log_level :warn # :info, :warn, :error
241
+
242
+ # Automatic version detection
243
+ auto_detect_version true # From payload structure
244
+ end
245
+ end
246
+ ```
247
+
248
+ ### Advanced: Version Migration
249
+
250
+ ```ruby
251
+ E11y.configure do |config|
252
+ config.versioning do
253
+ enabled true
254
+
255
+ # Auto-upgrade old versions
256
+ auto_upgrade_to_current do
257
+ enabled false # Disabled by default (explicit migration)
258
+
259
+ # If enabled, V1 events auto-converted to V2
260
+ upgrade 'order.paid' do
261
+ from_version 1
262
+ to_version 2
263
+
264
+ transform do |v1_payload|
265
+ v2_payload = v1_payload.dup
266
+ v2_payload[:currency] = 'USD' # Add default for missing field
267
+ v2_payload
268
+ end
269
+ end
270
+ end
271
+
272
+ # Deprecation enforcement
273
+ deprecation_enforcement do
274
+ # After this date, V1 events rejected
275
+ enforce_after '2026-06-01'
276
+
277
+ # What to do with deprecated versions after enforce_after
278
+ on_deprecated_version :reject # :reject, :warn, :upgrade
279
+ end
280
+ end
281
+ end
282
+ ```
283
+
284
+ ---
285
+
286
+ ## 📝 Event Definition Examples
287
+
288
+ ### Example 1: Simple Versioning
289
+
290
+ ```ruby
291
+ # app/events/order_paid.rb (V1 - no suffix!)
292
+ module Events
293
+ class OrderPaid < E11y::Event::Base
294
+ version 1 # Explicit version (recommended)
295
+ event_name 'order.paid'
296
+
297
+ schema do
298
+ required(:order_id).filled(:string)
299
+ required(:amount).filled(:decimal)
300
+ end
301
+ end
302
+ end
303
+
304
+ # app/events/order_paid_v2.rb (V2+ - with suffix!)
305
+ module Events
306
+ class OrderPaidV2 < E11y::Event::Base
307
+ version 2
308
+ event_name 'order.paid'
309
+
310
+ schema do
311
+ required(:order_id).filled(:string)
312
+ required(:amount).filled(:decimal)
313
+ required(:currency).filled(:string) # New required field
314
+ end
315
+
316
+ # Default version (latest)
317
+ default_version true
318
+ end
319
+ end
320
+
321
+ # Usage:
322
+ # Old code:
323
+ Events::OrderPaid.track(order_id: '123', amount: 99.99) # V1
324
+
325
+ # New code:
326
+ Events::OrderPaidV2.track(order_id: '123', amount: 99.99, currency: 'USD') # V2
327
+
328
+ # Or use version-agnostic routing (requires config):
329
+ E11y::Registry.track('order.paid', order_id: '123', ...) # → Routes to default_version (V2)
330
+ ```
331
+
332
+ ### Example 2: With Deprecation
333
+
334
+ ```ruby
335
+ module Events
336
+ # V1 (no suffix, but deprecated)
337
+ class UserLogin < E11y::Event::Base
338
+ version 1
339
+ event_name 'user.login'
340
+
341
+ # Mark as deprecated
342
+ deprecated true
343
+ deprecation_date '2026-03-01'
344
+ deprecation_message 'Use UserLoginV2 with ip_address field'
345
+
346
+ schema do
347
+ required(:user_id).filled(:string)
348
+ required(:success).filled(:bool)
349
+ end
350
+
351
+ # Emit warning on each track
352
+ after_track do |event|
353
+ Rails.logger.warn "[DEPRECATED] UserLogin (v1) used. Migrate to V2 by 2026-03-01"
354
+ end
355
+ end
356
+
357
+ # V2+ (with suffix)
358
+ class UserLoginV2 < E11y::Event::Base
359
+ version 2
360
+ event_name 'user.login'
361
+ default_version true
362
+
363
+ schema do
364
+ required(:user_id).filled(:string)
365
+ required(:success).filled(:bool)
366
+ required(:ip_address).filled(:string) # New security requirement
367
+ end
368
+ end
369
+ end
370
+ ```
371
+
372
+ ### Example 3: With Auto-Migration
373
+
374
+ ```ruby
375
+ module Events
376
+ # V1 (no suffix)
377
+ class OrderShipped < E11y::Event::Base
378
+ version 1
379
+ event_name 'order.shipped'
380
+
381
+ schema do
382
+ required(:order_id).filled(:string)
383
+ required(:tracking_number).filled(:string)
384
+ end
385
+
386
+ # Define migration to V2
387
+ def migrate_to_v2
388
+ OrderShippedV2.new(
389
+ order_id: payload[:order_id],
390
+ tracking_number: payload[:tracking_number],
391
+ carrier: 'USPS' # Default for old events
392
+ )
393
+ end
394
+ end
395
+
396
+ # V2+ (with suffix)
397
+ class OrderShippedV2 < E11y::Event::Base
398
+ version 2
399
+ event_name 'order.shipped'
400
+ default_version true
401
+
402
+ schema do
403
+ required(:order_id).filled(:string)
404
+ required(:tracking_number).filled(:string)
405
+ required(:carrier).filled(:string) # New required field
406
+ end
407
+ end
408
+ end
409
+
410
+ # Configuration for auto-migration:
411
+ E11y.configure do |config|
412
+ config.versioning.auto_upgrade do
413
+ upgrade 'order.shipped' do
414
+ from_version 1
415
+ to_version 2
416
+ transform_method :migrate_to_v2 # Call event.migrate_to_v2
417
+ end
418
+ end
419
+ end
420
+ ```
421
+
422
+ ---
423
+
424
+ ## 💡 Best Practices
425
+
426
+ ### ✅ DO
427
+
428
+ **1. Always increment version for breaking changes**
429
+ ```ruby
430
+ # ✅ GOOD: New version for breaking change
431
+ class OrderPaidV2 < E11y::Event::Base
432
+ version 2 # ← Incremented
433
+ event_name 'order.paid'
434
+
435
+ schema do
436
+ required(:currency).filled(:string) # ← New required field
437
+ end
438
+ end
439
+ ```
440
+
441
+ **2. Keep old versions for backward compatibility**
442
+ ```ruby
443
+ # ✅ GOOD: Keep V1 around during migration
444
+ class OrderPaid < E11y::Event::Base # V1 (no suffix)
445
+ version 1
446
+ deprecated true # Mark as deprecated
447
+ deprecation_date '2026-06-01'
448
+ end
449
+
450
+ class OrderPaidV2 < E11y::Event::Base # V2+ (with suffix)
451
+ version 2
452
+ default_version true
453
+ end
454
+
455
+ # Remove OrderPaid (v1) after deprecation_date + grace period
456
+ ```
457
+
458
+ **3. Document breaking changes**
459
+ ```ruby
460
+ # ✅ GOOD: Clear documentation
461
+ class PaymentProcessedV2 < E11y::Event::Base
462
+ version 2
463
+
464
+ # BREAKING CHANGES from V1:
465
+ # - Removed: card_number (security)
466
+ # - Added: payment_method_id (reference)
467
+ # - Renamed: user_id → customer_id
468
+
469
+ schema do
470
+ required(:customer_id).filled(:string) # Was: user_id
471
+ required(:payment_method_id).filled(:string) # New
472
+ end
473
+ end
474
+ ```
475
+
476
+ **4. Use semantic versioning for major changes**
477
+ ```ruby
478
+ # ✅ GOOD: Major version for major changes
479
+ class OrderPaidV1 < E11y::Event::Base
480
+ version 1 # Initial version
481
+ end
482
+
483
+ class OrderPaidV2 < E11y::Event::Base
484
+ version 2 # Added currency field
485
+ end
486
+
487
+ class OrderPaidV3 < E11y::Event::Base
488
+ version 3 # Restructured to support multi-currency
489
+ end
490
+ ```
491
+
492
+ ---
493
+
494
+ ### ❌ DON'T
495
+
496
+ **1. Don't change schema without versioning**
497
+ ```ruby
498
+ # ❌ BAD: Changed schema without version increment
499
+ class OrderPaid < E11y::Event::Base
500
+ schema do
501
+ required(:order_id).filled(:string)
502
+ required(:amount).filled(:decimal)
503
+ required(:currency).filled(:string) # ← Added without version++
504
+ end
505
+ end
506
+ # This BREAKS old code in production!
507
+ ```
508
+
509
+ **2. Don't delete old versions prematurely**
510
+ ```ruby
511
+ # ❌ BAD: Deleted V1 (OrderPaid) immediately
512
+ # Old services still sending V1 events → errors!
513
+
514
+ # ✅ GOOD: Deprecate first, delete after grace period
515
+ class OrderPaid < E11y::Event::Base # V1
516
+ deprecated true
517
+ deprecation_date '2026-06-01'
518
+ end
519
+
520
+ # Monitor for 30 days after deprecation_date
521
+ # Delete OrderPaid (v1) class only when no more V1 events tracked
522
+ ```
523
+
524
+ **3. Don't use version for non-breaking changes**
525
+ ```ruby
526
+ # ❌ BAD: Version increment for optional field
527
+ class OrderPaidV2 < E11y::Event::Base
528
+ version 2 # ← Unnecessary!
529
+
530
+ schema do
531
+ required(:order_id).filled(:string)
532
+ optional(:notes).filled(:string) # ← Optional = not breaking!
533
+ end
534
+ end
535
+
536
+ # ✅ GOOD: Just add optional field to existing version
537
+ class OrderPaid < E11y::Event::Base
538
+ version 1 # Same version
539
+
540
+ schema do
541
+ required(:order_id).filled(:string)
542
+ optional(:notes).filled(:string) # ← Optional = backward compatible
543
+ end
544
+ end
545
+ ```
546
+
547
+ ---
548
+
549
+ ## 🎯 Migration Strategy
550
+
551
+ ### Phase 1: Deploy New Version (Coexistence)
552
+
553
+ ```ruby
554
+ # Week 1: Deploy both versions
555
+ # - V1 still works (backward compatible)
556
+ # - V2 available for new code
557
+
558
+ # app/events/order_paid.rb (existing V1 - no suffix)
559
+ class OrderPaid < E11y::Event::Base
560
+ version 1
561
+ end
562
+
563
+ # app/events/order_paid_v2.rb (new V2 - with suffix)
564
+ class OrderPaidV2 < E11y::Event::Base
565
+ version 2
566
+ default_version true # New default
567
+ end
568
+ ```
569
+
570
+ ### Phase 2: Gradual Migration
571
+
572
+ ```ruby
573
+ # Week 2-4: Migrate services one by one
574
+
575
+ # Service A (updated):
576
+ Events::OrderPaidV2.track(order_id: '123', amount: 99.99, currency: 'USD')
577
+
578
+ # Service B (not updated yet):
579
+ Events::OrderPaid.track(order_id: '456', amount: 49.99) # V1 still works!
580
+
581
+ # Monitor:
582
+ # - % of V1 vs V2 events
583
+ # - Which services still use V1
584
+ ```
585
+
586
+ ### Phase 3: Deprecation Warning
587
+
588
+ ```ruby
589
+ # Week 5: Mark V1 as deprecated
590
+ class OrderPaid < E11y::Event::Base # V1
591
+ version 1
592
+ deprecated true
593
+ deprecation_date '2026-06-01' # 30 days from now
594
+
595
+ after_track do |event|
596
+ # Emit warning
597
+ Rails.logger.warn "DEPRECATED: OrderPaid (v1). Migrate by 2026-06-01"
598
+
599
+ # Track deprecation usage
600
+ Events::DeprecatedEventUsed.track(
601
+ event_class: 'OrderPaid',
602
+ event_version: 1,
603
+ service: ENV['SERVICE_NAME']
604
+ )
605
+ end
606
+ end
607
+ ```
608
+
609
+ ### Phase 4: Enforcement
610
+
611
+ ```ruby
612
+ # After 2026-06-01: Reject V1 events
613
+ E11y.configure do |config|
614
+ config.versioning.deprecation_enforcement do
615
+ enforce_after '2026-06-01'
616
+ on_deprecated_version :reject # Reject V1 events
617
+ end
618
+ end
619
+
620
+ # Or auto-upgrade:
621
+ E11y.configure do |config|
622
+ config.versioning.auto_upgrade do
623
+ upgrade 'order.paid' do
624
+ from_version 1
625
+ to_version 2
626
+ transform { |v1| v1.merge(currency: 'USD') } # Default currency
627
+ end
628
+ end
629
+ end
630
+ ```
631
+
632
+ ### Phase 5: Cleanup
633
+
634
+ ```ruby
635
+ # 30 days after enforcement: Delete V1 class
636
+ # 1. Verify zero V1 events in last 30 days
637
+ # 2. Remove OrderPaid (v1) class file: app/events/order_paid.rb
638
+ # 3. Rename OrderPaidV2 → OrderPaid (optional, for next v3 migration)
639
+ # 4. Update documentation
640
+ ```
641
+
642
+ ---
643
+
644
+ ## 📊 Monitoring & Metrics
645
+
646
+ ### Version Usage Metrics
647
+
648
+ ```ruby
649
+ # E11y automatically tracks version usage
650
+ E11y.metrics do
651
+ counter :events_by_version_total,
652
+ tags: [:event_name, :version],
653
+ comment: 'Events tracked by version'
654
+
655
+ gauge :deprecated_events_active,
656
+ tags: [:event_name, :version],
657
+ comment: 'Deprecated versions still in use'
658
+ end
659
+
660
+ # Prometheus queries:
661
+ # events_by_version_total{event_name="order.paid", version="1"}
662
+ # events_by_version_total{event_name="order.paid", version="2"}
663
+
664
+ # Alert when deprecated version usage > 0 after deprecation_date
665
+ ```
666
+
667
+ ### Deprecation Dashboard
668
+
669
+ ```ruby
670
+ # Grafana dashboard queries:
671
+
672
+ # % of events by version
673
+ sum(rate(events_by_version_total{event_name="order.paid"}[5m])) by (version)
674
+
675
+ # Services still using deprecated versions
676
+ sum(deprecated_events_active) by (service, event_name, version)
677
+
678
+ # Days until deprecation enforcement
679
+ (deprecation_date - now()) / 86400
680
+ ```
681
+
682
+ ---
683
+
684
+ ## 🔗 Related Use Cases
685
+
686
+ - **[UC-002: Business Event Tracking](./UC-002-business-event-tracking.md)** - Event schema definition
687
+ - **[UC-018: Testing Events](./UC-018-testing-events.md)** - Testing versioned events
688
+ - **[UC-016: Rails Logger Migration](./UC-016-rails-logger-migration.md)** - Migration strategies
689
+
690
+ ---
691
+
692
+ ## 🚀 Quick Start Checklist
693
+
694
+ - [ ] Enable versioning in config
695
+ - [ ] Define V2 event class with new schema
696
+ - [ ] Keep V1 event class (backward compatible)
697
+ - [ ] Update tracking calls gradually
698
+ - [ ] Monitor version usage metrics
699
+ - [ ] Mark V1 as deprecated
700
+ - [ ] Set deprecation_date
701
+ - [ ] Enforce after grace period
702
+ - [ ] Delete old version after 30 days no usage
703
+
704
+ ---
705
+
706
+ **Status:** ✅ Core Feature
707
+ **Priority:** High (schema evolution is critical)
708
+ **Complexity:** Intermediate