Skip to content

Commit 758f78c

Browse files
add the ability to create internal queues as quorum type instead of hard-coded classic
1 parent eba8c3f commit 758f78c

5 files changed

Lines changed: 75 additions & 18 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ new DemoHandler('demoQueue', rabbit, {
121121
retryDelay: 1000,
122122
logEnabled: true, //log queue processing time
123123
scope: 'SINGLETON', //can also be 'PROTOTYPE' to create a new instance every time
124-
createAndSubscribeToQueue: true // used internally no need to overwriteÏÏ
124+
createAndSubscribeToQueue: true // used internally no need to overwrite
125125
});
126126

127127
rabbit.publish('demoQueue', { test: 'data' }, { correlationId: '4' });
@@ -262,6 +262,9 @@ When declaring queues, the following rules apply:
262262

263263
The type of a queue is **immutable** once it has been declared. Attempting to change it after creation will result in a **PRECONDITION_FAILED** error.
264264

265+
So far the default internal queue `({prefix}_)?delay_reply` was always created as classic. The same applies for queues created when publishing with delay which followed the format `({prefix}_)?delay_{expiration}`. In order to avoid conflicts with existing queues in the cluster , when opted-in to create queues as `quorum` default type , new internal queue will be created with the format `({prefix}_)?delay_quorum_reply`. That way you can have old deployments using the classic queues and newer deployments with quorum queues.
266+
267+
265268
### Changelog
266269

267270
### New in v5.4.x

test/delay-queue.test.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ describe('Test DelayQueue', function() {
2222
await rabbit.destroyQueue('delay_3000');
2323
await rabbit.destroyQueue('delay_10');
2424
await rabbit.destroyQueue('delay_reply');
25-
});
26-
27-
afterEach(async function() {
25+
await rabbit.destroyQueue('delay_quorum_reply');
2826
await rabbit.close();
2927
});
3028

@@ -38,7 +36,7 @@ describe('Test DelayQueue', function() {
3836
'delay',
3937
{
4038
deadLetterExchange: '',
41-
deadLetterRoutingKey: 'delay_reply'
39+
deadLetterRoutingKey: 'delay_reply',
4240
}
4341
]);
4442
});
@@ -85,4 +83,43 @@ describe('Test DelayQueue', function() {
8583
spy.args[0].should.containDeep([{ queueName: 'queue', obj: content }, { expiration: '10' }, 'delay_10']);
8684
(<any>await promise).content.toString().should.eql(JSON.stringify(content));
8785
});
86+
87+
describe('when option createAsQuorum is true', function() {
88+
const createQueueAsQuorum = true;
89+
90+
it('should createDelayQueue as quorum type', async function() {
91+
const queueInstance = sinon.createStubInstance(Queue.default);
92+
const stub = sandbox.stub(Queue, 'default').returns(queueInstance);
93+
94+
await DelayQueue.createDelayQueue(rabbit.consumeChannel, 'delay', createQueueAsQuorum);
95+
96+
stub.args[0].should.eql([
97+
rabbit.consumeChannel,
98+
'delay',
99+
{
100+
deadLetterExchange: '',
101+
deadLetterRoutingKey: 'delay_reply',
102+
arguments: { 'x-queue-type': 'quorum' }
103+
}
104+
]);
105+
});
106+
107+
it('should createDelayQueueReply as quorum with relevant name', async function() {
108+
const queueInstance = sinon.createStubInstance(Queue.default);
109+
const stub = sandbox.stub(Queue, 'default').returns(queueInstance);
110+
111+
await DelayQueue.createDelayQueueReply(rabbit.consumeChannel, 'delay', createQueueAsQuorum);
112+
113+
stub.args.should.eql([[rabbit.consumeChannel, 'delay_quorum_reply', { arguments: { 'x-queue-type': 'quorum' } }]]);
114+
});
115+
116+
it('should publishWithDelay and create not existing queue', async function() {
117+
const stub = sandbox.stub(Queue.default, 'publish').returns(null);
118+
119+
await DelayQueue.publishWithDelay('delay', {}, {}, rabbit.consumeChannel, 'test', createQueueAsQuorum);
120+
121+
stub.calledOnce.should.be.true();
122+
stub.args[0].should.containDeep([{ queueName: 'test', obj: {} }, { expiration: '10000' }, 'delay_10000']);
123+
});
124+
});
88125
});

