Skip to content
This repository was archived by the owner on Nov 21, 2020. It is now read-only.

Commit fdb26ab

Browse files
author
batamar
committed
feat(message-queue): used rabbitmq
BREAKING CHANGE: No longer using redis as message broker close #223
1 parent 5a44df4 commit fdb26ab

File tree

6 files changed

+153
-104
lines changed

6 files changed

+153
-104
lines changed

.env.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ REDIS_HOST=localhost
3333
REDIS_PORT=6379
3434
REDIS_PASSWORD=
3535

36+
RABBITMQ_HOST=amqp://localhost
37+
3638
# Email
3739
COMPANY_EMAIL_FROM=noreply@erxes.io
3840
DEFAULT_EMAIL_SERVICE=sendgrid

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"@axelspringer/graphql-google-pubsub": "^1.2.1",
5858
"@google-cloud/pubsub": "0.18.0",
5959
"@google-cloud/storage": "^2.5.0",
60+
"amqplib": "0.5.3",
6061
"apollo-server-express": "^2.3.1",
6162
"aws-sdk": "^2.151.0",
6263
"bcryptjs": "^2.4.3",

src/index.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,18 @@ import { handleEngageUnSubscribe } from './data/resolvers/mutations/engageUtils'
1616
import { checkFile, getEnv, readFileRequest, uploadFile } from './data/utils';
1717
import { connect } from './db/connection';
1818
import { debugExternalApi, debugInit } from './debuggers';
19+
import './messageQueue';
1920
import integrationsApiMiddleware from './middlewares/integrationsApiMiddleware';
2021
import userMiddleware from './middlewares/userMiddleware';
21-
import { initSubscribe, unsubscribe } from './pubsub';
2222
import { initRedis } from './redisClient';
2323
import { init } from './startup';
2424

25+
initRedis();
26+
2527
// load environment variables
2628
dotenv.config();
2729

28-
// connect to redis server
29-
initRedis(() => {
30-
initSubscribe();
31-
});
32-
30+
const { NODE_ENV } = process.env;
3331
const MAIN_APP_DOMAIN = getEnv({ name: 'MAIN_APP_DOMAIN', defaultValue: '' });
3432
const WIDGETS_DOMAIN = getEnv({ name: 'WIDGETS_DOMAIN', defaultValue: '' });
3533

