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.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +69 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +64 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/Rakefile +37 -0
- data/benchmarks/run_all.rb +33 -0
- data/config/README.md +83 -0
- data/config/loki-local-config.yaml +35 -0
- data/config/prometheus.yml +15 -0
- data/docker-compose.yml +78 -0
- data/docs/00-ICP-AND-TIMELINE.md +483 -0
- data/docs/01-SCALE-REQUIREMENTS.md +858 -0
- data/docs/ADR-001-architecture.md +2617 -0
- data/docs/ADR-002-metrics-yabeda.md +1395 -0
- data/docs/ADR-003-slo-observability.md +3337 -0
- data/docs/ADR-004-adapter-architecture.md +2385 -0
- data/docs/ADR-005-tracing-context.md +1372 -0
- data/docs/ADR-006-security-compliance.md +4143 -0
- data/docs/ADR-007-opentelemetry-integration.md +1385 -0
- data/docs/ADR-008-rails-integration.md +1911 -0
- data/docs/ADR-009-cost-optimization.md +2993 -0
- data/docs/ADR-010-developer-experience.md +2166 -0
- data/docs/ADR-011-testing-strategy.md +1836 -0
- data/docs/ADR-012-event-evolution.md +958 -0
- data/docs/ADR-013-reliability-error-handling.md +2750 -0
- data/docs/ADR-014-event-driven-slo.md +1533 -0
- data/docs/ADR-015-middleware-order.md +1061 -0
- data/docs/ADR-016-self-monitoring-slo.md +1234 -0
- data/docs/API-REFERENCE-L28.md +914 -0
- data/docs/COMPREHENSIVE-CONFIGURATION.md +2366 -0
- data/docs/IMPLEMENTATION_NOTES.md +2804 -0
- data/docs/IMPLEMENTATION_PLAN.md +1971 -0
- data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +586 -0
- data/docs/PLAN.md +148 -0
- data/docs/QUICK-START.md +934 -0
- data/docs/README.md +296 -0
- data/docs/design/00-memory-optimization.md +593 -0
- data/docs/guides/MIGRATION-L27-L28.md +692 -0
- data/docs/guides/PERFORMANCE-BENCHMARKS.md +434 -0
- data/docs/guides/README.md +44 -0
- data/docs/prd/01-overview-vision.md +440 -0
- data/docs/use_cases/README.md +119 -0
- data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +813 -0
- data/docs/use_cases/UC-002-business-event-tracking.md +1953 -0
- data/docs/use_cases/UC-003-pattern-based-metrics.md +1627 -0
- data/docs/use_cases/UC-004-zero-config-slo-tracking.md +728 -0
- data/docs/use_cases/UC-005-sentry-integration.md +759 -0
- data/docs/use_cases/UC-006-trace-context-management.md +905 -0
- data/docs/use_cases/UC-007-pii-filtering.md +2648 -0
- data/docs/use_cases/UC-008-opentelemetry-integration.md +1153 -0
- data/docs/use_cases/UC-009-multi-service-tracing.md +1043 -0
- data/docs/use_cases/UC-010-background-job-tracking.md +1018 -0
- data/docs/use_cases/UC-011-rate-limiting.md +1906 -0
- data/docs/use_cases/UC-012-audit-trail.md +2301 -0
- data/docs/use_cases/UC-013-high-cardinality-protection.md +2127 -0
- data/docs/use_cases/UC-014-adaptive-sampling.md +1940 -0
- data/docs/use_cases/UC-015-cost-optimization.md +735 -0
- data/docs/use_cases/UC-016-rails-logger-migration.md +785 -0
- data/docs/use_cases/UC-017-local-development.md +867 -0
- data/docs/use_cases/UC-018-testing-events.md +1081 -0
- data/docs/use_cases/UC-019-tiered-storage-migration.md +562 -0
- data/docs/use_cases/UC-020-event-versioning.md +708 -0
- data/docs/use_cases/UC-021-error-handling-retry-dlq.md +956 -0
- data/docs/use_cases/UC-022-event-registry.md +648 -0
- data/docs/use_cases/backlog.md +226 -0
- data/e11y.gemspec +76 -0
- data/lib/e11y/adapters/adaptive_batcher.rb +207 -0
- data/lib/e11y/adapters/audit_encrypted.rb +239 -0
- data/lib/e11y/adapters/base.rb +580 -0
- data/lib/e11y/adapters/file.rb +224 -0
- data/lib/e11y/adapters/in_memory.rb +216 -0
- data/lib/e11y/adapters/loki.rb +333 -0
- data/lib/e11y/adapters/otel_logs.rb +203 -0
- data/lib/e11y/adapters/registry.rb +141 -0
- data/lib/e11y/adapters/sentry.rb +230 -0
- data/lib/e11y/adapters/stdout.rb +108 -0
- data/lib/e11y/adapters/yabeda.rb +370 -0
- data/lib/e11y/buffers/adaptive_buffer.rb +339 -0
- data/lib/e11y/buffers/base_buffer.rb +40 -0
- data/lib/e11y/buffers/request_scoped_buffer.rb +246 -0
- data/lib/e11y/buffers/ring_buffer.rb +267 -0
- data/lib/e11y/buffers.rb +14 -0
- data/lib/e11y/console.rb +122 -0
- data/lib/e11y/current.rb +48 -0
- data/lib/e11y/event/base.rb +894 -0
- data/lib/e11y/event/value_sampling_config.rb +84 -0
- data/lib/e11y/events/base_audit_event.rb +43 -0
- data/lib/e11y/events/base_payment_event.rb +33 -0
- data/lib/e11y/events/rails/cache/delete.rb +21 -0
- data/lib/e11y/events/rails/cache/read.rb +23 -0
- data/lib/e11y/events/rails/cache/write.rb +22 -0
- data/lib/e11y/events/rails/database/query.rb +45 -0
- data/lib/e11y/events/rails/http/redirect.rb +21 -0
- data/lib/e11y/events/rails/http/request.rb +26 -0
- data/lib/e11y/events/rails/http/send_file.rb +21 -0
- data/lib/e11y/events/rails/http/start_processing.rb +26 -0
- data/lib/e11y/events/rails/job/completed.rb +22 -0
- data/lib/e11y/events/rails/job/enqueued.rb +22 -0
- data/lib/e11y/events/rails/job/failed.rb +22 -0
- data/lib/e11y/events/rails/job/scheduled.rb +23 -0
- data/lib/e11y/events/rails/job/started.rb +22 -0
- data/lib/e11y/events/rails/log.rb +56 -0
- data/lib/e11y/events/rails/view/render.rb +23 -0
- data/lib/e11y/events.rb +18 -0
- data/lib/e11y/instruments/active_job.rb +201 -0
- data/lib/e11y/instruments/rails_instrumentation.rb +141 -0
- data/lib/e11y/instruments/sidekiq.rb +175 -0
- data/lib/e11y/logger/bridge.rb +205 -0
- data/lib/e11y/metrics/cardinality_protection.rb +172 -0
- data/lib/e11y/metrics/cardinality_tracker.rb +134 -0
- data/lib/e11y/metrics/registry.rb +234 -0
- data/lib/e11y/metrics/relabeling.rb +226 -0
- data/lib/e11y/metrics.rb +102 -0
- data/lib/e11y/middleware/audit_signing.rb +174 -0
- data/lib/e11y/middleware/base.rb +140 -0
- data/lib/e11y/middleware/event_slo.rb +167 -0
- data/lib/e11y/middleware/pii_filter.rb +266 -0
- data/lib/e11y/middleware/pii_filtering.rb +280 -0
- data/lib/e11y/middleware/rate_limiting.rb +214 -0
- data/lib/e11y/middleware/request.rb +163 -0
- data/lib/e11y/middleware/routing.rb +157 -0
- data/lib/e11y/middleware/sampling.rb +254 -0
- data/lib/e11y/middleware/slo.rb +168 -0
- data/lib/e11y/middleware/trace_context.rb +131 -0
- data/lib/e11y/middleware/validation.rb +118 -0
- data/lib/e11y/middleware/versioning.rb +132 -0
- data/lib/e11y/middleware.rb +12 -0
- data/lib/e11y/pii/patterns.rb +90 -0
- data/lib/e11y/pii.rb +13 -0
- data/lib/e11y/pipeline/builder.rb +155 -0
- data/lib/e11y/pipeline/zone_validator.rb +110 -0
- data/lib/e11y/pipeline.rb +12 -0
- data/lib/e11y/presets/audit_event.rb +65 -0
- data/lib/e11y/presets/debug_event.rb +34 -0
- data/lib/e11y/presets/high_value_event.rb +51 -0
- data/lib/e11y/presets.rb +19 -0
- data/lib/e11y/railtie.rb +138 -0
- data/lib/e11y/reliability/circuit_breaker.rb +216 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +277 -0
- data/lib/e11y/reliability/dlq/filter.rb +117 -0
- data/lib/e11y/reliability/retry_handler.rb +207 -0
- data/lib/e11y/reliability/retry_rate_limiter.rb +117 -0
- data/lib/e11y/sampling/error_spike_detector.rb +225 -0
- data/lib/e11y/sampling/load_monitor.rb +161 -0
- data/lib/e11y/sampling/stratified_tracker.rb +92 -0
- data/lib/e11y/sampling/value_extractor.rb +82 -0
- data/lib/e11y/self_monitoring/buffer_monitor.rb +79 -0
- data/lib/e11y/self_monitoring/performance_monitor.rb +97 -0
- data/lib/e11y/self_monitoring/reliability_monitor.rb +146 -0
- data/lib/e11y/slo/event_driven.rb +150 -0
- data/lib/e11y/slo/tracker.rb +119 -0
- data/lib/e11y/version.rb +9 -0
- data/lib/e11y.rb +283 -0
- metadata +452 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
# UC-022: Event Registry & Introspection
|
|
2
|
+
|
|
3
|
+
**Status:** Developer Experience Feature (v1.1+)
|
|
4
|
+
**Complexity:** Low
|
|
5
|
+
**Setup Time:** 5-10 minutes
|
|
6
|
+
**Target Users:** Backend Developers, QA Engineers, Documentation Writers
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ๐ Overview
|
|
11
|
+
|
|
12
|
+
### Problem Statement
|
|
13
|
+
|
|
14
|
+
**Current Pain Points:**
|
|
15
|
+
|
|
16
|
+
1. **No catalog of events**
|
|
17
|
+
- What events exist in the system?
|
|
18
|
+
- Need to grep codebase to find event classes
|
|
19
|
+
- Hard to document all events
|
|
20
|
+
|
|
21
|
+
2. **No runtime introspection**
|
|
22
|
+
- Can't list all registered events at runtime
|
|
23
|
+
- Can't find event class by name
|
|
24
|
+
- Can't inspect event schema programmatically
|
|
25
|
+
|
|
26
|
+
3. **Hard to build tooling**
|
|
27
|
+
- Can't build event explorer UI (no registry)
|
|
28
|
+
- Can't auto-generate documentation
|
|
29
|
+
- Can't validate that all events are documented
|
|
30
|
+
|
|
31
|
+
### E11y Solution
|
|
32
|
+
|
|
33
|
+
**Event Registry with Full Introspection:**
|
|
34
|
+
|
|
35
|
+
- Automatic registration of all event classes
|
|
36
|
+
- Query registry by event name, version, adapter
|
|
37
|
+
- Schema introspection (fields, types, validations)
|
|
38
|
+
- Build developer tools (event explorer, documentation generator)
|
|
39
|
+
|
|
40
|
+
**Result:** Full visibility into all events in the system.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ๐ฏ Use Case Scenarios
|
|
45
|
+
|
|
46
|
+
### Scenario 1: List All Events
|
|
47
|
+
|
|
48
|
+
**Problem:** Need to document all events in the system.
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# Without registry (MANUAL GREP):
|
|
52
|
+
$ grep -r "class.*< E11y::Event::Base" app/events/
|
|
53
|
+
# โ Manual, error-prone, outdated
|
|
54
|
+
|
|
55
|
+
# With registry (AUTOMATIC):
|
|
56
|
+
E11y::Registry.all_events
|
|
57
|
+
# => [
|
|
58
|
+
# Events::OrderCreated,
|
|
59
|
+
# Events::OrderPaid,
|
|
60
|
+
# Events::UserSignup,
|
|
61
|
+
# Events::PaymentFailed,
|
|
62
|
+
# ...
|
|
63
|
+
# ]
|
|
64
|
+
|
|
65
|
+
# Generate documentation:
|
|
66
|
+
E11y::Registry.all_events.each do |event_class|
|
|
67
|
+
puts "## #{event_class.event_name}"
|
|
68
|
+
puts "Version: #{event_class.version}"
|
|
69
|
+
puts "Schema: #{event_class.schema_definition}"
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Scenario 2: Find Event by Name
|
|
76
|
+
|
|
77
|
+
**Problem:** Need to find event class for dynamic event tracking.
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# Without registry (STRING EVAL - DANGEROUS!):
|
|
81
|
+
event_name = 'order.created'
|
|
82
|
+
event_class = eval("Events::#{event_name.classify}") # โ DANGEROUS!
|
|
83
|
+
|
|
84
|
+
# With registry (SAFE):
|
|
85
|
+
event_class = E11y::Registry.find('order.created')
|
|
86
|
+
# => Events::OrderCreated
|
|
87
|
+
|
|
88
|
+
# Dynamic tracking:
|
|
89
|
+
event_class.track(order_id: '123', amount: 99.99)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Scenario 3: Schema Introspection
|
|
95
|
+
|
|
96
|
+
**Problem:** Need to generate API documentation showing event schemas.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Introspect event schema
|
|
100
|
+
event = Events::OrderPaid
|
|
101
|
+
|
|
102
|
+
event.event_name
|
|
103
|
+
# => "order.paid"
|
|
104
|
+
|
|
105
|
+
event.version
|
|
106
|
+
# => 2
|
|
107
|
+
|
|
108
|
+
event.schema_definition
|
|
109
|
+
# => {
|
|
110
|
+
# order_id: { type: :string, required: true },
|
|
111
|
+
# amount: { type: :decimal, required: true },
|
|
112
|
+
# currency: { type: :string, required: true }
|
|
113
|
+
# }
|
|
114
|
+
|
|
115
|
+
event.adapters
|
|
116
|
+
# => [:loki, :sentry, :file]
|
|
117
|
+
|
|
118
|
+
event.severity_level
|
|
119
|
+
# => :info
|
|
120
|
+
|
|
121
|
+
# Generate OpenAPI spec:
|
|
122
|
+
{
|
|
123
|
+
"event.name": event.event_name,
|
|
124
|
+
"properties": event.schema_definition.transform_values { |v| v[:type] }
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### Scenario 4: Event Explorer UI
|
|
131
|
+
|
|
132
|
+
**Problem:** Developers need to see all events and test them.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# Rails controller for event explorer
|
|
136
|
+
class EventExplorerController < ApplicationController
|
|
137
|
+
def index
|
|
138
|
+
@events = E11y::Registry.all_events.map do |event_class|
|
|
139
|
+
{
|
|
140
|
+
name: event_class.event_name,
|
|
141
|
+
version: event_class.version,
|
|
142
|
+
schema: event_class.schema_definition,
|
|
143
|
+
adapters: event_class.adapters,
|
|
144
|
+
examples: event_class.example_payloads
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def show
|
|
150
|
+
@event = E11y::Registry.find(params[:name])
|
|
151
|
+
|
|
152
|
+
# Show details:
|
|
153
|
+
# - Schema
|
|
154
|
+
# - Recent tracked events
|
|
155
|
+
# - Metrics (how many times tracked)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def test
|
|
159
|
+
event_class = E11y::Registry.find(params[:name])
|
|
160
|
+
payload = JSON.parse(params[:payload])
|
|
161
|
+
|
|
162
|
+
# Test tracking
|
|
163
|
+
event_class.track(**payload)
|
|
164
|
+
|
|
165
|
+
flash[:success] = "Event tracked successfully!"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## ๐๏ธ Architecture
|
|
173
|
+
|
|
174
|
+
### Registry Structure
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
178
|
+
โ E11y::Registry (Global Singleton) โ
|
|
179
|
+
โ โ
|
|
180
|
+
โ @events = { โ
|
|
181
|
+
โ 'order.created' => { โ
|
|
182
|
+
โ v1: Events::OrderCreatedV1, โ
|
|
183
|
+
โ v2: Events::OrderCreatedV2 (current) โ
|
|
184
|
+
โ }, โ
|
|
185
|
+
โ 'order.paid' => { โ
|
|
186
|
+
โ v1: Events::OrderPaidV1, โ
|
|
187
|
+
โ v2: Events::OrderPaidV2 (current) โ
|
|
188
|
+
โ }, โ
|
|
189
|
+
โ 'user.signup' => { โ
|
|
190
|
+
โ v1: Events::UserSignup (current) โ
|
|
191
|
+
โ } โ
|
|
192
|
+
โ } โ
|
|
193
|
+
โ โ
|
|
194
|
+
โ Indexes: โ
|
|
195
|
+
โ - by_name: 'order.created' โ Events::OrderCreatedV2 โ
|
|
196
|
+
โ - by_adapter: :sentry โ [Events::PaymentFailed, ...] โ
|
|
197
|
+
โ - by_severity: :error โ [Events::SystemError, ...] โ
|
|
198
|
+
โ - by_version: 2 โ [Events::OrderCreatedV2, ...] โ
|
|
199
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Auto-Registration
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
# Event classes automatically register on load
|
|
206
|
+
module Events
|
|
207
|
+
class OrderCreated < E11y::Event::Base
|
|
208
|
+
# On class definition:
|
|
209
|
+
# 1. E11y::Registry.register(self)
|
|
210
|
+
# 2. Store: event_name, version, schema, adapters
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Behind the scenes:
|
|
215
|
+
class E11y::Event::Base
|
|
216
|
+
def self.inherited(subclass)
|
|
217
|
+
super
|
|
218
|
+
E11y::Registry.register(subclass) # Auto-register
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## ๐ง Configuration
|
|
226
|
+
|
|
227
|
+
### Basic Setup
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
# config/initializers/e11y.rb
|
|
231
|
+
E11y.configure do |config|
|
|
232
|
+
config.registry do
|
|
233
|
+
enabled true
|
|
234
|
+
|
|
235
|
+
# Eager load event classes (for registry)
|
|
236
|
+
eager_load true
|
|
237
|
+
eager_load_paths [
|
|
238
|
+
Rails.root.join('app', 'events')
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
# Registry features
|
|
242
|
+
enable_introspection true
|
|
243
|
+
enable_event_explorer true # Web UI at /e11y/events
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## ๐ Registry API
|
|
251
|
+
|
|
252
|
+
> **Implementation:** See [ADR-010 Section 5: Event Registry](../ADR-010-developer-experience.md#5-event-registry) for full registry architecture, including event discovery API, introspection, version tracking, and dynamic dispatch.
|
|
253
|
+
|
|
254
|
+
### Query Events
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# === List All Events ===
|
|
258
|
+
E11y::Registry.all_events
|
|
259
|
+
# => [Events::OrderCreated, Events::OrderPaid, ...]
|
|
260
|
+
|
|
261
|
+
E11y::Registry.count
|
|
262
|
+
# => 42
|
|
263
|
+
|
|
264
|
+
# === Find by Name ===
|
|
265
|
+
E11y::Registry.find('order.created')
|
|
266
|
+
# => Events::OrderCreated (latest version)
|
|
267
|
+
|
|
268
|
+
E11y::Registry.find('order.created', version: 1)
|
|
269
|
+
# => Events::OrderCreatedV1
|
|
270
|
+
|
|
271
|
+
# === Find by Criteria ===
|
|
272
|
+
E11y::Registry.where(adapter: :sentry)
|
|
273
|
+
# => [Events::PaymentFailed, Events::SystemError, ...]
|
|
274
|
+
|
|
275
|
+
E11y::Registry.where(severity: :error)
|
|
276
|
+
# => [Events::PaymentFailed, ...]
|
|
277
|
+
|
|
278
|
+
E11y::Registry.where(version: 2)
|
|
279
|
+
# => [Events::OrderCreatedV2, Events::OrderPaidV2, ...]
|
|
280
|
+
|
|
281
|
+
# === Search ===
|
|
282
|
+
E11y::Registry.search('payment')
|
|
283
|
+
# => [Events::PaymentProcessed, Events::PaymentFailed, ...]
|
|
284
|
+
|
|
285
|
+
# === Filtering ===
|
|
286
|
+
E11y::Registry.filter do |event_class|
|
|
287
|
+
event_class.adapters.include?(:sentry) &&
|
|
288
|
+
event_class.severity_level == :error
|
|
289
|
+
end
|
|
290
|
+
# => [Events::PaymentFailed, Events::SystemError]
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Introspection API
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
event = Events::OrderPaid
|
|
297
|
+
|
|
298
|
+
# === Basic Info ===
|
|
299
|
+
event.event_name
|
|
300
|
+
# => "order.paid"
|
|
301
|
+
|
|
302
|
+
event.version
|
|
303
|
+
# => 2
|
|
304
|
+
|
|
305
|
+
event.default_version?
|
|
306
|
+
# => true
|
|
307
|
+
|
|
308
|
+
event.deprecated?
|
|
309
|
+
# => false
|
|
310
|
+
|
|
311
|
+
# === Schema ===
|
|
312
|
+
event.schema_definition
|
|
313
|
+
# => {
|
|
314
|
+
# order_id: { type: :string, required: true },
|
|
315
|
+
# amount: { type: :decimal, required: true },
|
|
316
|
+
# currency: { type: :string, required: true }
|
|
317
|
+
# }
|
|
318
|
+
|
|
319
|
+
event.required_fields
|
|
320
|
+
# => [:order_id, :amount, :currency]
|
|
321
|
+
|
|
322
|
+
event.optional_fields
|
|
323
|
+
# => []
|
|
324
|
+
|
|
325
|
+
event.field_type(:amount)
|
|
326
|
+
# => :decimal
|
|
327
|
+
|
|
328
|
+
# === Adapters ===
|
|
329
|
+
event.adapters
|
|
330
|
+
# => [:loki, :sentry]
|
|
331
|
+
|
|
332
|
+
event.uses_adapter?(:sentry)
|
|
333
|
+
# => true
|
|
334
|
+
|
|
335
|
+
# === Severity ===
|
|
336
|
+
event.severity_level
|
|
337
|
+
# => :info
|
|
338
|
+
|
|
339
|
+
event.track_success?
|
|
340
|
+
# => false
|
|
341
|
+
|
|
342
|
+
# === Examples ===
|
|
343
|
+
event.example_payloads
|
|
344
|
+
# => [
|
|
345
|
+
# { order_id: '123', amount: 99.99, currency: 'USD' },
|
|
346
|
+
# { order_id: '456', amount: 49.99, currency: 'EUR' }
|
|
347
|
+
# ]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Statistics
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
# === Registry Stats ===
|
|
354
|
+
E11y::Registry.stats
|
|
355
|
+
# => {
|
|
356
|
+
# total_events: 42,
|
|
357
|
+
# by_adapter: {
|
|
358
|
+
# loki: 42,
|
|
359
|
+
# sentry: 15,
|
|
360
|
+
# file: 42
|
|
361
|
+
# },
|
|
362
|
+
# by_severity: {
|
|
363
|
+
# debug: 10,
|
|
364
|
+
# info: 20,
|
|
365
|
+
# warn: 8,
|
|
366
|
+
# error: 4
|
|
367
|
+
# },
|
|
368
|
+
# by_version: {
|
|
369
|
+
# 1: 30,
|
|
370
|
+
# 2: 12
|
|
371
|
+
# },
|
|
372
|
+
# deprecated: 5
|
|
373
|
+
# }
|
|
374
|
+
|
|
375
|
+
# === Event Usage Stats (requires tracking) ===
|
|
376
|
+
E11y::Registry.usage_stats
|
|
377
|
+
# => {
|
|
378
|
+
# 'order.created' => { total: 1000, last_24h: 100 },
|
|
379
|
+
# 'order.paid' => { total: 800, last_24h: 80 },
|
|
380
|
+
# ...
|
|
381
|
+
# }
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## ๐ก Developer Tools
|
|
387
|
+
|
|
388
|
+
### 1. Event Explorer Web UI
|
|
389
|
+
|
|
390
|
+
```ruby
|
|
391
|
+
# Available at: http://localhost:3000/e11y/events
|
|
392
|
+
|
|
393
|
+
# Features:
|
|
394
|
+
# - List all events
|
|
395
|
+
# - Search/filter events
|
|
396
|
+
# - View event schema
|
|
397
|
+
# - Test event tracking
|
|
398
|
+
# - View recent tracked events
|
|
399
|
+
# - View event metrics
|
|
400
|
+
|
|
401
|
+
# Enable in config:
|
|
402
|
+
E11y.configure do |config|
|
|
403
|
+
config.development.event_explorer do
|
|
404
|
+
enabled true
|
|
405
|
+
mount_path '/e11y/events'
|
|
406
|
+
|
|
407
|
+
# Authentication (production)
|
|
408
|
+
authenticate_with do |username, password|
|
|
409
|
+
username == ENV['E11Y_USER'] && password == ENV['E11Y_PASS']
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 2. Documentation Generator
|
|
416
|
+
|
|
417
|
+
```ruby
|
|
418
|
+
# Rake task: generate event documentation
|
|
419
|
+
# lib/tasks/e11y_docs.rake
|
|
420
|
+
namespace :e11y do
|
|
421
|
+
desc 'Generate event documentation'
|
|
422
|
+
task docs: :environment do
|
|
423
|
+
output = StringIO.new
|
|
424
|
+
|
|
425
|
+
output.puts "# E11y Events Documentation"
|
|
426
|
+
output.puts
|
|
427
|
+
output.puts "Total events: #{E11y::Registry.count}"
|
|
428
|
+
output.puts
|
|
429
|
+
|
|
430
|
+
E11y::Registry.all_events.each do |event_class|
|
|
431
|
+
output.puts "## #{event_class.event_name}"
|
|
432
|
+
output.puts
|
|
433
|
+
output.puts "**Version:** #{event_class.version}"
|
|
434
|
+
output.puts "**Severity:** #{event_class.severity_level}"
|
|
435
|
+
output.puts "**Adapters:** #{event_class.adapters.join(', ')}"
|
|
436
|
+
output.puts
|
|
437
|
+
output.puts "### Schema"
|
|
438
|
+
output.puts
|
|
439
|
+
output.puts "| Field | Type | Required |"
|
|
440
|
+
output.puts "|-------|------|----------|"
|
|
441
|
+
|
|
442
|
+
event_class.schema_definition.each do |field, opts|
|
|
443
|
+
output.puts "| #{field} | #{opts[:type]} | #{opts[:required] ? 'Yes' : 'No'} |"
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
output.puts
|
|
447
|
+
output.puts "### Example"
|
|
448
|
+
output.puts
|
|
449
|
+
output.puts "```ruby"
|
|
450
|
+
output.puts "#{event_class.name}.track("
|
|
451
|
+
event_class.example_payloads.first.each do |key, value|
|
|
452
|
+
output.puts " #{key}: #{value.inspect},"
|
|
453
|
+
end
|
|
454
|
+
output.puts ")"
|
|
455
|
+
output.puts "```"
|
|
456
|
+
output.puts
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
File.write('docs/EVENTS.md', output.string)
|
|
460
|
+
puts "โ
Documentation generated: docs/EVENTS.md"
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Run:
|
|
465
|
+
# $ rake e11y:docs
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### 3. Event Validator
|
|
469
|
+
|
|
470
|
+
```ruby
|
|
471
|
+
# Validate all events are documented
|
|
472
|
+
# lib/tasks/e11y_validate.rake
|
|
473
|
+
namespace :e11y do
|
|
474
|
+
desc 'Validate all events'
|
|
475
|
+
task validate: :environment do
|
|
476
|
+
errors = []
|
|
477
|
+
|
|
478
|
+
E11y::Registry.all_events.each do |event_class|
|
|
479
|
+
# Check: has example payload
|
|
480
|
+
if event_class.example_payloads.empty?
|
|
481
|
+
errors << "#{event_class.name} has no example payloads"
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Check: has documentation comment
|
|
485
|
+
unless event_class.documented?
|
|
486
|
+
errors << "#{event_class.name} has no documentation"
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# Check: deprecated events have deprecation_date
|
|
490
|
+
if event_class.deprecated? && event_class.deprecation_date.nil?
|
|
491
|
+
errors << "#{event_class.name} is deprecated but no deprecation_date"
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
if errors.any?
|
|
496
|
+
puts "โ Found #{errors.size} issues:"
|
|
497
|
+
errors.each { |err| puts " - #{err}" }
|
|
498
|
+
exit 1
|
|
499
|
+
else
|
|
500
|
+
puts "โ
All events valid"
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Run in CI:
|
|
506
|
+
# $ rake e11y:validate
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 4. OpenAPI Generator
|
|
510
|
+
|
|
511
|
+
```ruby
|
|
512
|
+
# Generate OpenAPI spec for events
|
|
513
|
+
namespace :e11y do
|
|
514
|
+
desc 'Generate OpenAPI spec'
|
|
515
|
+
task openapi: :environment do
|
|
516
|
+
spec = {
|
|
517
|
+
openapi: '3.0.0',
|
|
518
|
+
info: {
|
|
519
|
+
title: 'E11y Events API',
|
|
520
|
+
version: '1.0.0'
|
|
521
|
+
},
|
|
522
|
+
paths: {}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
E11y::Registry.all_events.each do |event_class|
|
|
526
|
+
spec[:paths]["/events/#{event_class.event_name}"] = {
|
|
527
|
+
post: {
|
|
528
|
+
summary: "Track #{event_class.event_name} event",
|
|
529
|
+
requestBody: {
|
|
530
|
+
content: {
|
|
531
|
+
'application/json': {
|
|
532
|
+
schema: {
|
|
533
|
+
type: 'object',
|
|
534
|
+
properties: event_class.schema_definition.transform_values { |v|
|
|
535
|
+
{ type: v[:type].to_s }
|
|
536
|
+
},
|
|
537
|
+
required: event_class.required_fields.map(&:to_s)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
File.write('docs/openapi.json', JSON.pretty_generate(spec))
|
|
547
|
+
puts "โ
OpenAPI spec generated: docs/openapi.json"
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## ๐งช Testing
|
|
555
|
+
|
|
556
|
+
### RSpec Examples
|
|
557
|
+
|
|
558
|
+
```ruby
|
|
559
|
+
RSpec.describe E11y::Registry do
|
|
560
|
+
describe '.all_events' do
|
|
561
|
+
it 'returns all registered events' do
|
|
562
|
+
events = E11y::Registry.all_events
|
|
563
|
+
|
|
564
|
+
expect(events).to include(Events::OrderCreated)
|
|
565
|
+
expect(events).to include(Events::OrderPaid)
|
|
566
|
+
expect(events.size).to be > 0
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
describe '.find' do
|
|
571
|
+
it 'finds event by name' do
|
|
572
|
+
event = E11y::Registry.find('order.created')
|
|
573
|
+
|
|
574
|
+
expect(event).to eq(Events::OrderCreated)
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
it 'finds event by name and version' do
|
|
578
|
+
event = E11y::Registry.find('order.created', version: 1)
|
|
579
|
+
|
|
580
|
+
expect(event).to eq(Events::OrderCreatedV1)
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
it 'returns nil for unknown event' do
|
|
584
|
+
event = E11y::Registry.find('unknown.event')
|
|
585
|
+
|
|
586
|
+
expect(event).to be_nil
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
describe '.where' do
|
|
591
|
+
it 'filters by adapter' do
|
|
592
|
+
events = E11y::Registry.where(adapter: :sentry)
|
|
593
|
+
|
|
594
|
+
expect(events).to all(satisfy { |e| e.adapters.include?(:sentry) })
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
it 'filters by severity' do
|
|
598
|
+
events = E11y::Registry.where(severity: :error)
|
|
599
|
+
|
|
600
|
+
expect(events).to all(have_attributes(severity_level: :error))
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
describe 'introspection' do
|
|
605
|
+
let(:event) { Events::OrderPaid }
|
|
606
|
+
|
|
607
|
+
it 'exposes event metadata' do
|
|
608
|
+
expect(event.event_name).to eq('order.paid')
|
|
609
|
+
expect(event.version).to eq(2)
|
|
610
|
+
expect(event.adapters).to include(:loki, :sentry)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
it 'exposes schema' do
|
|
614
|
+
schema = event.schema_definition
|
|
615
|
+
|
|
616
|
+
expect(schema).to include(
|
|
617
|
+
order_id: { type: :string, required: true },
|
|
618
|
+
amount: { type: :decimal, required: true }
|
|
619
|
+
)
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
## ๐ Related Use Cases
|
|
628
|
+
|
|
629
|
+
- **[UC-017: Local Development](./UC-017-local-development.md)** - Event Explorer UI
|
|
630
|
+
- **[UC-020: Event Versioning](./UC-020-event-versioning.md)** - Version registry
|
|
631
|
+
- **[UC-002: Business Event Tracking](./UC-002-business-event-tracking.md)** - Event definitions
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## ๐ Quick Start Checklist
|
|
636
|
+
|
|
637
|
+
- [ ] Enable registry in config
|
|
638
|
+
- [ ] Enable eager loading of event classes
|
|
639
|
+
- [ ] Access registry: `E11y::Registry.all_events`
|
|
640
|
+
- [ ] Enable event explorer UI (development only)
|
|
641
|
+
- [ ] Set up documentation generator rake task
|
|
642
|
+
- [ ] Run validation in CI: `rake e11y:validate`
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
**Status:** โ
Developer Experience Feature
|
|
647
|
+
**Priority:** Nice-to-Have (v1.1+)
|
|
648
|
+
**Complexity:** Low
|