agent99 0.0.4 → 0.0.5
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/A2A_SPEC-dev.md +1829 -0
- data/CHANGELOG.md +31 -0
- data/COMMITS.md +196 -0
- data/DOCS.md +96 -0
- data/README.md +200 -78
- data/Rakefile +62 -0
- data/docs/AI/htm.md +215 -0
- data/docs/AI/htm.rb +141 -0
- data/docs/AI/htm_demo.db +0 -0
- data/docs/AI/notes_on_htm_implementation.md +1319 -0
- data/docs/AI/some_code.rb +692 -0
- data/docs/advanced-topics/a2a-protocol.md +13 -0
- data/docs/{control_actions.md → advanced-topics/control-actions.md} +2 -0
- data/docs/advanced-topics/model-context-protocol.md +4 -0
- data/docs/advanced-topics/multi-agent-processing.md +674 -0
- data/docs/agent-development/request-response-handling.md +512 -0
- data/docs/api-reference/agent99-base.md +463 -0
- data/docs/api-reference/message-clients.md +495 -0
- data/docs/api-reference/registry-client.md +470 -0
- data/docs/api-reference/schemas.md +518 -0
- data/docs/assets/css/custom.css +27 -0
- data/docs/assets/images/agent-lifecycle.svg +73 -0
- data/docs/assets/images/agent-registry-process.svg +86 -0
- data/docs/assets/images/agent-registry-processes.svg +114 -0
- data/docs/assets/images/agent-types-overview.svg +51 -0
- data/docs/assets/images/agent99-architecture.svg +85 -0
- data/docs/assets/images/agent99_logo.png +0 -0
- data/docs/assets/images/control-actions-state.svg +83 -0
- data/docs/assets/images/knowledge-graph.svg +77 -0
- data/docs/assets/images/message-processing-flow.svg +148 -0
- data/docs/assets/images/multi-agent-system.svg +66 -0
- data/docs/assets/images/proxy-pattern-sequence.svg +48 -0
- data/docs/assets/images/request-flow.svg +97 -0
- data/docs/assets/images/request-processing-lifecycle.svg +50 -0
- data/docs/assets/images/request-response-sequence.svg +39 -0
- data/docs/{agent_lifecycle.md → core-concepts/agent-lifecycle.md} +2 -0
- data/docs/core-concepts/agent-types.md +255 -0
- data/docs/{architecture.md → core-concepts/architecture.md} +5 -5
- data/docs/{what_is_an_agent.md → core-concepts/what-is-an-agent.md} +1 -1
- data/docs/diagrams/message-flow-sequence.svg +198 -0
- data/docs/diagrams/p2p-network-topology.svg +181 -0
- data/docs/diagrams/smart-transport-routing.svg +165 -0
- data/docs/diagrams/three-layer-architecture.svg +77 -0
- data/docs/diagrams/transport-extension-api.svg +309 -0
- data/docs/diagrams/transport-extension-architecture.svg +234 -0
- data/docs/diagrams/transport-selection-flowchart.svg +264 -0
- data/docs/examples/advanced-examples.md +951 -0
- data/docs/examples/basic-examples.md +268 -0
- data/docs/{agent_registry_processes.md → framework-components/agent-registry.md} +1 -1
- data/docs/{message_processing.md → framework-components/message-processing.md} +3 -1
- data/docs/getting-started/basic-example.md +306 -0
- data/docs/getting-started/installation.md +160 -0
- data/docs/getting-started/overview.md +64 -0
- data/docs/getting-started/quick-start.md +179 -0
- data/docs/index.md +97 -0
- data/examples/DEMO.md +148 -0
- data/examples/README.md +50 -0
- data/examples/bad_agent.rb +32 -0
- data/examples/registry.rb +0 -8
- data/examples/run_demo.rb +433 -0
- data/lib/agent99/amqp_message_client.rb +2 -2
- data/lib/agent99/base.rb +1 -1
- data/lib/agent99/message_processing.rb +6 -12
- data/lib/agent99/registry_client.rb +4 -1
- data/lib/agent99/version.rb +1 -1
- data/lib/agent99.rb +1 -1
- data/mkdocs.yml +195 -0
- data/p2p_plan.md +533 -0
- data/p2p_roadmap.md +299 -0
- data/registry_plan.md +1818 -0
- metadata +89 -32
- data/docs/README.md +0 -57
- data/docs/diagrams/agent_registry_processes.dot +0 -42
- data/docs/diagrams/agent_registry_processes.png +0 -0
- data/docs/diagrams/high_level_architecture.dot +0 -26
- data/docs/diagrams/high_level_architecture.png +0 -0
- data/docs/diagrams/request_flow.dot +0 -42
- data/docs/diagrams/request_flow.png +0 -0
- /data/docs/{advanced_features.md → advanced-topics/advanced-features.md} +0 -0
- /data/docs/{extending_the_framework.md → advanced-topics/extending-the-framework.md} +0 -0
- /data/docs/{custom_agent_implementation.md → agent-development/custom-agent-implementation.md} +0 -0
- /data/docs/{error_handling_and_logging.md → agent-development/error-handling-and-logging.md} +0 -0
- /data/docs/{schema_definition.md → agent-development/schema-definition.md} +0 -0
- /data/docs/{api_reference.md → api-reference/overview.md} +0 -0
- /data/docs/{agent_discovery.md → framework-components/agent-discovery.md} +0 -0
- /data/docs/{messaging_system.md → framework-components/messaging-system.md} +0 -0
- /data/docs/{breaking_change_v0.0.4.md → operations/breaking-changes.md} +0 -0
- /data/docs/{configuration.md → operations/configuration.md} +0 -0
- /data/docs/{preformance_considerations.md → operations/performance-considerations.md} +0 -0
- /data/docs/{security.md → operations/security.md} +0 -0
- /data/docs/{troubleshooting.md → operations/troubleshooting.md} +0 -0
@@ -0,0 +1,512 @@
|
|
1
|
+
# Request & Response Handling
|
2
|
+
|
3
|
+
Understanding how to properly handle requests and responses is fundamental to building robust Agent99 applications. This guide covers patterns, best practices, and advanced techniques.
|
4
|
+
|
5
|
+
## Request Processing Lifecycle
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
## Basic Request Handling
|
10
|
+
|
11
|
+
### Simple Request Processing
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class SimpleAgent < Agent99::Base
|
15
|
+
def process_request(payload)
|
16
|
+
# Extract data from payload
|
17
|
+
user_name = payload.dig(:name) || "Anonymous"
|
18
|
+
|
19
|
+
# Perform business logic
|
20
|
+
greeting = generate_greeting(user_name)
|
21
|
+
|
22
|
+
# Send response
|
23
|
+
send_response(message: greeting)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def generate_greeting(name)
|
29
|
+
"Hello, #{name}! Welcome to Agent99."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
### With Input Validation
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class ValidatingAgent < Agent99::Base
|
38
|
+
def process_request(payload)
|
39
|
+
# Validate required fields
|
40
|
+
unless payload.key?(:user_id)
|
41
|
+
return send_error("Missing required field: user_id", "MISSING_FIELD")
|
42
|
+
end
|
43
|
+
|
44
|
+
unless payload[:user_id].is_a?(String) && !payload[:user_id].empty?
|
45
|
+
return send_error("Invalid user_id: must be non-empty string", "INVALID_FORMAT")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Process valid request
|
49
|
+
result = lookup_user_data(payload[:user_id])
|
50
|
+
send_response(result)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def lookup_user_data(user_id)
|
56
|
+
# Simulate database lookup
|
57
|
+
{
|
58
|
+
user_id: user_id,
|
59
|
+
name: "User #{user_id}",
|
60
|
+
status: "active",
|
61
|
+
last_seen: Time.now.iso8601
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
## Advanced Request Patterns
|
68
|
+
|
69
|
+
### Asynchronous Processing
|
70
|
+
|
71
|
+
For long-running operations, acknowledge receipt immediately and process asynchronously:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class AsyncProcessingAgent < Agent99::Base
|
75
|
+
def initialize
|
76
|
+
super
|
77
|
+
@job_queue = Queue.new
|
78
|
+
@worker_thread = Thread.new { process_jobs }
|
79
|
+
end
|
80
|
+
|
81
|
+
def process_request(payload)
|
82
|
+
job_id = SecureRandom.uuid
|
83
|
+
|
84
|
+
# Acknowledge receipt immediately
|
85
|
+
send_response(
|
86
|
+
status: "accepted",
|
87
|
+
job_id: job_id,
|
88
|
+
message: "Request queued for processing"
|
89
|
+
)
|
90
|
+
|
91
|
+
# Queue for background processing
|
92
|
+
@job_queue << {
|
93
|
+
id: job_id,
|
94
|
+
payload: payload,
|
95
|
+
requester: current_request_id
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def process_jobs
|
102
|
+
while job = @job_queue.pop
|
103
|
+
begin
|
104
|
+
result = perform_long_operation(job[:payload])
|
105
|
+
|
106
|
+
# Send completion notification (if supported)
|
107
|
+
notify_completion(job[:requester], job[:id], result)
|
108
|
+
rescue => e
|
109
|
+
logger.error "Job #{job[:id]} failed: #{e.message}"
|
110
|
+
notify_failure(job[:requester], job[:id], e.message)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def perform_long_operation(payload)
|
116
|
+
# Simulate long operation
|
117
|
+
sleep(5)
|
118
|
+
{ result: "Operation completed", data: payload }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
### Streaming Responses
|
124
|
+
|
125
|
+
For data that comes in chunks:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class StreamingAgent < Agent99::Base
|
129
|
+
def process_request(payload)
|
130
|
+
file_path = payload.dig(:file_path)
|
131
|
+
|
132
|
+
unless File.exist?(file_path)
|
133
|
+
return send_error("File not found: #{file_path}", "FILE_NOT_FOUND")
|
134
|
+
end
|
135
|
+
|
136
|
+
# Send initial response
|
137
|
+
send_response(
|
138
|
+
status: "streaming",
|
139
|
+
total_size: File.size(file_path),
|
140
|
+
chunk_size: 1024
|
141
|
+
)
|
142
|
+
|
143
|
+
# Stream file contents
|
144
|
+
File.open(file_path, 'rb') do |file|
|
145
|
+
chunk_number = 0
|
146
|
+
while chunk = file.read(1024)
|
147
|
+
send_chunk(chunk_number, chunk)
|
148
|
+
chunk_number += 1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Send completion marker
|
153
|
+
send_completion()
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def send_chunk(number, data)
|
159
|
+
# Implementation depends on messaging system
|
160
|
+
# This is a conceptual example
|
161
|
+
publish_message({
|
162
|
+
type: "chunk",
|
163
|
+
sequence: number,
|
164
|
+
data: Base64.encode64(data)
|
165
|
+
})
|
166
|
+
end
|
167
|
+
|
168
|
+
def send_completion
|
169
|
+
publish_message({
|
170
|
+
type: "stream_complete"
|
171
|
+
})
|
172
|
+
end
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
## Response Patterns
|
177
|
+
|
178
|
+
### Success Responses
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
# Simple success
|
182
|
+
send_response(result: "Operation successful")
|
183
|
+
|
184
|
+
# Rich success response
|
185
|
+
send_response(
|
186
|
+
status: "success",
|
187
|
+
data: processed_data,
|
188
|
+
metadata: {
|
189
|
+
processing_time: elapsed_time,
|
190
|
+
version: "1.0.0"
|
191
|
+
}
|
192
|
+
)
|
193
|
+
|
194
|
+
# Collection response
|
195
|
+
send_response(
|
196
|
+
items: search_results,
|
197
|
+
total_count: total_count,
|
198
|
+
page: current_page,
|
199
|
+
has_more: has_more_pages
|
200
|
+
)
|
201
|
+
```
|
202
|
+
|
203
|
+
### Error Responses
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
# Simple error
|
207
|
+
send_error("Something went wrong")
|
208
|
+
|
209
|
+
# Structured error
|
210
|
+
send_error("Invalid input data", "VALIDATION_ERROR")
|
211
|
+
|
212
|
+
# Rich error response
|
213
|
+
send_error(
|
214
|
+
"Database connection failed",
|
215
|
+
"DATABASE_ERROR",
|
216
|
+
{
|
217
|
+
retry_after: 30,
|
218
|
+
support_id: "ERR-#{SecureRandom.hex(8)}",
|
219
|
+
details: {
|
220
|
+
attempted_host: db_host,
|
221
|
+
timeout: connection_timeout
|
222
|
+
}
|
223
|
+
}
|
224
|
+
)
|
225
|
+
```
|
226
|
+
|
227
|
+
## Request Context and Headers
|
228
|
+
|
229
|
+
### Accessing Request Headers
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
class HeaderAwareAgent < Agent99::Base
|
233
|
+
def process_request(payload)
|
234
|
+
# Access headers from the current request
|
235
|
+
request_id = header_value('request_id')
|
236
|
+
user_id = header_value('user_id')
|
237
|
+
correlation_id = header_value('correlation_id')
|
238
|
+
|
239
|
+
logger.info "Processing request #{request_id} for user #{user_id}"
|
240
|
+
|
241
|
+
# Use headers in processing
|
242
|
+
if authorized_user?(user_id)
|
243
|
+
result = perform_operation(payload)
|
244
|
+
send_response(result)
|
245
|
+
else
|
246
|
+
send_error("Unauthorized access", "UNAUTHORIZED")
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
def authorized_user?(user_id)
|
253
|
+
# Check authorization logic
|
254
|
+
user_id && user_id.start_with?('user_')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
```
|
258
|
+
|
259
|
+
### Setting Response Headers
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
def process_request(payload)
|
263
|
+
result = process_data(payload)
|
264
|
+
|
265
|
+
# Set custom headers in response
|
266
|
+
set_header('processing_node', Socket.gethostname)
|
267
|
+
set_header('cache_status', 'miss')
|
268
|
+
set_header('processing_time', elapsed_time.to_s)
|
269
|
+
|
270
|
+
send_response(result)
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
## Error Handling Strategies
|
275
|
+
|
276
|
+
### Graceful Degradation
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
class ResilientAgent < Agent99::Base
|
280
|
+
def process_request(payload)
|
281
|
+
begin
|
282
|
+
# Try primary service
|
283
|
+
result = primary_service.process(payload)
|
284
|
+
send_response(result)
|
285
|
+
rescue PrimaryServiceError => e
|
286
|
+
logger.warn "Primary service failed: #{e.message}"
|
287
|
+
|
288
|
+
begin
|
289
|
+
# Fallback to secondary service
|
290
|
+
result = secondary_service.process(payload)
|
291
|
+
result[:fallback_used] = true
|
292
|
+
send_response(result)
|
293
|
+
rescue SecondaryServiceError => e2
|
294
|
+
logger.error "Both services failed: #{e2.message}"
|
295
|
+
|
296
|
+
# Return cached or default result
|
297
|
+
default_result = get_cached_result(payload) || default_response
|
298
|
+
default_result[:degraded_service] = true
|
299
|
+
send_response(default_result)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
private
|
305
|
+
|
306
|
+
def get_cached_result(payload)
|
307
|
+
# Check cache for previous result
|
308
|
+
cache_key = generate_cache_key(payload)
|
309
|
+
cached_data = cache.get(cache_key)
|
310
|
+
|
311
|
+
if cached_data && fresh_enough?(cached_data)
|
312
|
+
cached_data[:from_cache] = true
|
313
|
+
return cached_data
|
314
|
+
end
|
315
|
+
|
316
|
+
nil
|
317
|
+
end
|
318
|
+
|
319
|
+
def default_response
|
320
|
+
{
|
321
|
+
status: "service_unavailable",
|
322
|
+
message: "Service temporarily unavailable",
|
323
|
+
retry_after: 60
|
324
|
+
}
|
325
|
+
end
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
### Circuit Breaker Pattern
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
class CircuitBreakerAgent < Agent99::Base
|
333
|
+
def initialize
|
334
|
+
super
|
335
|
+
@circuit_breaker = CircuitBreaker.new(
|
336
|
+
failure_threshold: 5,
|
337
|
+
recovery_timeout: 30
|
338
|
+
)
|
339
|
+
end
|
340
|
+
|
341
|
+
def process_request(payload)
|
342
|
+
@circuit_breaker.call do
|
343
|
+
# Potentially failing operation
|
344
|
+
external_service.process(payload)
|
345
|
+
end.then do |result|
|
346
|
+
send_response(result)
|
347
|
+
end.rescue do |error|
|
348
|
+
case error
|
349
|
+
when CircuitBreaker::OpenError
|
350
|
+
send_error("Service temporarily unavailable", "CIRCUIT_OPEN")
|
351
|
+
else
|
352
|
+
send_error("Service error: #{error.message}", "SERVICE_ERROR")
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
```
|
358
|
+
|
359
|
+
## Request Routing and Delegation
|
360
|
+
|
361
|
+
### Multi-operation Agent
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
class MultiOperationAgent < Agent99::Base
|
365
|
+
def process_request(payload)
|
366
|
+
operation = payload.dig(:operation)
|
367
|
+
|
368
|
+
case operation
|
369
|
+
when 'create'
|
370
|
+
handle_create(payload)
|
371
|
+
when 'read'
|
372
|
+
handle_read(payload)
|
373
|
+
when 'update'
|
374
|
+
handle_update(payload)
|
375
|
+
when 'delete'
|
376
|
+
handle_delete(payload)
|
377
|
+
else
|
378
|
+
send_error("Unknown operation: #{operation}", "INVALID_OPERATION")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
private
|
383
|
+
|
384
|
+
def handle_create(payload)
|
385
|
+
# Creation logic
|
386
|
+
entity = create_entity(payload[:data])
|
387
|
+
send_response(entity: entity, status: "created")
|
388
|
+
end
|
389
|
+
|
390
|
+
def handle_read(payload)
|
391
|
+
# Read logic
|
392
|
+
entity = find_entity(payload[:id])
|
393
|
+
if entity
|
394
|
+
send_response(entity: entity)
|
395
|
+
else
|
396
|
+
send_error("Entity not found", "NOT_FOUND")
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def handle_update(payload)
|
401
|
+
# Update logic
|
402
|
+
entity = update_entity(payload[:id], payload[:data])
|
403
|
+
send_response(entity: entity, status: "updated")
|
404
|
+
end
|
405
|
+
|
406
|
+
def handle_delete(payload)
|
407
|
+
# Delete logic
|
408
|
+
delete_entity(payload[:id])
|
409
|
+
send_response(status: "deleted")
|
410
|
+
end
|
411
|
+
end
|
412
|
+
```
|
413
|
+
|
414
|
+
## Testing Request Handling
|
415
|
+
|
416
|
+
### Unit Testing
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
require 'minitest/autorun'
|
420
|
+
|
421
|
+
class TestCalculatorAgent < Minitest::Test
|
422
|
+
def setup
|
423
|
+
@agent = CalculatorAgent.new
|
424
|
+
end
|
425
|
+
|
426
|
+
def test_successful_addition
|
427
|
+
payload = { operation: 'add', a: 5, b: 3 }
|
428
|
+
|
429
|
+
# Mock the send_response method
|
430
|
+
response = nil
|
431
|
+
@agent.stub(:send_response, ->(data) { response = data }) do
|
432
|
+
@agent.process_request(payload)
|
433
|
+
end
|
434
|
+
|
435
|
+
assert_equal 8, response[:result]
|
436
|
+
assert_equal 'add', response[:operation]
|
437
|
+
end
|
438
|
+
|
439
|
+
def test_division_by_zero
|
440
|
+
payload = { operation: 'divide', a: 10, b: 0 }
|
441
|
+
|
442
|
+
error_response = nil
|
443
|
+
@agent.stub(:send_error, ->(msg, code) {
|
444
|
+
error_response = { message: msg, code: code }
|
445
|
+
}) do
|
446
|
+
@agent.process_request(payload)
|
447
|
+
end
|
448
|
+
|
449
|
+
assert_equal 'DIVISION_BY_ZERO', error_response[:code]
|
450
|
+
end
|
451
|
+
end
|
452
|
+
```
|
453
|
+
|
454
|
+
## Best Practices
|
455
|
+
|
456
|
+
### Do's ✅
|
457
|
+
|
458
|
+
- **Always respond**: Every request should get a response
|
459
|
+
- **Validate early**: Check inputs before processing
|
460
|
+
- **Use schemas**: Define and validate request/response structures
|
461
|
+
- **Log appropriately**: Track requests without logging sensitive data
|
462
|
+
- **Handle timeouts**: Set reasonable processing time limits
|
463
|
+
- **Provide context**: Include helpful error messages and codes
|
464
|
+
|
465
|
+
### Don'ts ❌
|
466
|
+
|
467
|
+
- **Don't block indefinitely**: Always have timeouts
|
468
|
+
- **Don't expose internals**: Keep error messages user-friendly
|
469
|
+
- **Don't ignore errors**: Handle and respond to all error conditions
|
470
|
+
- **Don't trust input**: Always validate and sanitize
|
471
|
+
- **Don't forget correlation**: Maintain request tracing across calls
|
472
|
+
|
473
|
+
## Performance Considerations
|
474
|
+
|
475
|
+
### Memory Management
|
476
|
+
|
477
|
+
```ruby
|
478
|
+
def process_request(payload)
|
479
|
+
# Stream large data instead of loading all at once
|
480
|
+
if payload[:data_size] > LARGE_DATA_THRESHOLD
|
481
|
+
process_data_streaming(payload)
|
482
|
+
else
|
483
|
+
process_data_in_memory(payload)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
```
|
487
|
+
|
488
|
+
### Connection Pooling
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
class DatabaseAgent < Agent99::Base
|
492
|
+
def initialize
|
493
|
+
super
|
494
|
+
@connection_pool = ConnectionPool.new(size: 10) do
|
495
|
+
Database.connect
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def process_request(payload)
|
500
|
+
@connection_pool.with do |connection|
|
501
|
+
result = connection.query(payload[:sql])
|
502
|
+
send_response(rows: result.to_a)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
```
|
507
|
+
|
508
|
+
## Next Steps
|
509
|
+
|
510
|
+
- **[Error Handling & Logging](error-handling-and-logging.md)** - Comprehensive error strategies
|
511
|
+
- **[Schema Definition](schema-definition.md)** - Advanced schema patterns
|
512
|
+
- **[Advanced Features](../advanced-topics/advanced-features.md)** - Complex patterns and techniques
|