test/rabbit.test.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ describe('Test rabbit class', function() {
157157
const stub = sandbox.stub(rabbit.consumeChannel, 'assertQueue')
158158
.resolves({ queue: this.name, messageCount: 0, consumerCount: 0 });
159159
const handler = () => {};
160-
await rabbit.createQueue(this.name, { }, handler);
160+
await rabbit.createQueue(this.name, {}, handler);
161161
subscription.calledWith(handler).should.be.true();
162162
stub.calledOnce.should.be.true();
163163
const [name, options] = stub.firstCall.args;
@@ -173,7 +173,7 @@ describe('Test rabbit class', function() {
173173
.resolves({ queue: this.name, messageCount: 0, consumerCount: 0 });
174174
const handler = () => {};
175175
const queueType = 'quorum';
176-
await rabbit.createQueue(this.name, { arguments: { 'x-queue-type': queueType }}, handler);
176+
await rabbit.createQueue(this.name, { arguments: { 'x-queue-type': queueType } }, handler);
177177
subscription.calledWith(handler).should.be.true();
178178
const [name, options] = stub.firstCall.args;
179179
name.should.equal(this.name);
@@ -284,7 +284,7 @@ describe('Test rabbit class', function() {
284284
const headers = { headers: { test: 1 } };
285285
await rabbit.publishWithDelay(`test_${this.name}`, content, headers);
286286
stub.calledOnce.should.be.true();
287-
stub.args.should.eql([['test_delay', content, headers, rabbit.consumeChannel, `test_${this.name}`]]);
287+
stub.args.should.eql([['test_delay', content, headers, rabbit.consumeChannel, `test_${this.name}`, false]]);
288288
});
289289

290290
it('should publish to queue with getReply', async function() {
@@ -393,4 +393,16 @@ describe('Test rabbit class', function() {
393393
(rabbit as any).sigtermHandler();
394394
stub.calledTwice.should.be.true();
395395
});
396+
397+
describe('when defaultQueueType is quorum', function() {
398+
it('should publish to queue with Delay, and use quorum delay queue', async function() {
399+
const stub = sandbox.stub(DelayQueue, 'publishWithDelay');
400+
rabbit = new Rabbit(this.url, { prefix: 'test', scheduledPublish: true, defaultQueueType: 'quorum' });
401+
const content = { content: true };
402+
const headers = { headers: { test: 1 } };
403+
await rabbit.publishWithDelay(`test_${this.name}`, content, headers);
404+
stub.calledOnce.should.be.true();
405+
stub.args.should.eql([['test_quorum_delay', content, headers, rabbit.consumeChannel, `test_${this.name}`, true]]);
406+
});
407+
});
396408
});

ts/delay-queue.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ let delayedQueue: { [key: string]: Queue } = {};
99
let delayedQueueReply: Queue;
1010
let delayedQueueNameReply: string;
1111

12-
export async function createDelayQueueReply(channel: Channel, delayedQueueName: string) {
13-
delayedQueueNameReply = `${delayedQueueName}_reply`;
14-
delayedQueueReply = new Queue(channel, delayedQueueNameReply, {});
12+
export async function createDelayQueueReply(channel: Channel, delayedQueueName: string, createAsQuorum: boolean = false) {
13+
delayedQueueNameReply = createAsQuorum ? `${delayedQueueName}_quorum_reply` : `${delayedQueueName}_reply`;
14+
delayedQueueReply = new Queue(channel, delayedQueueNameReply, { ...createAsQuorum && { arguments: { 'x-queue-type': 'quorum' } } });
1515
await delayedQueueReply.created;
1616
delayedQueueReply.subscribe(onMessage(channel));
1717
}
1818

19-
export async function createDelayQueue(channel: Channel, delayedQueueName: string) {
19+
export async function createDelayQueue(channel: Channel, delayedQueueName: string, createAsQuorum: boolean = false) {
2020
delayedQueue[delayedQueueName] = new Queue(channel, delayedQueueName, {
2121
deadLetterExchange: '',
22-
deadLetterRoutingKey: delayedQueueNameReply
22+
deadLetterRoutingKey: delayedQueueNameReply,
23+
...createAsQuorum && { arguments: { 'x-queue-type': 'quorum' } }
2324
});
2425
await delayedQueue[delayedQueueName].created;
2526
}
@@ -29,13 +30,14 @@ export async function publishWithDelay(
2930
obj,
3031
headers: amqp.Options.Publish = {},
3132
channel: Channel,
32-
queueName: string
33+
queueName: string,
34+
createAsQuorum: boolean = false
3335
) {
3436
const { expiration = '10000' } = headers || {};
3537
name = `${name}_${expiration}`;
3638

3739
if (!delayedQueue[name]) {
38-
await createDelayQueue(channel, name);
40+
await createDelayQueue(channel, name, createAsQuorum);
3941
}
4042
const timestamp = new Date().getTime();
4143
Queue.publish(

ts/rabbit.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ export default class Rabbit extends EventEmitter {
104104
await createReplyQueue(this.consumeChannel);
105105
}
106106
if (!publish && this.scheduledPublish) {
107-
await createDelayQueueReply(this.consumeChannel, this.updateName('delay'));
107+
const createAsQuorum = this.defaultQueueType === 'quorum';
108+
await createDelayQueueReply(this.consumeChannel, this.updateName('delay'), createAsQuorum);
108109
}
109110
}
110111

@@ -121,7 +122,7 @@ export default class Rabbit extends EventEmitter {
121122

122123
async createQueue(
123124
name: string,
124-
options: amqp.Options.AssertQueue & amqp.Options.Consume & { prefix?: string; prefetch? } = {},
125+
options: amqp.Options.AssertQueue & amqp.Options.Consume & { prefix?: string; prefetch?} = {},
125126
handler?: (msg: any, ack: (error?, reply?) => any) => any
126127
) {
127128
if (this.defaultQueueType && !options.arguments?.['x-queue-type']) {
@@ -187,7 +188,9 @@ export default class Rabbit extends EventEmitter {
187188
}
188189
name = this.updateName(name, prefix);
189190
await this.connected;
190-
await publishWithDelay(this.updateName('delay'), obj, properties, this.consumeChannel, name);
191+
const createQueueAsQuorum = this.defaultQueueType === 'quorum';
192+
const queueName = createQueueAsQuorum ? 'quorum_delay' : 'delay';
193+
await publishWithDelay(this.updateName(queueName), obj, properties, this.consumeChannel, name, createQueueAsQuorum);
191194
}
192195

193196
async getReply(name: string, obj, properties: amqp.Options.Publish, prefix?: string, timeout?: number) {

0 commit comments

Comments
 (0)