@@ -223,11 +221,9 @@ httpServer.listen(PORT, () => {
223221
process.stdin.resume(); // so the program will not close instantly
224222

225223
// If the Node process ends, close the Mongoose connection
226-
(['SIGINT', 'SIGKILL', 'SIGTERM', 'SIGQUIT'] as NodeJS.Signals[]).forEach(sig => {
227-
try {
224+
if (NODE_ENV === 'production') {
225+
(['SIGINT', 'SIGTERM'] as NodeJS.Signals[]).forEach(sig => {
228226
process.on(sig, () => {
229-
unsubscribe();
230-
231227
// Stops the server from accepting new connections and finishes existing connections.
232228
httpServer.close((error: Error) => {
233229
if (error) {
@@ -241,7 +237,5 @@ process.stdin.resume(); // so the program will not close instantly
241237
});
242238
});
243239
});
244-
} catch (e) {
245-
console.log(e.message);
246-
}
247-
});
240+
});
241+
}

src/messageQueue.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as amqplib from 'amqplib';
2+
import * as dotenv from 'dotenv';
3+
import { ActivityLogs, Conversations } from './db/models';
4+
import { debugBase } from './debuggers';
5+
import { graphqlPubsub } from './pubsub';
6+
import { get, set } from './redisClient';
7+
8+
dotenv.config();
9+
10+
const { NODE_ENV, RABBITMQ_HOST = 'amqp://localhost' } = process.env;
11+
12+
interface IMessage {
13+
action: string;
14+
data: {
15+
trigger: string;
16+
type: string;
17+
payload: any;
18+
};
19+
}
20+
21+
const reciveMessage = async ({ action, data }: IMessage) => {
22+
if (NODE_ENV === 'test') {
23+
return;
24+
}
25+
26+
if (action === 'callPublish') {
27+
if (data.trigger === 'conversationMessageInserted') {
28+
const { customerId, conversationId } = data.payload;
29+
const conversation = await Conversations.findOne({ _id: conversationId }, { integrationId: 1 });
30+
const customerLastStatus = await get(`customer_last_status_${customerId}`);
31+
32+
// if customer's last status is left then mark as joined when customer ask
33+
if (conversation && customerLastStatus === 'left') {
34+
set(`customer_last_status_${customerId}`, 'joined');
35+
36+
// customer has joined + time
37+
const conversationMessages = await Conversations.changeCustomerStatus(
38+
'joined',
39+
customerId,
40+
conversation.integrationId,
41+
);
42+
43+
for (const message of conversationMessages) {
44+
graphqlPubsub.publish('conversationMessageInserted', {
45+
conversationMessageInserted: message,
46+
});
47+
}
48+
49+
// notify as connected
50+
graphqlPubsub.publish('customerConnectionChanged', {
51+
customerConnectionChanged: {
52+
_id: customerId,
53+
status: 'connected',
54+
},
55+
});
56+
}
57+
}
58+
59+
graphqlPubsub.publish(data.trigger, { [data.trigger]: data.payload });
60+
}
61+
62+
if (action === 'activityLog') {
63+
ActivityLogs.createLogFromWidget(data.type, data.payload);
64+
}
65+
};
66+
67+
const initConsumer = async () => {
68+
// Consumer
69+
try {
70+
const conn = await amqplib.connect(RABBITMQ_HOST);
71+
const channel = await conn.createChannel();
72+
73+
await channel.assertQueue('widgetNotification');
74+
75+
channel.consume('widgetNotification', async msg => {
76+
if (msg !== null) {
77+
await reciveMessage(JSON.parse(msg.content.toString()));
78+
channel.ack(msg);
79+
}
80+
});
81+
} catch (e) {
82+
debugBase(e.message);
83+
}
84+
};
85+
86+
initConsumer();

src/pubsub.ts

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,11 @@ import * as fs from 'fs';
33
import { RedisPubSub } from 'graphql-redis-subscriptions';
44
import * as Redis from 'ioredis';
55
import * as path from 'path';
6-
import { ActivityLogs, Conversations } from './db/models';
7-
import { debugBase } from './debuggers';
8-
import { get, redisOptions, set } from './redisClient';
6+
import { redisOptions } from './redisClient';
97

108
// load environment variables
119
dotenv.config();
1210

13-
interface IPubsubMessage {
14-
action: string;
15-
data: {
16-
trigger: string;
17-
type: string;
18-
payload: any;
19-
};
20-
}
21-
2211
interface IGoogleOptions {
2312
projectId: string;
2413
credentials: {
@@ -31,7 +20,7 @@ const { PUBSUB_TYPE, NODE_ENV, PROCESS_NAME } = process.env;
3120

3221
// Google pubsub message handler
3322
const commonMessageHandler = payload => {
34-
return convertPubSubBuffer(payload.data);
23+
return JSON.parse(payload.data.toString());
3524
};
3625

3726
const configGooglePubsub = (): IGoogleOptions => {
@@ -69,9 +58,9 @@ const createPubsubInstance = () => {
6958

7059
const GooglePubSub = require('@axelspringer/graphql-google-pubsub').GooglePubSub;
7160

72-
pubsub = new GooglePubSub(googleOptions, undefined, commonMessageHandler);
61+
return new GooglePubSub(googleOptions, undefined, commonMessageHandler);
7362
} else {
74-
pubsub = new RedisPubSub({
63+
return new RedisPubSub({
7564
connectionListener: error => {
7665
if (error) {
7766
console.error(error);
@@ -81,78 +70,6 @@ const createPubsubInstance = () => {
8170
subscriber: new Redis(redisOptions),
8271
});
8372
}
84-
85-
return pubsub;
86-
};
87-
88-
export const initSubscribe = () => {
89-
setTimeout(async () => {
90-
const isSubscribed = await get('isErxesApiSubscribed');
91-
92-
if (isSubscribed !== 'true') {
93-
debugBase('Subscribing .....');
94-
set('isErxesApiSubscribed', 'true');
95-
96-
graphqlPubsub.subscribe('widgetNotification', message => {
97-
return publishMessage(message);
98-
});
99-
}
100-
}, 1000 * Math.floor(Math.random() * 6) + 1);
101-
};
102-
103-
export const unsubscribe = async () => {
104-
debugBase('Unsubscribing .....');
105-
await set('isErxesApiSubscribed', 'false');
106-
};
107-
108-
const publishMessage = async ({ action, data }: IPubsubMessage) => {
109-
if (NODE_ENV === 'test') {
110-
return;
111-
}
112-
113-
if (action === 'callPublish') {
114-
if (data.trigger === 'conversationMessageInserted') {
115-
const { customerId, conversationId } = data.payload;
116-
const conversation = await Conversations.findOne({ _id: conversationId }, { integrationId: 1 });
117-
const customerLastStatus = await get(`customer_last_status_${customerId}`);
118-
119-
// if customer's last status is left then mark as joined when customer ask
120-
if (conversation && customerLastStatus === 'left') {
121-
set(`customer_last_status_${customerId}`, 'joined');
122-
123-
// customer has joined + time
124-
const conversationMessages = await Conversations.changeCustomerStatus(
125-
'joined',
126-
customerId,
127-
conversation.integrationId,
128-
);
129-
130-
for (const message of conversationMessages) {
131-
graphqlPubsub.publish('conversationMessageInserted', {
132-
conversationMessageInserted: message,
133-
});
134-
}
135-
136-
// notify as connected
137-
graphqlPubsub.publish('customerConnectionChanged', {
138-
customerConnectionChanged: {
139-
_id: customerId,
140-
status: 'connected',
141-
},
142-
});
143-
}
144-
}
145-
146-
graphqlPubsub.publish(data.trigger, { [data.trigger]: data.payload });
147-
}
148-
149-
if (action === 'activityLog') {
150-
ActivityLogs.createLogFromWidget(data.type, data.payload);
151-
}
152-
};
153-
154-
const convertPubSubBuffer = (data: Buffer) => {
155-
return JSON.parse(data.toString());
15673
};
15774

15875
export const graphqlPubsub = createPubsubInstance();

0 commit comments

Comments
 (0)