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/README.md
CHANGED
|
@@ -1,312 +1,245 @@
|
|
|
1
|
-
#
|
|
1
|
+
# BugBunny
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/bug_bunny)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
RESTful messaging over RabbitMQ for Ruby microservices.
|
|
6
6
|
|
|
7
|
-
BugBunny
|
|
8
|
-
|
|
9
|
-
## ✨ Características
|
|
10
|
-
|
|
11
|
-
* **RESTful Routing:** Define tus endpoints AMQP con un DSL estilo Rails (`get`, `post`, `resources`).
|
|
12
|
-
* **Active Record Pattern:** Modela tus recursos remotos con validaciones, callbacks y tracking de cambios.
|
|
13
|
-
* **Middleware Stack:** Arquitectura de cebolla (Onion) para interceptar peticiones, manejar errores y transformar payloads.
|
|
14
|
-
* **RPC & Pub/Sub:** Soporta nativamente tanto peticiones síncronas (Request-Response) como publicaciones asíncronas.
|
|
15
|
-
* **Observabilidad de Clase Mundial:** Integración nativa con **ExisRay** (Tracing distribuido y Logs estructurados KV).
|
|
16
|
-
* **Resiliencia Enterprise:** Reconexión automática con Backoff Exponencial y Health Checks automáticos.
|
|
7
|
+
BugBunny maps AMQP messages to controllers, routes, and models using the same patterns as Rails. Services communicate through RabbitMQ without HTTP coupling, with full support for synchronous RPC and fire-and-forget publishing.
|
|
17
8
|
|
|
18
9
|
---
|
|
19
10
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
Añade esta línea al `Gemfile` de tu aplicación:
|
|
11
|
+
## Installation
|
|
23
12
|
|
|
24
13
|
```ruby
|
|
25
14
|
gem 'bug_bunny'
|
|
26
15
|
```
|
|
27
16
|
|
|
28
|
-
Y luego ejecuta:
|
|
29
17
|
```bash
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
O instálalo manualmente:
|
|
34
|
-
```bash
|
|
35
|
-
$ gem install bug_bunny
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Luego, genera el inicializador (en Rails):
|
|
39
|
-
```bash
|
|
40
|
-
$ rails generate bug_bunny:install
|
|
18
|
+
bundle install
|
|
19
|
+
rails generate bug_bunny:install # Rails only
|
|
41
20
|
```
|
|
42
21
|
|
|
43
22
|
---
|
|
44
23
|
|
|
45
|
-
##
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
BugBunny connects two services through RabbitMQ. One service hosts the consumer (server side); the other uses a Resource or Client to call it (client side).
|
|
46
27
|
|
|
47
|
-
|
|
28
|
+
### Service B — Consumer
|
|
48
29
|
|
|
49
30
|
```ruby
|
|
50
31
|
# config/initializers/bug_bunny.rb
|
|
51
32
|
BugBunny.configure do |config|
|
|
52
|
-
config.host
|
|
53
|
-
config.port
|
|
54
|
-
config.username = 'guest'
|
|
55
|
-
config.password = 'guest'
|
|
56
|
-
|
|
57
|
-
# Resiliencia
|
|
58
|
-
config.max_reconnect_attempts = 10 # Falla tras 10 intentos (útil en K8s)
|
|
59
|
-
config.max_reconnect_interval = 60 # Máximo 60s entre reintentos
|
|
60
|
-
config.network_recovery_interval = 5 # Intervalo base para backoff
|
|
61
|
-
|
|
62
|
-
# Infraestructura por defecto (Nivel 2)
|
|
63
|
-
config.exchange_options = { durable: true }
|
|
33
|
+
config.host = ENV.fetch('RABBITMQ_HOST', 'localhost')
|
|
34
|
+
config.port = 5672
|
|
35
|
+
config.username = ENV.fetch('RABBITMQ_USER', 'guest')
|
|
36
|
+
config.password = ENV.fetch('RABBITMQ_PASS', 'guest')
|
|
64
37
|
end
|
|
65
|
-
```
|
|
66
38
|
|
|
67
|
-
|
|
39
|
+
# config/initializers/bug_bunny_routes.rb
|
|
40
|
+
BugBunny.routes.draw do
|
|
41
|
+
resources :nodes
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# app/controllers/bug_bunny/controllers/nodes_controller.rb
|
|
45
|
+
module BugBunny
|
|
46
|
+
module Controllers
|
|
47
|
+
class NodesController < BugBunny::Controller
|
|
48
|
+
def show
|
|
49
|
+
node = Node.find(params[:id])
|
|
50
|
+
render status: :ok, json: node.as_json
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def index
|
|
54
|
+
render status: :ok, json: Node.all.map(&:as_json)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
68
59
|
|
|
69
|
-
|
|
60
|
+
# Worker entrypoint (dedicated thread or process)
|
|
61
|
+
consumer = BugBunny::Consumer.new
|
|
62
|
+
consumer.subscribe(
|
|
63
|
+
queue_name: 'inventory_queue',
|
|
64
|
+
exchange_name: 'inventory',
|
|
65
|
+
routing_key: 'nodes'
|
|
66
|
+
)
|
|
67
|
+
```
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
### Service A — Producer
|
|
72
70
|
|
|
73
71
|
```ruby
|
|
72
|
+
# config/initializers/bug_bunny.rb — same connection config as above
|
|
73
|
+
|
|
74
|
+
# Pool shared across threads (Puma / Sidekiq)
|
|
75
|
+
BUG_BUNNY_POOL = ConnectionPool.new(size: 5, timeout: 5) do
|
|
76
|
+
BugBunny.create_connection
|
|
77
|
+
end
|
|
78
|
+
|
|
74
79
|
class RemoteNode < BugBunny::Resource
|
|
75
|
-
|
|
76
|
-
self.
|
|
77
|
-
self.resource_name = 'nodes' # Equivale al path de la URL
|
|
80
|
+
self.exchange = 'inventory'
|
|
81
|
+
self.resource_name = 'nodes'
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
attribute :name, :string
|
|
83
|
+
attribute :name, :string
|
|
81
84
|
attribute :status, :string
|
|
82
|
-
attribute :cpu_cores, :integer
|
|
83
|
-
|
|
84
|
-
# Validaciones
|
|
85
|
-
validates :name, presence: true
|
|
86
85
|
end
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
RemoteNode.connection_pool = BUG_BUNNY_POOL
|
|
88
|
+
|
|
89
|
+
# Use it like ActiveRecord
|
|
90
|
+
node = RemoteNode.find('node-123') # GET nodes/node-123 via RabbitMQ
|
|
90
91
|
node.status = 'active'
|
|
91
|
-
node.save
|
|
92
|
-
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
# El consumer los recibe en params[] igual que en Rails.
|
|
96
|
-
RemoteNode.all # GET nodes
|
|
97
|
-
RemoteNode.where(status: 'active') # GET nodes?status=active
|
|
98
|
-
RemoteNode.where(q: { cpu_cores: 4 }) # GET nodes?q[cpu_cores]=4
|
|
92
|
+
node.save # PUT nodes/node-123
|
|
93
|
+
|
|
94
|
+
RemoteNode.where(status: 'active') # GET nodes?status=active
|
|
95
|
+
RemoteNode.create(name: 'web-01', status: 'pending')
|
|
99
96
|
```
|
|
100
97
|
|
|
101
98
|
---
|
|
102
99
|
|
|
103
|
-
##
|
|
104
|
-
|
|
105
|
-
Define cómo debe responder tu aplicación a los mensajes entrantes:
|
|
100
|
+
## Modes of Use
|
|
106
101
|
|
|
107
|
-
|
|
108
|
-
# config/rabbit_routes.rb
|
|
109
|
-
BugBunny.routes.draw do
|
|
110
|
-
resources :nodes do
|
|
111
|
-
member do
|
|
112
|
-
put :drain
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
102
|
+
**Resource ORM** — ActiveRecord-like model for a remote service. Handles CRUD, validations, change tracking, and typed or dynamic attributes. Best when you own both sides of the communication.
|
|
116
103
|
|
|
117
|
-
|
|
118
|
-
module BugBunny
|
|
119
|
-
module Controllers
|
|
120
|
-
class NodesController < BugBunny::Controller
|
|
121
|
-
def index
|
|
122
|
-
nodes = Node.all # Lógica local de tu app
|
|
123
|
-
render status: :ok, json: nodes
|
|
124
|
-
end
|
|
104
|
+
**Direct Client** — `BugBunny::Client` for explicit RPC or fire-and-forget calls with full middleware control. Best when calling external services or when you need precise control over the request.
|
|
125
105
|
|
|
126
|
-
|
|
127
|
-
# El ID viene automáticamente en params[:id]
|
|
128
|
-
Node.find(params[:id]).start_drain_process!
|
|
129
|
-
render status: :accepted, json: { message: "Draining started" }
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
> **Namespace de Controladores:** Por defecto BugBunny busca los controladores bajo `BugBunny::Controllers`. Podés cambiarlo en la configuración:
|
|
137
|
-
> ```ruby
|
|
138
|
-
> BugBunny.configure do |config|
|
|
139
|
-
> config.controller_namespace = 'MyApp::RabbitControllers'
|
|
140
|
-
> end
|
|
141
|
-
> ```
|
|
106
|
+
**Consumer** — Subscribe loop that routes incoming messages to controllers, with a middleware stack for cross-cutting concerns (tracing, auth, auditing).
|
|
142
107
|
|
|
143
108
|
---
|
|
144
109
|
|
|
145
|
-
##
|
|
146
|
-
|
|
147
|
-
Puedes extender el comportamiento del cliente globalmente o por recurso:
|
|
110
|
+
## Configuration
|
|
148
111
|
|
|
149
112
|
```ruby
|
|
150
|
-
# Globalmente en el inicializador
|
|
151
113
|
BugBunny.configure do |config|
|
|
152
|
-
#
|
|
153
|
-
|
|
114
|
+
# Connection — required
|
|
115
|
+
config.host = 'localhost'
|
|
116
|
+
config.port = 5672
|
|
117
|
+
config.username = 'guest'
|
|
118
|
+
config.password = 'guest'
|
|
119
|
+
config.vhost = '/'
|
|
154
120
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
end
|
|
121
|
+
# Resilience
|
|
122
|
+
config.max_reconnect_attempts = 10 # nil = infinite
|
|
123
|
+
config.max_reconnect_interval = 60 # seconds, ceiling for backoff
|
|
124
|
+
config.network_recovery_interval = 5 # seconds, base for exponential backoff
|
|
160
125
|
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
126
|
+
# Timeouts
|
|
127
|
+
config.rpc_timeout = 30 # seconds, for synchronous RPC calls
|
|
128
|
+
config.connection_timeout = 10
|
|
129
|
+
config.read_timeout = 10
|
|
130
|
+
config.write_timeout = 10
|
|
165
131
|
|
|
166
|
-
#
|
|
167
|
-
|
|
168
|
-
|
|
132
|
+
# AMQP defaults applied to all exchanges and queues
|
|
133
|
+
config.exchange_options = { durable: true }
|
|
134
|
+
config.queue_options = { durable: true }
|
|
169
135
|
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
req.method = :get
|
|
173
|
-
req.delivery_mode = :rpc
|
|
174
|
-
req.timeout = 5
|
|
175
|
-
end
|
|
136
|
+
# Controller namespace (default: 'BugBunny::Controllers')
|
|
137
|
+
config.controller_namespace = 'MyApp::RabbitHandlers'
|
|
176
138
|
|
|
177
|
-
#
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
req.method = :get
|
|
183
|
-
req.params = { q: { active: true }, page: 2 }
|
|
139
|
+
# Logger — any object responding to debug/info/warn/error
|
|
140
|
+
config.logger = Rails.logger
|
|
141
|
+
|
|
142
|
+
# Health check file for Kubernetes / Docker Swarm liveness probes
|
|
143
|
+
config.health_check_file = '/tmp/bug_bunny_health'
|
|
184
144
|
end
|
|
185
|
-
|
|
186
|
-
client.request('users', method: :get, params: { q: { active: true }, page: 2 })
|
|
145
|
+
```
|
|
187
146
|
|
|
188
|
-
|
|
189
|
-
client.request('users/1') # Siempre :rpc
|
|
190
|
-
client.publish('events', body: { type: 'click' }) # Siempre :publish
|
|
147
|
+
`BugBunny.configure` validates all required fields on exit. A missing or invalid value raises `BugBunny::ConfigurationError` immediately, before any connection attempt.
|
|
191
148
|
|
|
192
|
-
|
|
193
|
-
response = client.request('users/1', method: :get)
|
|
194
|
-
```
|
|
149
|
+
---
|
|
195
150
|
|
|
196
|
-
|
|
151
|
+
## Routing DSL
|
|
197
152
|
|
|
198
153
|
```ruby
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
154
|
+
BugBunny.routes.draw do
|
|
155
|
+
resources :users # GET/POST users, GET/PUT/DELETE users/:id
|
|
156
|
+
resources :orders, only: [:index, :show, :create]
|
|
157
|
+
|
|
158
|
+
resources :nodes do
|
|
159
|
+
member { put :drain } # PUT nodes/:id/drain
|
|
160
|
+
collection { post :rebalance } # POST nodes/rebalance
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
namespace :api do
|
|
164
|
+
namespace :v1 do
|
|
165
|
+
resources :metrics # Routes to Api::V1::MetricsController
|
|
166
|
+
end
|
|
203
167
|
end
|
|
168
|
+
|
|
169
|
+
get 'status', to: 'health#show'
|
|
170
|
+
post 'events/:id', to: 'events#track'
|
|
204
171
|
end
|
|
205
172
|
```
|
|
206
173
|
|
|
207
174
|
---
|
|
208
175
|
|
|
209
|
-
##
|
|
210
|
-
|
|
211
|
-
BugBunny expone un middleware stack que se ejecuta **antes** de que la gema procese cada mensaje entrante (antes del primer log `consumer.message_received`). Es el punto ideal para hidratar contexto de tracing distribuido, autenticación, auditoría, etc.
|
|
212
|
-
|
|
213
|
-
### Implementar un middleware
|
|
176
|
+
## Direct Client
|
|
214
177
|
|
|
215
178
|
```ruby
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
@app.call(delivery_info, properties, body)
|
|
221
|
-
# lógica post-procesamiento (opcional)
|
|
222
|
-
end
|
|
179
|
+
pool = ConnectionPool.new(size: 5, timeout: 5) { BugBunny.create_connection }
|
|
180
|
+
client = BugBunny::Client.new(pool: pool) do |stack|
|
|
181
|
+
stack.use BugBunny::Middleware::RaiseError
|
|
182
|
+
stack.use BugBunny::Middleware::JsonResponse
|
|
223
183
|
end
|
|
224
|
-
```
|
|
225
184
|
|
|
226
|
-
|
|
185
|
+
# Synchronous RPC
|
|
186
|
+
response = client.request('users/42', method: :get)
|
|
187
|
+
response['body'] # => { 'id' => 42, 'name' => 'Alice' }
|
|
227
188
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# ...
|
|
231
|
-
end
|
|
189
|
+
# Fire-and-forget
|
|
190
|
+
client.publish('events', body: { type: 'user.signed_in', user_id: 42 })
|
|
232
191
|
|
|
233
|
-
|
|
192
|
+
# With params
|
|
193
|
+
client.request('users', method: :get, params: { role: 'admin', page: 2 })
|
|
234
194
|
```
|
|
235
195
|
|
|
236
|
-
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Consumer Middleware
|
|
237
199
|
|
|
238
|
-
|
|
200
|
+
Middlewares run before every message reaches the router. Use them for distributed tracing, authentication, or audit logging.
|
|
239
201
|
|
|
240
202
|
```ruby
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
203
|
+
class TracingMiddleware < BugBunny::ConsumerMiddleware::Base
|
|
204
|
+
def call(delivery_info, properties, body)
|
|
205
|
+
trace_id = properties.headers&.dig('X-Trace-Id')
|
|
206
|
+
MyTracer.with_trace(trace_id) { @app.call(delivery_info, properties, body) }
|
|
207
|
+
end
|
|
208
|
+
end
|
|
244
209
|
|
|
245
|
-
|
|
246
|
-
# require 'exis_ray/bug_bunny/consumer_tracing'
|
|
210
|
+
BugBunny.consumer_middlewares.use TracingMiddleware
|
|
247
211
|
```
|
|
248
212
|
|
|
249
|
-
### Datos disponibles en el middleware
|
|
250
|
-
|
|
251
|
-
| Argumento | Tipo | Contenido |
|
|
252
|
-
|---|---|---|
|
|
253
|
-
| `delivery_info` | `Bunny::DeliveryInfo` | `routing_key`, `exchange`, `delivery_tag` |
|
|
254
|
-
| `properties` | `Bunny::MessageProperties` | `headers` (headers AMQP custom), `correlation_id`, `reply_to`, `content_type` |
|
|
255
|
-
| `body` | `String` | Payload crudo del mensaje |
|
|
256
|
-
|
|
257
|
-
> **Orden de ejecución:** FIFO — el primero en registrarse es el primero en ejecutarse.
|
|
258
|
-
> `Middleware A → Middleware B → process_message`
|
|
259
|
-
|
|
260
213
|
---
|
|
261
214
|
|
|
262
|
-
##
|
|
215
|
+
## Observability
|
|
263
216
|
|
|
264
|
-
|
|
217
|
+
All internal events are emitted as structured key=value logs compatible with Datadog, CloudWatch, and ELK.
|
|
265
218
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
* **Reloj Monotónico:** Las duraciones (`duration_s`) se calculan con precisión de microsegundos usando el reloj monotónico del sistema.
|
|
271
|
-
* **Campos de Identidad:** Todos los logs incluyen `component=bug_bunny` y un `event` semántico.
|
|
272
|
-
|
|
273
|
-
**Ejemplos de Logs:**
|
|
274
|
-
```text
|
|
275
|
-
# Mensaje procesado con éxito (incluye duración y status numérico)
|
|
276
|
-
component=bug_bunny event=consumer.message_processed status=200 duration_s=0.015432 controller=UsersController action=show
|
|
277
|
-
|
|
278
|
-
# Error de ejecución (campos estandarizados)
|
|
279
|
-
component=bug_bunny event=consumer.execution_error error_class=NoMethodError error_message="undefined method..." duration_s=0.008123
|
|
280
|
-
|
|
281
|
-
# Reintento de conexión con backoff (sufijos de unidad)
|
|
282
|
-
component=bug_bunny event=consumer.connection_error error_message="..." attempt_count=3 retry_in_s=20
|
|
219
|
+
```
|
|
220
|
+
component=bug_bunny event=consumer.message_processed status=200 duration_s=0.012 controller=NodesController action=show
|
|
221
|
+
component=bug_bunny event=consumer.execution_error error_class=RuntimeError error_message="..." duration_s=0.003
|
|
222
|
+
component=bug_bunny event=consumer.connection_error attempt_count=2 retry_in_s=10 error_message="..."
|
|
283
223
|
```
|
|
284
224
|
|
|
285
|
-
|
|
286
|
-
Al usar `duration_s` como un float puro, puedes realizar consultas analíticas directamente en tu motor de logs sin parsear strings:
|
|
287
|
-
`stats avg(duration_s), max(duration_s) by controller, action`
|
|
288
|
-
|
|
289
|
-
### 2. Distributed Tracing
|
|
290
|
-
El `correlation_id` se mantiene intacto a través de toda la cadena: `Producer -> RabbitMQ -> Consumer -> Controller`.
|
|
225
|
+
Sensitive keys (`password`, `token`, `secret`, `api_key`, `authorization`, etc.) are automatically filtered to `[FILTERED]` in all log output.
|
|
291
226
|
|
|
292
|
-
|
|
293
|
-
Captura excepciones y devuélvelas como códigos de estado AMQP/HTTP.
|
|
227
|
+
---
|
|
294
228
|
|
|
295
|
-
|
|
296
|
-
class ApplicationController < BugBunny::Controller
|
|
297
|
-
rescue_from ActiveRecord::RecordNotFound do |e|
|
|
298
|
-
render status: :not_found, json: { error: "Resource missing" }
|
|
299
|
-
end
|
|
229
|
+
## Documentation
|
|
300
230
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
231
|
+
- [Concepts](docs/concepts.md) — What BugBunny is, AMQP in 5 minutes, RPC vs fire-and-forget
|
|
232
|
+
- [Routing](docs/howto/routing.md) — Full routing DSL reference
|
|
233
|
+
- [Controllers](docs/howto/controller.md) — Filters, `rescue_from`, `render`, `after_action`
|
|
234
|
+
- [Resource ORM](docs/howto/resource.md) — CRUD, typed and dynamic attributes, `.with` scoping
|
|
235
|
+
- [Client Middleware](docs/howto/middleware_client.md) — Request/response middleware stack
|
|
236
|
+
- [Consumer Middleware](docs/howto/middleware_consumer.md) — Message processing middleware stack
|
|
237
|
+
- [Distributed Tracing](docs/howto/tracing.md) — Propagating trace context through RPC cycles
|
|
238
|
+
- [Rails Setup](docs/howto/rails.md) — Full integration: Puma, Sidekiq, Zeitwerk, health checks
|
|
239
|
+
- [Testing](docs/howto/testing.md) — Unit and integration testing with Bunny mocks
|
|
307
240
|
|
|
308
241
|
---
|
|
309
242
|
|
|
310
|
-
##
|
|
243
|
+
## License
|
|
311
244
|
|
|
312
|
-
|
|
245
|
+
[MIT](https://opensource.org/licenses/MIT)
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
2
|
require 'rake/testtask'
|
|
3
|
+
require 'rspec/core/rake_task'
|
|
3
4
|
|
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
6
|
+
t.rspec_opts = '--require spec_helper'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
RSpec::Core::RakeTask.new('spec:unit') do |t|
|
|
10
|
+
t.pattern = 'spec/unit/**/*_spec.rb'
|
|
11
|
+
t.rspec_opts = '--require spec_helper'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
RSpec::Core::RakeTask.new('spec:integration') do |t|
|
|
15
|
+
t.pattern = 'spec/integration/**/*_spec.rb'
|
|
16
|
+
t.rspec_opts = '--require spec_helper'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Mantiene la tarea :test apuntando a los tests de integración legacy de Minitest
|
|
4
20
|
Rake::TestTask.new(:test) do |t|
|
|
5
21
|
t.libs << 'test'
|
|
6
22
|
t.libs << 'lib'
|
|
7
23
|
t.test_files = FileList['test/**/*_test.rb']
|
|
8
|
-
t.verbose
|
|
9
|
-
t.warning
|
|
24
|
+
t.verbose = true
|
|
25
|
+
t.warning = false
|
|
10
26
|
end
|
|
11
27
|
|
|
12
|
-
task default: :
|
|
28
|
+
task default: :spec
|
data/docs/_index.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# docs/_index.md — Documentation Manifest
|
|
2
|
+
|
|
3
|
+
This file is the source of truth for the `docs/` directory structure.
|
|
4
|
+
It is read by the `/release` command to know which files to generate or update.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Human documentation (developers integrating BugBunny)
|
|
9
|
+
|
|
10
|
+
| File | Purpose |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `concepts.md` | AMQP in 5 min, architecture diagram, RPC vs fire-and-forget, connection pool |
|
|
13
|
+
| `howto/routing.md` | Routes DSL: `resources`, `namespace`, `member`, `collection`, `recognize` |
|
|
14
|
+
| `howto/controller.md` | `params`, `before_action`, `after_action`, `around_action`, `rescue_from`, `render` |
|
|
15
|
+
| `howto/resource.md` | CRUD methods, typed vs dynamic attributes, dirty tracking, validations, `.with` |
|
|
16
|
+
| `howto/middleware_client.md` | Client-side middlewares: built-ins, custom, usage in Client and Resource |
|
|
17
|
+
| `howto/middleware_consumer.md` | Consumer-side middlewares: execution order, writing, registering |
|
|
18
|
+
| `howto/tracing.md` | Trace context propagation: `rpc_reply_headers`, `on_rpc_reply`, consumer middleware |
|
|
19
|
+
| `howto/rails.md` | Full Rails setup: initializer, connection pool, Zeitwerk, Puma, Sidekiq, K8s health checks |
|
|
20
|
+
| `howto/testing.md` | Bunny doubles, unit tests for controllers/middleware, integration helper |
|
|
21
|
+
|
|
22
|
+
These files are referenced by `README.md`. Update them before updating the README.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## AI documentation (agents consuming or maintaining BugBunny)
|
|
27
|
+
|
|
28
|
+
Managed by `docs/ai/_index.md`. See that file for the full manifest and audience breakdown.
|
|
29
|
+
|
|
30
|
+
| File | Audience | Purpose |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `ai/_index.md` | internal + external | Manifest: version, profile, file index |
|
|
33
|
+
| `ai/glossary.md` | internal + external | Domain terms with precise definitions |
|
|
34
|
+
| `ai/architecture.md` | internal | Internal patterns, component map, data flows |
|
|
35
|
+
| `ai/api.md` | external | Public API contracts |
|
|
36
|
+
| `ai/faq_internal.md` | internal | Q&A for gem maintainers |
|
|
37
|
+
| `ai/faq_external.md` | external | Q&A for gem integrators |
|
|
38
|
+
| `ai/antipatterns.md` | internal + external | What NOT to do and why |
|
|
39
|
+
| `ai/errors.md` | external | All exceptions with cause and resolution |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Update rules for `/release`
|
|
44
|
+
|
|
45
|
+
1. Run tests first. If they fail, stop.
|
|
46
|
+
2. Read this file to discover all files to update.
|
|
47
|
+
3. For each file in **Human documentation**: update only sections affected by the changes in this release.
|
|
48
|
+
4. For each file in **AI documentation**: update only sections affected by the changes. Update `version` in `ai/_index.md`.
|
|
49
|
+
5. Update `README.md` last — it depends on `docs/howto/` being up to date.
|
|
50
|
+
6. Show the full diff to the developer and wait for approval before touching version or CHANGELOG.
|
data/docs/ai/_index.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: knowledge_base
|
|
3
|
+
kind: gem
|
|
4
|
+
name: bug_bunny
|
|
5
|
+
version: 4.8.0
|
|
6
|
+
profile: full
|
|
7
|
+
language: ruby
|
|
8
|
+
generated_by: gem-ai-setup@1.0.0
|
|
9
|
+
audiences:
|
|
10
|
+
- internal
|
|
11
|
+
- external
|
|
12
|
+
files:
|
|
13
|
+
- path: glossary.md
|
|
14
|
+
audience: [internal, external]
|
|
15
|
+
- path: architecture.md
|
|
16
|
+
audience: [internal]
|
|
17
|
+
- path: api.md
|
|
18
|
+
audience: [external]
|
|
19
|
+
- path: faq_internal.md
|
|
20
|
+
audience: [internal]
|
|
21
|
+
- path: faq_external.md
|
|
22
|
+
audience: [external]
|
|
23
|
+
- path: antipatterns.md
|
|
24
|
+
audience: [internal, external]
|
|
25
|
+
- path: errors.md
|
|
26
|
+
audience: [external]
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## What is BugBunny?
|
|
30
|
+
|
|
31
|
+
BugBunny is a Ruby gem that implements a RESTful routing layer over AMQP (RabbitMQ). It lets microservices communicate via RabbitMQ using familiar HTTP patterns: verbs (GET, POST, PUT, DELETE), controllers, declarative routes, synchronous RPC, and fire-and-forget.
|
|
32
|
+
|
|
33
|
+
**Problem solved:** Eliminates direct HTTP coupling between microservices. RabbitMQ acts as the message bus with the same ergonomics as a web framework.
|
|
34
|
+
|
|
35
|
+
## Version
|
|
36
|
+
|
|
37
|
+
4.8.0 — April 2026
|
|
38
|
+
|
|
39
|
+
## Key features in this version
|
|
40
|
+
|
|
41
|
+
- Namespace routing (`namespace :admin { resources :users }`)
|
|
42
|
+
- `after_action` filter (runs after action, not after `before_action` halts)
|
|
43
|
+
- `render(headers:)` — inject custom headers into RPC replies
|
|
44
|
+
- `Consumer#shutdown` — explicit graceful shutdown with health check cleanup
|
|
45
|
+
- `Configuration#validate!` — invoked automatically at end of `BugBunny.configure`
|
|
46
|
+
- Producer/Session caching per connection slot (prevents double-consumer AMQP error)
|
|
47
|
+
- Expanded `SENSITIVE_KEYS` filter in `safe_log`
|
|
48
|
+
- `ConsumerMiddleware::Stack` mutex for thread-safe registration
|
|
49
|
+
|
|
50
|
+
## How to use this knowledge base
|
|
51
|
+
|
|
52
|
+
- **Building an integration** → start with `api.md`, then `faq_external.md`
|
|
53
|
+
- **Debugging errors** → `errors.md`
|
|
54
|
+
- **Avoiding mistakes** → `antipatterns.md`
|
|
55
|
+
- **Understanding internals** → `architecture.md`, then `faq_internal.md`
|
|
56
|
+
- **Domain vocabulary** → `glossary.md`
|