@cap-js-community/event-queue 1.0.3 → 1.1.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.
- package/package.json +5 -5
- package/src/EventQueueProcessorBase.js +2 -2
- package/src/config.js +51 -3
- package/src/dbHandler.js +1 -1
- package/src/initialize.js +17 -1
- package/src/outbox/EventQueueGenericOutboxHandler.js +37 -0
- package/src/outbox/eventQueueAsOutbox.js +110 -0
- package/src/periodicEvents.js +1 -1
- package/src/processEventQueue.js +2 -2
- package/src/redisPubSub.js +31 -6
- package/src/runner.js +2 -2
- package/src/shared/WorkerQueue.js +1 -1
- package/src/shared/cdsHelper.js +1 -1
- package/src/shared/eventScheduler.js +1 -1
- package/src/shared/redis.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "An event queue that enables secure transactional processing of asynchronous events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -47,17 +47,17 @@
|
|
|
47
47
|
"yaml": "2.3.4"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@sap/cds": "7.5.
|
|
51
|
-
"@sap/cds-dk": "7.5.
|
|
50
|
+
"@sap/cds": "7.5.2",
|
|
51
|
+
"@sap/cds-dk": "7.5.1",
|
|
52
52
|
"eslint": "8.56.0",
|
|
53
53
|
"eslint-config-prettier": "9.1.0",
|
|
54
|
-
"eslint-plugin-jest": "27.6.
|
|
54
|
+
"eslint-plugin-jest": "27.6.2",
|
|
55
55
|
"eslint-plugin-node": "11.1.0",
|
|
56
56
|
"express": "4.18.2",
|
|
57
57
|
"hdb": "0.19.7",
|
|
58
58
|
"jest": "29.7.0",
|
|
59
59
|
"prettier": "2.8.8",
|
|
60
|
-
"sqlite3": "5.1.7
|
|
60
|
+
"sqlite3": "5.1.7"
|
|
61
61
|
},
|
|
62
62
|
"homepage": "https://cap-js-community.github.io/event-queue/",
|
|
63
63
|
"repository": {
|
|
@@ -12,7 +12,7 @@ const eventConfig = require("./config");
|
|
|
12
12
|
const PerformanceTracer = require("./shared/PerformanceTracer");
|
|
13
13
|
|
|
14
14
|
const IMPLEMENT_ERROR_MESSAGE = "needs to be reimplemented";
|
|
15
|
-
const COMPONENT_NAME = "eventQueue/EventQueueProcessorBase";
|
|
15
|
+
const COMPONENT_NAME = "/eventQueue/EventQueueProcessorBase";
|
|
16
16
|
|
|
17
17
|
const DEFAULT_RETRY_ATTEMPTS = 3;
|
|
18
18
|
const DEFAULT_PARALLEL_EVENT_PROCESSING = 1;
|
|
@@ -869,7 +869,7 @@ class EventQueueProcessorBase {
|
|
|
869
869
|
return await checkAndUpdatePromise;
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
-
async
|
|
872
|
+
async acquireDistributedLock() {
|
|
873
873
|
if (this.concurrentEventProcessing) {
|
|
874
874
|
return true;
|
|
875
875
|
}
|
package/src/config.js
CHANGED
|
@@ -10,12 +10,15 @@ const FOR_UPDATE_TIMEOUT = 10;
|
|
|
10
10
|
const GLOBAL_TX_TIMEOUT = 30 * 60 * 1000;
|
|
11
11
|
const REDIS_CONFIG_CHANNEL = "EVENT_QUEUE_CONFIG_CHANNEL";
|
|
12
12
|
const REDIS_CONFIG_BLOCKLIST_CHANNEL = "REDIS_CONFIG_BLOCKLIST_CHANNEL";
|
|
13
|
-
const COMPONENT_NAME = "eventQueue/config";
|
|
13
|
+
const COMPONENT_NAME = "/eventQueue/config";
|
|
14
14
|
const MIN_INTERVAL_SEC = 10;
|
|
15
15
|
const DEFAULT_LOAD = 1;
|
|
16
16
|
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
17
17
|
const COMMAND_BLOCK = "EVENT_QUEUE_EVENT_BLOCK";
|
|
18
18
|
const COMMAND_UNBLOCK = "EVENT_QUEUE_EVENT_UNBLOCK";
|
|
19
|
+
const CAP_EVENT_TYPE = "CAP_OUTBOX";
|
|
20
|
+
|
|
21
|
+
const CAP_PARALLEL_DEFAULT = 5;
|
|
19
22
|
|
|
20
23
|
const BASE_PERIODIC_EVENTS = [
|
|
21
24
|
{
|
|
@@ -51,6 +54,7 @@ class Config {
|
|
|
51
54
|
#blockedPeriodicEvents;
|
|
52
55
|
#isPeriodicEventBlockedCb;
|
|
53
56
|
#thresholdLoggingEventProcessing;
|
|
57
|
+
#useAsCAPOutbox;
|
|
54
58
|
static #instance;
|
|
55
59
|
constructor() {
|
|
56
60
|
this.#logger = cds.log(COMPONENT_NAME);
|
|
@@ -76,6 +80,10 @@ class Config {
|
|
|
76
80
|
return this.#eventMap[this.generateKey(type, subType)];
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
isCapOutboxEvent(type) {
|
|
84
|
+
return type === CAP_EVENT_TYPE;
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
hasEventAfterCommitFlag(type, subType) {
|
|
80
88
|
return this.#eventMap[this.generateKey(type, subType)]?.processAfterCommit ?? true;
|
|
81
89
|
}
|
|
@@ -180,6 +188,30 @@ class Config {
|
|
|
180
188
|
});
|
|
181
189
|
}
|
|
182
190
|
|
|
191
|
+
addCAPOutboxEvent(serviceName, config) {
|
|
192
|
+
if (this.#eventMap[this.generateKey(CAP_EVENT_TYPE, serviceName)]) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const eventConfig = {
|
|
197
|
+
type: CAP_EVENT_TYPE,
|
|
198
|
+
subType: serviceName,
|
|
199
|
+
load: config.load ?? DEFAULT_LOAD,
|
|
200
|
+
impl: "./outbox/EventQueueGenericOutboxHandler",
|
|
201
|
+
selectMaxChunkSize: config.chunkSize,
|
|
202
|
+
parallelEventProcessing: config.parallelEventProcessing ?? (config.parallel && CAP_PARALLEL_DEFAULT),
|
|
203
|
+
retryAttempts: config.maxAttempts,
|
|
204
|
+
transactionMode: config.transactionMode,
|
|
205
|
+
processAfterCommit: config.processAfterCommit,
|
|
206
|
+
eventOutdatedCheck: config.eventOutdatedCheck,
|
|
207
|
+
checkForNextChunk: config.checkForNextChunk,
|
|
208
|
+
deleteFinishedEventsAfterDays: config.deleteFinishedEventsAfterDays,
|
|
209
|
+
internalEvent: true,
|
|
210
|
+
};
|
|
211
|
+
this.#config.events.push(eventConfig);
|
|
212
|
+
this.#eventMap[this.generateKey(CAP_EVENT_TYPE, serviceName)] = eventConfig;
|
|
213
|
+
}
|
|
214
|
+
|
|
183
215
|
#unblockPeriodicEventLocalState(key, tenant) {
|
|
184
216
|
const map = this.#blockedPeriodicEvents[key];
|
|
185
217
|
if (!map) {
|
|
@@ -214,7 +246,7 @@ class Config {
|
|
|
214
246
|
this.#eventMap = config.events.reduce((result, event) => {
|
|
215
247
|
event.load = event.load ?? DEFAULT_LOAD;
|
|
216
248
|
this.validateAdHocEvents(result, event);
|
|
217
|
-
result[
|
|
249
|
+
result[this.generateKey(event.type, event.subType)] = event;
|
|
218
250
|
return result;
|
|
219
251
|
}, {});
|
|
220
252
|
this.#eventMap = config.periodicEvents.reduce((result, event) => {
|
|
@@ -222,7 +254,7 @@ class Config {
|
|
|
222
254
|
event.type = `${event.type}${SUFFIX_PERIODIC}`;
|
|
223
255
|
event.isPeriodic = true;
|
|
224
256
|
this.validatePeriodicConfig(result, event);
|
|
225
|
-
result[
|
|
257
|
+
result[this.generateKey(event.type, event.subType)] = event;
|
|
226
258
|
return result;
|
|
227
259
|
}, this.#eventMap);
|
|
228
260
|
}
|
|
@@ -257,6 +289,14 @@ class Config {
|
|
|
257
289
|
return [type, subType].join("##");
|
|
258
290
|
}
|
|
259
291
|
|
|
292
|
+
removeEvent(type, subType) {
|
|
293
|
+
const index = this.#config.events.findIndex((event) => event.type === "CAP_OUTBOX");
|
|
294
|
+
if (index >= 0) {
|
|
295
|
+
this.#config.events.splice(index, 1);
|
|
296
|
+
}
|
|
297
|
+
delete this.#eventMap[this.generateKey(type, subType)];
|
|
298
|
+
}
|
|
299
|
+
|
|
260
300
|
get fileContent() {
|
|
261
301
|
return this.#config;
|
|
262
302
|
}
|
|
@@ -405,6 +445,14 @@ class Config {
|
|
|
405
445
|
return this.#thresholdLoggingEventProcessing;
|
|
406
446
|
}
|
|
407
447
|
|
|
448
|
+
set useAsCAPOutbox(value) {
|
|
449
|
+
this.#useAsCAPOutbox = value;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
get useAsCAPOutbox() {
|
|
453
|
+
return this.#useAsCAPOutbox;
|
|
454
|
+
}
|
|
455
|
+
|
|
408
456
|
get isMultiTenancy() {
|
|
409
457
|
return !!cds.requires.multitenancy;
|
|
410
458
|
}
|
package/src/dbHandler.js
CHANGED
|
@@ -5,7 +5,7 @@ const cds = require("@sap/cds");
|
|
|
5
5
|
const { broadcastEvent } = require("./redisPubSub");
|
|
6
6
|
const config = require("./config");
|
|
7
7
|
|
|
8
|
-
const COMPONENT_NAME = "eventQueue/dbHandler";
|
|
8
|
+
const COMPONENT_NAME = "/eventQueue/dbHandler";
|
|
9
9
|
|
|
10
10
|
const registerEventQueueDbHandler = (dbService) => {
|
|
11
11
|
const def = dbService.model.definitions[config.tableNameEventQueue];
|
package/src/initialize.js
CHANGED
|
@@ -14,6 +14,7 @@ const dbHandler = require("./dbHandler");
|
|
|
14
14
|
const config = require("./config");
|
|
15
15
|
const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redisPubSub");
|
|
16
16
|
const { closeMainClient } = require("./shared/redis");
|
|
17
|
+
const eventQueueAsOutbox = require("./outbox/eventQueueAsOutbox");
|
|
17
18
|
|
|
18
19
|
const readFileAsync = promisify(fs.readFile);
|
|
19
20
|
|
|
@@ -35,6 +36,7 @@ const CONFIG_VARS = [
|
|
|
35
36
|
["skipCsnCheck", false],
|
|
36
37
|
["updatePeriodicEvents", true],
|
|
37
38
|
["thresholdLoggingEventProcessing", 50],
|
|
39
|
+
["useAsCAPOutbox", false],
|
|
38
40
|
];
|
|
39
41
|
|
|
40
42
|
const initialize = async ({
|
|
@@ -49,6 +51,7 @@ const initialize = async ({
|
|
|
49
51
|
skipCsnCheck,
|
|
50
52
|
updatePeriodicEvents,
|
|
51
53
|
thresholdLoggingEventProcessing,
|
|
54
|
+
useAsCAPOutbox,
|
|
52
55
|
} = {}) => {
|
|
53
56
|
// TODO: initialize check:
|
|
54
57
|
// - content of yaml check
|
|
@@ -70,7 +73,8 @@ const initialize = async ({
|
|
|
70
73
|
disableRedis,
|
|
71
74
|
skipCsnCheck,
|
|
72
75
|
updatePeriodicEvents,
|
|
73
|
-
thresholdLoggingEventProcessing
|
|
76
|
+
thresholdLoggingEventProcessing,
|
|
77
|
+
useAsCAPOutbox
|
|
74
78
|
);
|
|
75
79
|
|
|
76
80
|
const logger = cds.log(COMPONENT);
|
|
@@ -84,6 +88,7 @@ const initialize = async ({
|
|
|
84
88
|
dbHandler.registerEventQueueDbHandler(dbService);
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
monkeyPatchCAPOutbox();
|
|
87
92
|
registerEventProcessors();
|
|
88
93
|
registerCdsShutdown();
|
|
89
94
|
logger.info("event queue initialized", {
|
|
@@ -133,6 +138,17 @@ const registerEventProcessors = () => {
|
|
|
133
138
|
}
|
|
134
139
|
};
|
|
135
140
|
|
|
141
|
+
const monkeyPatchCAPOutbox = () => {
|
|
142
|
+
if (config.useAsCAPOutbox) {
|
|
143
|
+
Object.defineProperty(cds, "outboxed", {
|
|
144
|
+
get: () => eventQueueAsOutbox.outboxed,
|
|
145
|
+
});
|
|
146
|
+
Object.defineProperty(cds, "unboxed", {
|
|
147
|
+
get: () => eventQueueAsOutbox.unboxed,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
136
152
|
const csnCheck = async () => {
|
|
137
153
|
const eventCsn = cds.model.definitions[config.tableNameEventQueue];
|
|
138
154
|
if (!eventCsn) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const cds = require("@sap/cds");
|
|
4
|
+
|
|
5
|
+
const EventQueueBaseClass = require("../EventQueueProcessorBase");
|
|
6
|
+
const { EventProcessingStatus } = require("../constants");
|
|
7
|
+
|
|
8
|
+
const COMPONENT_NAME = "/eventQueue/outbox/generic";
|
|
9
|
+
|
|
10
|
+
class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
11
|
+
constructor(context, eventType, eventSubType, config) {
|
|
12
|
+
super(context, eventType, eventSubType, config);
|
|
13
|
+
this.logger = cds.log(`${COMPONENT_NAME}/${eventSubType}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async processEvent(processContext, key, queueEntries, payload) {
|
|
17
|
+
let status = EventProcessingStatus.Done;
|
|
18
|
+
try {
|
|
19
|
+
const service = await cds.connect.to(this.eventSubType);
|
|
20
|
+
const userId = payload.contextUser;
|
|
21
|
+
const msg = payload._fromSend ? new cds.Request(payload) : new cds.Event(payload);
|
|
22
|
+
const invocationFn = payload._fromSend ? "send" : "emit";
|
|
23
|
+
delete msg._fromSend;
|
|
24
|
+
delete msg.contextUser;
|
|
25
|
+
processContext.user = new cds.User.Privileged(userId);
|
|
26
|
+
await cds.unboxed(service).tx(processContext)[invocationFn](msg);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
status = EventProcessingStatus.Error;
|
|
29
|
+
this.logger.error("error processing outboxed service call", err, {
|
|
30
|
+
serviceName: this.eventSubType,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return queueEntries.map((queueEntry) => [queueEntry.ID, status]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = EventQueueGenericOutboxHandler;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const cds = require("@sap/cds");
|
|
4
|
+
|
|
5
|
+
const { publishEvent } = require("../publishEvent");
|
|
6
|
+
const config = require("../config");
|
|
7
|
+
|
|
8
|
+
const OUTBOXED = Symbol("outboxed");
|
|
9
|
+
const UNBOXED = Symbol("unboxed");
|
|
10
|
+
|
|
11
|
+
const CDS_EVENT_TYPE = "CAP_OUTBOX";
|
|
12
|
+
|
|
13
|
+
const COMPONENT_NAME = "/eventQueue/eventQueueAsOutbox";
|
|
14
|
+
|
|
15
|
+
function outboxed(srv, customOpts) {
|
|
16
|
+
// outbox max. once
|
|
17
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
18
|
+
if (!new.target) {
|
|
19
|
+
const former = srv[OUTBOXED];
|
|
20
|
+
if (former) {
|
|
21
|
+
return former;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const originalSrv = srv[UNBOXED] || srv;
|
|
26
|
+
const outboxedSrv = Object.create(originalSrv);
|
|
27
|
+
outboxedSrv[UNBOXED] = originalSrv;
|
|
28
|
+
|
|
29
|
+
if (!new.target) {
|
|
30
|
+
Object.defineProperty(srv, OUTBOXED, { value: outboxedSrv });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const outboxOpts = Object.assign(
|
|
34
|
+
{},
|
|
35
|
+
(typeof cds.requires.outbox === "object" && cds.requires.outbox) || {},
|
|
36
|
+
(typeof srv.options?.outbox === "object" && srv.options.outbox) || {},
|
|
37
|
+
customOpts || {}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
config.addCAPOutboxEvent(srv.name, outboxOpts);
|
|
41
|
+
outboxedSrv.handle = async function (req) {
|
|
42
|
+
const context = req.context || cds.context;
|
|
43
|
+
if (outboxOpts.kind === "persistent-outbox") {
|
|
44
|
+
config.addCAPOutboxEvent(srv.name, outboxOpts);
|
|
45
|
+
await _mapToEventAndPublish(context, srv.name, req);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
context.on("succeeded", async () => {
|
|
49
|
+
try {
|
|
50
|
+
if (req.reply) {
|
|
51
|
+
await originalSrv.send(req);
|
|
52
|
+
} else {
|
|
53
|
+
await originalSrv.emit(req);
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
logger.error("In memory processing failed", { event: req.event, cause: err });
|
|
57
|
+
if (isUnrecoverable(originalSrv, err) && outboxOpts.crashOnError !== false) {
|
|
58
|
+
cds.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return outboxedSrv;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function unboxed(srv) {
|
|
68
|
+
return srv[UNBOXED] || srv;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const _mapToEventAndPublish = async (context, name, msg) => {
|
|
72
|
+
const event = {
|
|
73
|
+
contextUser: context.user.id,
|
|
74
|
+
...(msg._fromSend || (msg.reply && { _fromSend: true })), // send or emit
|
|
75
|
+
...(msg.inbound && { inbound: msg.inbound }),
|
|
76
|
+
...(msg.event && { event: msg.event }),
|
|
77
|
+
...(msg.data && { data: msg.data }),
|
|
78
|
+
...(msg.headers && { headers: msg.headers }),
|
|
79
|
+
...(msg.query && { query: msg.query }),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await publishEvent(cds.tx(context), {
|
|
83
|
+
type: CDS_EVENT_TYPE,
|
|
84
|
+
subType: name,
|
|
85
|
+
payload: JSON.stringify(event),
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const isUnrecoverable = (service, error) => {
|
|
90
|
+
let unrecoverable = service.isUnrecoverableError && service.isUnrecoverableError(error);
|
|
91
|
+
if (unrecoverable === undefined) {
|
|
92
|
+
unrecoverable = error.unrecoverable;
|
|
93
|
+
}
|
|
94
|
+
return unrecoverable || isStandardError(error);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const isStandardError = (err) => {
|
|
98
|
+
return (
|
|
99
|
+
err instanceof TypeError ||
|
|
100
|
+
err instanceof ReferenceError ||
|
|
101
|
+
err instanceof SyntaxError ||
|
|
102
|
+
err instanceof RangeError ||
|
|
103
|
+
err instanceof URIError
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
outboxed,
|
|
109
|
+
unboxed,
|
|
110
|
+
};
|
package/src/periodicEvents.js
CHANGED
|
@@ -6,7 +6,7 @@ const { EventProcessingStatus } = require("./constants");
|
|
|
6
6
|
const { processChunkedSync } = require("./shared/common");
|
|
7
7
|
const eventConfig = require("./config");
|
|
8
8
|
|
|
9
|
-
const COMPONENT_NAME = "eventQueue/periodicEvents";
|
|
9
|
+
const COMPONENT_NAME = "/eventQueue/periodicEvents";
|
|
10
10
|
const CHUNK_SIZE_INSERT_PERIODIC_EVENTS = 4;
|
|
11
11
|
|
|
12
12
|
const checkAndInsertPeriodicEvents = async (context) => {
|
package/src/processEventQueue.js
CHANGED
|
@@ -10,7 +10,7 @@ const { limiter } = require("./shared/common");
|
|
|
10
10
|
|
|
11
11
|
const { executeInNewTransaction, TriggerRollback } = require("./shared/cdsHelper");
|
|
12
12
|
|
|
13
|
-
const COMPONENT_NAME = "eventQueue/processEventQueue";
|
|
13
|
+
const COMPONENT_NAME = "/eventQueue/processEventQueue";
|
|
14
14
|
const MAX_EXECUTION_TIME = 5 * 60 * 1000;
|
|
15
15
|
|
|
16
16
|
const processEventQueue = async (context, eventType, eventSubType, startTime = new Date()) => {
|
|
@@ -29,7 +29,7 @@ const processEventQueue = async (context, eventType, eventSubType, startTime = n
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
baseInstance = new EventTypeClass(context, eventType, eventSubType, eventConfig);
|
|
32
|
-
const continueProcessing = await baseInstance.
|
|
32
|
+
const continueProcessing = await baseInstance.acquireDistributedLock();
|
|
33
33
|
if (!continueProcessing) {
|
|
34
34
|
return;
|
|
35
35
|
}
|
package/src/redisPubSub.js
CHANGED
|
@@ -5,11 +5,11 @@ const cds = require("@sap/cds");
|
|
|
5
5
|
const redis = require("./shared/redis");
|
|
6
6
|
const { checkLockExistsAndReturnValue } = require("./shared/distributedLock");
|
|
7
7
|
const config = require("./config");
|
|
8
|
-
const
|
|
8
|
+
const runner = require("./runner");
|
|
9
9
|
const { getSubdomainForTenantId } = require("./shared/cdsHelper");
|
|
10
10
|
|
|
11
11
|
const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
|
|
12
|
-
const COMPONENT_NAME = "eventQueue/redisPubSub";
|
|
12
|
+
const COMPONENT_NAME = "/eventQueue/redisPubSub";
|
|
13
13
|
|
|
14
14
|
let subscriberClientPromise;
|
|
15
15
|
|
|
@@ -17,10 +17,10 @@ const initEventQueueRedisSubscribe = () => {
|
|
|
17
17
|
if (subscriberClientPromise || !config.redisEnabled) {
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
redis.subscribeRedisChannel(EVENT_MESSAGE_CHANNEL,
|
|
20
|
+
redis.subscribeRedisChannel(EVENT_MESSAGE_CHANNEL, _messageHandlerProcessEvents);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const _messageHandlerProcessEvents = async (messageData) => {
|
|
24
24
|
const logger = cds.log(COMPONENT_NAME);
|
|
25
25
|
try {
|
|
26
26
|
const { tenantId, type, subType } = JSON.parse(messageData);
|
|
@@ -43,8 +43,30 @@ const messageHandlerProcessEvents = async (messageData) => {
|
|
|
43
43
|
// NOTE: we need this because of logging otherwise logs would not contain the subdomain
|
|
44
44
|
http: { req: { authInfo: { getSubdomain: () => subdomain } } },
|
|
45
45
|
};
|
|
46
|
+
|
|
47
|
+
if (!config.getEventConfig(type, subType)) {
|
|
48
|
+
if (config.isCapOutboxEvent(type)) {
|
|
49
|
+
try {
|
|
50
|
+
const service = await cds.connect.to(subType);
|
|
51
|
+
cds.outboxed(service);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logger.error("could not connect to outboxed service", err, {
|
|
54
|
+
type,
|
|
55
|
+
subType,
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
logger.error("cannot find configuration for published event. Event won't be processed", {
|
|
61
|
+
type,
|
|
62
|
+
subType,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
return await cds.tx(tenantContext, async ({ context }) => {
|
|
47
|
-
return await runEventCombinationForTenant(context, type, subType);
|
|
69
|
+
return await runner.runEventCombinationForTenant(context, type, subType);
|
|
48
70
|
});
|
|
49
71
|
} catch (err) {
|
|
50
72
|
logger.error("could not parse event information", {
|
|
@@ -76,7 +98,7 @@ const broadcastEvent = async (tenantId, type, subType) => {
|
|
|
76
98
|
}
|
|
77
99
|
|
|
78
100
|
return await cds.tx(context, async ({ context }) => {
|
|
79
|
-
return await runEventCombinationForTenant(context, type, subType);
|
|
101
|
+
return await runner.runEventCombinationForTenant(context, type, subType);
|
|
80
102
|
});
|
|
81
103
|
}
|
|
82
104
|
return;
|
|
@@ -122,4 +144,7 @@ module.exports = {
|
|
|
122
144
|
initEventQueueRedisSubscribe,
|
|
123
145
|
broadcastEvent,
|
|
124
146
|
closeSubscribeClient,
|
|
147
|
+
__: {
|
|
148
|
+
_messageHandlerProcessEvents,
|
|
149
|
+
},
|
|
125
150
|
};
|
package/src/runner.js
CHANGED
|
@@ -12,7 +12,7 @@ const { getSubdomainForTenantId } = require("./shared/cdsHelper");
|
|
|
12
12
|
const periodicEvents = require("./periodicEvents");
|
|
13
13
|
const { hashStringTo32Bit } = require("./shared/common");
|
|
14
14
|
|
|
15
|
-
const COMPONENT_NAME = "eventQueue/runner";
|
|
15
|
+
const COMPONENT_NAME = "/eventQueue/runner";
|
|
16
16
|
const EVENT_QUEUE_RUN_ID = "EVENT_QUEUE_RUN_ID";
|
|
17
17
|
const EVENT_QUEUE_RUN_TS = "EVENT_QUEUE_RUN_TS";
|
|
18
18
|
const EVENT_QUEUE_RUN_PERIODIC_EVENT = "EVENT_QUEUE_RUN_PERIODIC_EVENT";
|
|
@@ -321,7 +321,7 @@ module.exports = {
|
|
|
321
321
|
multiTenancyDb,
|
|
322
322
|
multiTenancyRedis,
|
|
323
323
|
runEventCombinationForTenant,
|
|
324
|
-
|
|
324
|
+
__: {
|
|
325
325
|
_singleTenantDb,
|
|
326
326
|
_multiTenancyRedis,
|
|
327
327
|
_multiTenancyDb,
|
|
@@ -5,7 +5,7 @@ const cds = require("@sap/cds");
|
|
|
5
5
|
const config = require("../config");
|
|
6
6
|
const EventQueueError = require("../EventQueueError");
|
|
7
7
|
|
|
8
|
-
const COMPONENT_NAME = "eventQueue/WorkerQueue";
|
|
8
|
+
const COMPONENT_NAME = "/eventQueue/WorkerQueue";
|
|
9
9
|
const NANO_TO_MS = 1e6;
|
|
10
10
|
const THRESHOLD = {
|
|
11
11
|
INFO: 35 * 1000,
|
package/src/shared/cdsHelper.js
CHANGED
|
@@ -8,7 +8,7 @@ const config = require("../config");
|
|
|
8
8
|
const subdomainCache = {};
|
|
9
9
|
|
|
10
10
|
const VERROR_CLUSTER_NAME = "ExecuteInNewTransactionError";
|
|
11
|
-
const COMPONENT_NAME = "eventQueue/cdsHelper";
|
|
11
|
+
const COMPONENT_NAME = "/eventQueue/cdsHelper";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Execute logic in a new managed CDS transaction context, auto-handling commit, rollback and error/exception situations.
|
|
@@ -5,7 +5,7 @@ const cds = require("@sap/cds");
|
|
|
5
5
|
const { broadcastEvent } = require("../redisPubSub");
|
|
6
6
|
const config = require("./../config");
|
|
7
7
|
|
|
8
|
-
const COMPONENT_NAME = "eventQueue/shared/eventScheduler";
|
|
8
|
+
const COMPONENT_NAME = "/eventQueue/shared/eventScheduler";
|
|
9
9
|
|
|
10
10
|
let instance;
|
|
11
11
|
class EventScheduler {
|
package/src/shared/redis.js
CHANGED
|
@@ -5,7 +5,7 @@ const redis = require("redis");
|
|
|
5
5
|
const { getEnvInstance } = require("./env");
|
|
6
6
|
const EventQueueError = require("../EventQueueError");
|
|
7
7
|
|
|
8
|
-
const COMPONENT_NAME = "eventQueue/shared/redis";
|
|
8
|
+
const COMPONENT_NAME = "/eventQueue/shared/redis";
|
|
9
9
|
|
|
10
10
|
let mainClientPromise;
|
|
11
11
|
const subscriberChannelClientPromise = {};
|