bug_bunny 4.6.1 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.agents/skills/rabbitmq-expert/SKILL.md +1555 -0
- data/.claude/commands/gem-ai-setup.md +174 -0
- data/.claude/commands/pr.md +53 -0
- data/.claude/commands/release.md +52 -0
- data/.claude/commands/rubocop.md +22 -0
- data/.claude/commands/service-ai-setup.md +168 -0
- data/.claude/commands/test.md +28 -0
- data/.claude/commands/yard.md +46 -0
- data/CHANGELOG.md +50 -15
- data/CLAUDE.md +240 -0
- data/README.md +154 -221
- data/Rakefile +19 -3
- data/docs/_index.md +50 -0
- data/docs/ai/_index.md +56 -0
- data/docs/ai/antipatterns.md +166 -0
- data/docs/ai/api.md +251 -0
- data/docs/ai/architecture.md +92 -0
- data/docs/ai/errors.md +158 -0
- data/docs/ai/faq_external.md +133 -0
- data/docs/ai/faq_internal.md +86 -0
- data/docs/ai/glossary.md +45 -0
- data/docs/concepts.md +140 -0
- data/docs/howto/controller.md +194 -0
- data/docs/howto/middleware_client.md +119 -0
- data/docs/howto/middleware_consumer.md +127 -0
- data/docs/howto/rails.md +214 -0
- data/docs/howto/resource.md +200 -0
- data/docs/howto/routing.md +133 -0
- data/docs/howto/testing.md +259 -0
- data/docs/howto/tracing.md +119 -0
- data/lib/bug_bunny/client.rb +45 -21
- data/lib/bug_bunny/configuration.rb +63 -0
- data/lib/bug_bunny/consumer.rb +51 -37
- data/lib/bug_bunny/consumer_middleware.rb +14 -5
- data/lib/bug_bunny/controller.rb +39 -18
- data/lib/bug_bunny/exception.rb +5 -1
- data/lib/bug_bunny/middleware/raise_error.rb +3 -3
- data/lib/bug_bunny/observability.rb +28 -6
- data/lib/bug_bunny/producer.rb +11 -13
- data/lib/bug_bunny/railtie.rb +8 -7
- data/lib/bug_bunny/request.rb +3 -11
- data/lib/bug_bunny/resource.rb +81 -41
- data/lib/bug_bunny/routing/route.rb +6 -1
- data/lib/bug_bunny/routing/route_set.rb +60 -22
- data/lib/bug_bunny/session.rb +18 -11
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +4 -2
- data/lib/generators/bug_bunny/install/install_generator.rb +45 -5
- data/lib/tasks/bug_bunny.rake +50 -0
- data/plan_test.txt +63 -0
- data/skills-lock.json +10 -0
- data/spec/integration/client_spec.rb +117 -0
- data/spec/integration/consumer_middleware_spec.rb +86 -0
- data/spec/integration/controller_spec.rb +140 -0
- data/spec/integration/error_handling_spec.rb +57 -0
- data/spec/integration/infrastructure_spec.rb +52 -0
- data/spec/integration/resource_spec.rb +113 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/support/bunny_mocks.rb +18 -0
- data/spec/support/integration_helper.rb +87 -0
- data/spec/unit/client_session_pool_spec.rb +159 -0
- data/spec/unit/configuration_spec.rb +164 -0
- data/spec/unit/consumer_middleware_spec.rb +129 -0
- data/spec/unit/consumer_spec.rb +90 -0
- data/spec/unit/controller_after_action_spec.rb +155 -0
- data/spec/unit/observability_spec.rb +167 -0
- data/spec/unit/resource_attributes_spec.rb +69 -0
- data/spec/unit/session_spec.rb +98 -0
- metadata +50 -3
- data/sig/bug_bunny.rbs +0 -4
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# BugBunny — Project Intelligence
|
|
2
|
+
|
|
3
|
+
## ¿Qué es BugBunny?
|
|
4
|
+
|
|
5
|
+
BugBunny es una gema Ruby que implementa una capa de enrutamiento RESTful sobre AMQP (RabbitMQ). Permite que microservicios se comuniquen via RabbitMQ usando patrones familiares de HTTP: verbos (GET, POST, PUT, DELETE), controladores, rutas declarativas, RPC síncrono y fire-and-forget.
|
|
6
|
+
|
|
7
|
+
**Problema que resuelve:** Eliminar el acoplamiento directo entre microservicios via HTTP, usando RabbitMQ como bus de mensajes con la misma ergonomía de un framework web.
|
|
8
|
+
|
|
9
|
+
## Knowledge Base
|
|
10
|
+
|
|
11
|
+
- **Docs AI:** `docs/ai/` — conocimiento estructurado para agentes
|
|
12
|
+
- **Index:** `docs/ai/_index.md` — manifest con versión, audiencias y archivos
|
|
13
|
+
- **Docs humanos:** `docs/_index.md` — manifest de toda la documentación (`howto/`, `concepts.md`)
|
|
14
|
+
|
|
15
|
+
## Skills disponibles
|
|
16
|
+
|
|
17
|
+
- `.agents/skills/rabbitmq-expert/` — arquitectura AMQP, exchanges, quorum queues, DLX, HA, clustering
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Arquitectura
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Publisher (Client/Resource)
|
|
25
|
+
└─ Producer → Session → Bunny → RabbitMQ Exchange
|
|
26
|
+
│
|
|
27
|
+
RabbitMQ Queue
|
|
28
|
+
│
|
|
29
|
+
Consumer (subscribe loop)
|
|
30
|
+
└─ ConsumerMiddleware::Stack
|
|
31
|
+
└─ process_message
|
|
32
|
+
└─ Router → Controller → Action
|
|
33
|
+
└─ reply (RPC)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Componentes clave
|
|
37
|
+
|
|
38
|
+
| Clase | Responsabilidad |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `BugBunny::Session` | Wrapper de canal Bunny. Declara exchanges y queues. |
|
|
41
|
+
| `BugBunny::Consumer` | Subscribe loop. Rutea mensajes a controladores via `BugBunny.routes`. |
|
|
42
|
+
| `BugBunny::ConsumerMiddleware::Stack` | Pipeline de middlewares antes de `process_message`. |
|
|
43
|
+
| `BugBunny::Producer` | Publica mensajes. Implementa RPC con `Concurrent::IVar`. |
|
|
44
|
+
| `BugBunny::Client` | API de alto nivel para el publicador. Pool de conexiones. |
|
|
45
|
+
| `BugBunny::Controller` | Base class tipo Rails. `around_action`, `before_action`, `render`. |
|
|
46
|
+
| `BugBunny::Resource` | ActiveRecord-like sobre AMQP. `find`, `where`, `create`, etc. |
|
|
47
|
+
| `BugBunny::Request` | Value object del mensaje saliente (path, method, params, headers). |
|
|
48
|
+
| `BugBunny::Observability` | Mixin de logging estructurado. `safe_log`, `exception_metadata`. |
|
|
49
|
+
| `BugBunny::Configuration` | Configuración global. Logger, timeouts, middleware hooks. |
|
|
50
|
+
|
|
51
|
+
### Flujo RPC completo
|
|
52
|
+
|
|
53
|
+
1. `Resource.find(id)` → `Client#request` → `Producer#rpc`
|
|
54
|
+
2. Producer publica en exchange con `reply_to: 'amq.rabbitmq.reply-to'`
|
|
55
|
+
3. `Concurrent::IVar` bloquea el thread principal (`future.value(timeout)`)
|
|
56
|
+
4. Consumer recibe → middleware stack → controller → `reply(response)`
|
|
57
|
+
5. Reply listener thread setea `future.set({ body:, headers: })`
|
|
58
|
+
6. Thread principal: `on_rpc_reply&.call(headers)` → `parse_response(body)`
|
|
59
|
+
|
|
60
|
+
## Hooks de extensión
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# Middleware antes de process_message (ej: tracing, auth)
|
|
64
|
+
BugBunny.consumer_middlewares.use MyMiddleware
|
|
65
|
+
|
|
66
|
+
# Headers a inyectar en el reply RPC (ej: trace context actualizado)
|
|
67
|
+
config.rpc_reply_headers = -> { { 'X-Amzn-Trace-Id' => Tracer.header } }
|
|
68
|
+
|
|
69
|
+
# Callback en el thread principal al recibir el reply (ej: hidratar tracer)
|
|
70
|
+
config.on_rpc_reply = ->(headers) { Tracer.hydrate(headers['X-Amzn-Trace-Id']) }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Dominio y Expertise
|
|
76
|
+
|
|
77
|
+
Al trabajar en esta gema aplicá expertise en:
|
|
78
|
+
|
|
79
|
+
- **Ruby idiomático**: módulos, mixins, metaprogramación, `class_attribute`, `Concurrent::*`
|
|
80
|
+
- **RabbitMQ / AMQP**: exchanges (direct/topic/fanout), queues, bindings, `reply_to`, `correlation_id`, `properties.headers`, publisher confirms, manual ack
|
|
81
|
+
- **Bunny**: la gema Ruby que wrappea AMQP. `channel`, `basic_consume`, `basic_publish`, `IVar`
|
|
82
|
+
- **Rails patterns**: `ActiveModel`, `ActiveSupport`, `class_attribute`, `concerns`, `constantize`
|
|
83
|
+
- **Rack**: `Rack::Utils.parse_nested_query`, `build_nested_query`
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Observability — Estándar de Logging
|
|
88
|
+
|
|
89
|
+
Esta gema implementa su propio patrón de observability via `BugBunny::Observability`.
|
|
90
|
+
|
|
91
|
+
### Reglas fundamentales
|
|
92
|
+
|
|
93
|
+
- **Formato**: `component=x event=clase.evento [key=value ...]` — todo en una línea
|
|
94
|
+
- **Nunca** llamar al logger directamente. Siempre usar `safe_log`
|
|
95
|
+
- **Nunca** `Kernel#warn`, `$stderr`, `puts`
|
|
96
|
+
- **Niveles**: `ERROR`=excepción, `WARN`=inesperado+continuó, `INFO`=normal, `DEBUG`=detalle
|
|
97
|
+
- `DEBUG` siempre en bloque: `logger.debug { "k=#{v}" }` — `safe_log` lo maneja internamente
|
|
98
|
+
- Duraciones: `Process.clock_gettime(Process::CLOCK_MONOTONIC)`, nunca `Time.now`
|
|
99
|
+
- Logger failures **nunca** interrumpen el flujo — `safe_log` tiene `rescue StandardError`
|
|
100
|
+
|
|
101
|
+
### Uso en clases nuevas
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class BugBunny::MiClase
|
|
105
|
+
include BugBunny::Observability
|
|
106
|
+
|
|
107
|
+
def initialize
|
|
108
|
+
@logger = BugBunny.configuration.logger
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def mi_metodo
|
|
112
|
+
start = monotonic_now
|
|
113
|
+
# ...
|
|
114
|
+
safe_log(:info, "mi_clase.mi_evento", campo: valor, duration_s: duration_s(start))
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
safe_log(:error, "mi_clase.error", **exception_metadata(e))
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Naming de eventos
|
|
122
|
+
|
|
123
|
+
Formato estricto: `"clase.evento"` (string, nunca symbol)
|
|
124
|
+
|
|
125
|
+
| Evento | Nivel | Cuándo |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `consumer.start` | INFO | Consumer inicia subscribe |
|
|
128
|
+
| `consumer.bound` | INFO | Queue bindeada al exchange |
|
|
129
|
+
| `consumer.message_received` | INFO | Mensaje recibido, antes del routing |
|
|
130
|
+
| `consumer.route_matched` | DEBUG | Ruta encontrada |
|
|
131
|
+
| `consumer.message_processed` | INFO | Procesamiento exitoso con duración |
|
|
132
|
+
| `consumer.execution_error` | ERROR | Excepción en el procesamiento |
|
|
133
|
+
| `producer.publish` | INFO | Mensaje publicado |
|
|
134
|
+
| `producer.rpc_waiting` | DEBUG | Bloqueando esperando respuesta |
|
|
135
|
+
| `producer.rpc_response_received` | DEBUG | Reply recibido (thread principal) |
|
|
136
|
+
|
|
137
|
+
### Campos estándar
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
safe_log(:error, "clase.error", **exception_metadata(e))
|
|
141
|
+
# => error_class: "RuntimeError", error_message: "..."
|
|
142
|
+
|
|
143
|
+
safe_log(:info, "clase.evento", duration_s: duration_s(start_time))
|
|
144
|
+
# => duration_s: 0.001234
|
|
145
|
+
|
|
146
|
+
# Valores sensibles se filtran automáticamente:
|
|
147
|
+
# password, token, secret, api_key, auth → [FILTERED]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Standards de Código
|
|
153
|
+
|
|
154
|
+
### RuboCop
|
|
155
|
+
|
|
156
|
+
Esta gema usa **rubocop-rails-omakase**. Todo código nuevo o modificado debe cumplir.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
160
|
+
bundle exec rubocop
|
|
161
|
+
bundle exec rubocop -a # autocorrect
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**No corregir código existente no tocado.** Solo el código nuevo o modificado en el PR.
|
|
165
|
+
|
|
166
|
+
### YARD
|
|
167
|
+
|
|
168
|
+
Todo método público nuevo o modificado lleva documentación YARD:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
# Descripción breve.
|
|
172
|
+
#
|
|
173
|
+
# Descripción extendida si es necesario.
|
|
174
|
+
#
|
|
175
|
+
# @param name [Type] Descripción
|
|
176
|
+
# @return [Type] Descripción
|
|
177
|
+
# @raise [ErrorClass] Cuándo se lanza
|
|
178
|
+
# @example
|
|
179
|
+
# resultado = mi_metodo(arg)
|
|
180
|
+
def mi_metodo(name)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
bundle exec yard doc
|
|
185
|
+
bundle exec yard stats --list-undoc
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### RSpec
|
|
189
|
+
|
|
190
|
+
Tests en `spec/`. Sin mocks de dependencias externas reales (RabbitMQ se mockea con doubles de Bunny).
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
194
|
+
bundle exec rspec
|
|
195
|
+
bundle exec rspec spec/bug_bunny/consumer_spec.rb # archivo específico
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Entorno de Desarrollo
|
|
201
|
+
|
|
202
|
+
### Ruby
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Nunca usar `bundle exec ruby` con el Ruby del sistema (2.6). Siempre sourcear chruby primero.
|
|
209
|
+
|
|
210
|
+
### Worktrees
|
|
211
|
+
|
|
212
|
+
- **Main**: `/Users/gabriel/src/gems/bug_bunny` (rama `main`)
|
|
213
|
+
- **Work**: `/Users/gabriel/src/gems/worktrees/current-5n3` (ramas de feature)
|
|
214
|
+
- `main` está checkeado en otro worktree — no se puede hacer `git checkout main` desde el worktree de trabajo
|
|
215
|
+
|
|
216
|
+
### Push a remoto
|
|
217
|
+
|
|
218
|
+
SSH está roto en este entorno. Para push siempre:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git remote set-url origin https://github.com/gedera/bug_bunny.git
|
|
222
|
+
git push origin main
|
|
223
|
+
git remote set-url origin git@github.com:gedera/bug_bunny.git # restaurar
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Release Workflow
|
|
229
|
+
|
|
230
|
+
Usá el comando `/release` para el flujo completo. Manualmente:
|
|
231
|
+
|
|
232
|
+
1. Determinar tipo: `patch`=bugfix, `minor`=feature nueva, `major`=breaking change
|
|
233
|
+
2. Actualizar `lib/bug_bunny/version.rb`
|
|
234
|
+
3. Agregar entrada al tope de `CHANGELOG.md`
|
|
235
|
+
4. Commit con mensaje convencional
|
|
236
|
+
5. Desde `/Users/gabriel/src/gems/bug_bunny`: `git merge --ff-only <branch>`
|
|
237
|
+
6. Push via HTTPS + restaurar SSH
|
|
238
|
+
7. `git tag vX.Y.Z && git push origin vX.Y.Z`
|
|
239
|
+
|
|
240
|
+
**Nunca commitear ni pushear sin permiso explícito del usuario.**
|