|
| 1 | +# Dead Letter Queue Requeue Feature |
| 2 | + |
| 3 | +## Overview |
| 4 | +API endpoint to requeue messages from a dead letter queue to a specified destination queue using the RabbitMQ Shovel plugin. |
| 5 | + |
| 6 | +## Requirements |
| 7 | +- Support requeue all messages OR a specific count (via query parameter) |
| 8 | +- Require explicit destination queue in request body |
| 9 | +- Use RabbitMQ Shovel plugin for atomic message transfers |
| 10 | +- Self-deleting shovel (no polling, no manual cleanup) |
| 11 | +- Fail gracefully if requeue already in progress |
| 12 | +- Return estimated message count in response |
| 13 | +- Clear error handling when shovel plugin is not enabled |
| 14 | + |
| 15 | +## API Design |
| 16 | + |
| 17 | +### Endpoint |
| 18 | +``` |
| 19 | +POST /dlq/:queueName/requeue?count=N |
| 20 | +``` |
| 21 | + |
| 22 | +**Parameters:** |
| 23 | +- `queueName` (path): Name of the dead letter queue to consume from |
| 24 | +- `count` (query, optional): Number of messages to requeue. If omitted, requeues all. |
| 25 | + |
| 26 | +**Request Body:** |
| 27 | +```json |
| 28 | +{ |
| 29 | + "destinationQueue": "target-queue" |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +**Response:** |
| 34 | +```json |
| 35 | +{ |
| 36 | + "status": "initiated", |
| 37 | + "requeued": 5, |
| 38 | + "failed": 0, |
| 39 | + "errors": [] |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +- `status`: `"initiated"` (async, shovel created) or `"completed"` (sync, e.g. MemoryQueue) |
| 44 | +- `requeued`: Estimated number of messages to be moved (queried before shovel creation) |
| 45 | + |
| 46 | +**Error Responses:** |
| 47 | +- `400`: Missing/invalid `destinationQueue` or invalid `count` |
| 48 | +- `409`-style error: Requeue already in progress for this queue |
| 49 | +- `500`: Shovel plugin not available, auth failure, or connection error |
| 50 | + |
| 51 | +## Implementation |
| 52 | + |
| 53 | +### 1. Types in Queue class |
| 54 | +**File:** `src/queue/index.ts` |
| 55 | + |
| 56 | +```typescript |
| 57 | +export type RequeueOptions = { |
| 58 | + count?: number; // Number of messages to requeue (undefined = all) |
| 59 | + destinationQueue: string; // Target queue to move messages to |
| 60 | +} |
| 61 | + |
| 62 | +export type RequeueResult = { |
| 63 | + status: 'initiated' | 'completed'; |
| 64 | + requeued: number; // Estimated (initiated) or actual (completed) count |
| 65 | + failed: number; |
| 66 | + errors: Array<{ error: string }>; |
| 67 | +} |
| 68 | +``` |
| 69 | +
|
| 70 | +### 2. AmqpQueue implementation via Shovel plugin |
| 71 | +**File:** `src/queue/drivers/amqp.ts` |
| 72 | +
|
| 73 | +Uses RabbitMQ Management API: |
| 74 | +
|
| 75 | +1. **Check for existing shovel** via `GET /api/shovels/{vhost}/{name}` |
| 76 | + - Uses deterministic name: `arnavon-requeue-{sourceQueue}` |
| 77 | + - Fails if shovel already exists (requeue in progress) |
| 78 | +
|
| 79 | +2. **Get queue message count** via `GET /api/queues/{vhost}/{queue}` |
| 80 | + - Used to return estimated requeue count |
| 81 | +
|
| 82 | +3. **Create self-deleting shovel** via `PUT /api/parameters/shovel/{vhost}/{name}` |
| 83 | + - `src-delete-after`: `count` or `'queue-length'` (auto-deletes when done) |
| 84 | + - `ack-mode`: `'on-confirm'` for safety |
| 85 | +
|
| 86 | +4. **Return immediately** with estimated count |
| 87 | + - No polling, no manual deletion |
| 88 | + - Shovel auto-deletes after moving messages |
| 89 | +
|
| 90 | +Key shovel configuration: |
| 91 | +```json |
| 92 | +{ |
| 93 | + "value": { |
| 94 | + "src-uri": "amqp://...", |
| 95 | + "src-queue": "dead-letters", |
| 96 | + "dest-uri": "amqp://...", |
| 97 | + "dest-queue": "send-email", |
| 98 | + "src-delete-after": "queue-length", |
| 99 | + "ack-mode": "on-confirm" |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | +
|
| 104 | +### 3. MemoryQueue implementation |
| 105 | +**File:** `src/queue/drivers/memory.ts` |
| 106 | +
|
| 107 | +Synchronous implementation moving items between named queues for testing. |
| 108 | +Returns `status: 'completed'` with actual count. |
| 109 | +
|
| 110 | +### 4. REST API endpoint |
| 111 | +**File:** `src/server/rest/index.ts` |
| 112 | +
|
| 113 | +```typescript |
| 114 | +api.post('/dlq/:queueName/requeue', async (req, res, next) => { |
| 115 | + const { queueName } = req.params; |
| 116 | + const count = req.query.count ? parseInt(req.query.count as string, 10) : undefined; |
| 117 | + const { destinationQueue } = req.body || {}; |
| 118 | + // Validation and call to Arnavon.queue.requeue() |
| 119 | +}); |
| 120 | +``` |
| 121 | + |
| 122 | +## Files Modified |
| 123 | + |
| 124 | +1. `src/queue/index.ts` - Add `requeue` method and types |
| 125 | +2. `src/queue/drivers/amqp.ts` - Implement via Shovel API |
| 126 | +3. `src/queue/drivers/memory.ts` - Simple queue-to-queue move |
| 127 | +4. `src/server/rest/index.ts` - Add `/dlq/:queueName/requeue` endpoint |
| 128 | + |
| 129 | +## Configuration |
| 130 | + |
| 131 | +### RabbitMQ Plugins |
| 132 | +**File:** `example/rabbitmq/enabled_plugins` |
| 133 | +```erlang |
| 134 | +[rabbitmq_management,rabbitmq_shovel,rabbitmq_shovel_management]. |
| 135 | +``` |
| 136 | + |
| 137 | +### Docker Compose |
| 138 | +**File:** `example/docker-compose.yml` |
| 139 | +- RabbitMQ 4.x with management image |
| 140 | +- Mount enabled_plugins file |
| 141 | +- Healthcheck for service readiness |
| 142 | + |
| 143 | +## Testing |
| 144 | + |
| 145 | +### Unit Tests |
| 146 | +- `tests/queue/index.spec.ts` - Base Queue class requeue method |
| 147 | +- `tests/queue/drivers/memory.spec.ts` - MemoryQueue requeue |
| 148 | +- `tests/server/rest/api.spec.ts` - REST API endpoint tests |
| 149 | + |
| 150 | +### Integration Tests |
| 151 | +- `example/webspicy/arnavon/dlq/_queueName/requeue/post.yml` - Webspicy tests |
| 152 | +- `example/webspicy/schema.fio` - Finitio types for request/response |
| 153 | + |
| 154 | +### Manual Testing |
| 155 | +1. Start RabbitMQ with shovel plugins enabled |
| 156 | +2. Push jobs to a queue with DLQ configured |
| 157 | +3. Force jobs to fail (so they go to DLQ) |
| 158 | +4. Call `POST /dlq/dead-letters/requeue -d '{"destinationQueue": "send-email"}'` |
| 159 | +5. Verify response contains estimated count |
| 160 | +6. Verify messages are moved to destination queue |
| 161 | +7. Verify shovel auto-deleted after completion |
0 commit comments