@ti-engine/core 1.0.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.
@@ -0,0 +1,418 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * SPDX-License-Identifier: ICU
4
+ */
5
+
6
+ const MessageObserver = require( "#message-observer" );
7
+ const _ = require( "lodash" );
8
+ const exceptions = require( "#exceptions" );
9
+ const messageTracer = require( "#message-tracer" );
10
+
11
+ /**
12
+ * @typedef {Object} MessageDestination
13
+ * @property {string|undefined} [instanceID] The instance ID of the message exchange by which the message was received (available after acceptance).
14
+ * @property {string} route The route to destination for the message. Exact structure will depend on the implementation of the message exchange.
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} MessageSource
19
+ * @property {string} instanceID The instance ID of the message exchange from which the service call originated.
20
+ * @property {string} route The route from source of the message. Exact structure will depend on the implementation of the message exchange.
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} Message
25
+ * @property {string} chainID Unique identifier of the message chain if the message is part of one.
26
+ * @property {number} chainLevel The node level of this message in the message chain tree.
27
+ * @property {MessageDestination} destination The destination of the message.
28
+ * @property {string} messageID Unique message identifier.
29
+ * @property {Object|string|undefined} payload The message contents to be processed in destination. If string, it is ID of the payload in the memory cache instead.
30
+ * Note that if this is not an Object or a string, there is no guarantee that it will be delivered in the same/proper format!
31
+ * @property {MessageSource} source The source of the message.
32
+ */
33
+
34
+ /**
35
+ * An abstract class that defines a message exchange behavior.
36
+ * <br/>
37
+ * NOTE: While this sets the basis frame for the message based communication between microservices, it has to be inherited and
38
+ * extended with additional logic that is NOT implemented here. For a working example please see {@link DefaultMessageExchange} class.
39
+ * <br/>
40
+ * NOTE: This class and its children are designed to be used internally by the {@link MessageDispatcher} and its related classes.
41
+ *
42
+ * @class MessageExchange
43
+ * @extends MessageObserver
44
+ * @abstract
45
+ * @public
46
+ */
47
+ class MessageExchange extends MessageObserver {
48
+
49
+ static #connectionNameRequestsOut = "connection-msg-requests-out";
50
+ static #connectionNameRequestsIn = "connection-msg-requests-in";
51
+ static #connectionNameResponsesOut = "connection-msg-responses-out";
52
+ static #connectionNameResponsesIn = "connection-msg-responses-in";
53
+ #instanceID;
54
+ #serviceDomainName;
55
+ #disruptedConnections;
56
+ #configuredOutbound = false;
57
+ #configuredInbound = false;
58
+ /** @type MessageSender */
59
+ #messageRequestsOut;
60
+ /** @type MessageSender */
61
+ #messageResponsesOut;
62
+ /** @type MessageReceiver */
63
+ #messageRequestsIn;
64
+ /** @type MessageReceiver */
65
+ #messageResponsesIn;
66
+
67
+ /**
68
+ * @constructor
69
+ * @param {string} instanceID The unique identifier of the microservice instance using the message exchange.
70
+ * @param {string} serviceDomainName The domain name of the microservice using the message exchange.
71
+ */
72
+ constructor( instanceID, serviceDomainName ) {
73
+ super();
74
+
75
+ // make sure this abstract class cannot be instantiated:
76
+ if ( new.target === MessageExchange ) {
77
+ throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
78
+ }
79
+
80
+ this.#instanceID = instanceID;
81
+ this.#serviceDomainName = serviceDomainName;
82
+ this.#disruptedConnections = [];
83
+ }
84
+
85
+ /* Public interface */
86
+
87
+ /**
88
+ * Property returning the configured connection name for the outgoing message requests.
89
+ *
90
+ * @property
91
+ * @returns {string}
92
+ * @public
93
+ */
94
+ static get connectionNameRequestsOut() { return this.#connectionNameRequestsOut; }
95
+
96
+ /**
97
+ * Used to set the connection name for the outgoing message requests.
98
+ *
99
+ * @property
100
+ * @param {string} value
101
+ * @public
102
+ */
103
+ static set connectionNameRequestsOut( value ) { this.#connectionNameRequestsOut = value; }
104
+
105
+ /**
106
+ * Property returning the configured connection name for the incoming message requests.
107
+ *
108
+ * @property
109
+ * @returns {string}
110
+ * @public
111
+ */
112
+ static get connectionNameRequestsIn() { return this.#connectionNameRequestsIn; }
113
+
114
+ /**
115
+ * Used to set the connection name for the incoming message requests.
116
+ *
117
+ * @property
118
+ * @param {string} value
119
+ * @public
120
+ */
121
+ static set connectionNameRequestsIn( value ) { this.#connectionNameRequestsIn = value; }
122
+
123
+ /**
124
+ * Property returning the configured connection name for the outgoing message responses.
125
+ *
126
+ * @property
127
+ * @returns {string}
128
+ * @public
129
+ */
130
+ static get connectionNameResponsesOut() { return this.#connectionNameResponsesOut; }
131
+
132
+ /**
133
+ * Used to set the connection name for the outgoing message responses.
134
+ *
135
+ * @property
136
+ * @param {string} value
137
+ * @public
138
+ */
139
+ static set connectionNameResponsesOut( value ) { this.#connectionNameResponsesOut = value; }
140
+
141
+ /**
142
+ * Property returning the configured connection name for the incoming message responses.
143
+ *
144
+ * @property
145
+ * @returns {string}
146
+ * @public
147
+ */
148
+ static get connectionNameResponsesIn() { return this.#connectionNameResponsesIn; }
149
+
150
+ /**
151
+ * Used to set the connection name for the incoming message responses.
152
+ *
153
+ * @property
154
+ * @param {string} value
155
+ * @public
156
+ */
157
+ static set connectionNameResponsesIn( value ) { this.#connectionNameResponsesIn = value; }
158
+
159
+ /**
160
+ * Property returning the identifier of the pending messages queue.
161
+ *
162
+ * @property
163
+ * @returns {string}
164
+ * @public
165
+ */
166
+ static get pendingQueue() { return "pending:"; }
167
+
168
+ /**
169
+ * Property returning the identifier of the processed messages queue.
170
+ *
171
+ * @property
172
+ * @returns {string}
173
+ * @public
174
+ */
175
+ static get processedQueue() { return "processed:"; }
176
+
177
+ /**
178
+ * Property returning the configured service instance ID.
179
+ *
180
+ * @property
181
+ * @returns {string}
182
+ * @public
183
+ */
184
+ get instanceID() { return this.#instanceID; }
185
+
186
+ /**
187
+ * Property returning the configured service domain name.
188
+ *
189
+ * @property
190
+ * @returns {string}
191
+ * @public
192
+ */
193
+ get serviceDomainName() { return this.#serviceDomainName; }
194
+
195
+ /**
196
+ * Returns the currently configured {@link MessageSender} for outbound message requests.
197
+ *
198
+ * @property
199
+ * @returns {MessageSender}
200
+ * @public
201
+ */
202
+ get messageRequestsOut() { return this.#messageRequestsOut; }
203
+
204
+ /**
205
+ * Returns the currently configured {@link MessageSender} for outbound message responses.
206
+ *
207
+ * @property
208
+ * @returns {MessageSender}
209
+ * @public
210
+ */
211
+ get messageResponsesOut() { return this.#messageResponsesOut; }
212
+
213
+ /**
214
+ * Returns the currently configured {@link MessageReceiver} for inbound message requests.
215
+ *
216
+ * @property
217
+ * @returns {MessageReceiver}
218
+ * @public
219
+ */
220
+ get messageRequestsIn() { return this.#messageRequestsIn; }
221
+
222
+ /**
223
+ * Returns the currently configured {@link MessageReceiver} for inbound message responses.
224
+ *
225
+ * @property
226
+ * @returns {MessageReceiver}
227
+ * @public
228
+ */
229
+ get messageResponsesIn() { return this.#messageResponsesIn; }
230
+
231
+ /**
232
+ * Returns a flag indicating if the message exchange is configured for outbound communication.
233
+ *
234
+ * @property
235
+ * @returns {boolean}
236
+ * @public
237
+ */
238
+ get configuredOutbound() { return this.#configuredOutbound; }
239
+
240
+ /**
241
+ * Returns a flag indicating if the message exchange is configured for inbound communication.
242
+ *
243
+ * @property
244
+ * @returns {boolean}
245
+ * @public
246
+ */
247
+ get configuredInbound() { return this.#configuredInbound; }
248
+
249
+ /**
250
+ * Should be used to enable all communication channels for messaging.
251
+ * <br/>
252
+ * NOTE: Override this to implement messaging initialization.
253
+ *
254
+ * @method
255
+ * @param {boolean} configureInbound If set to 'true' it tells the message exchange to setup inbound messaging.
256
+ * @param {boolean} configureOutbound If set to 'true' it tells the message exchange to setup outbound messaging.
257
+ * @returns {Promise}
258
+ * @abstract
259
+ * @public
260
+ */
261
+ enableMessaging( configureInbound, configureOutbound ) {
262
+ return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.enableMessaging.name } ) );
263
+ }
264
+
265
+ /**
266
+ * Should be used to gracefully disable all communication channels for messaging.
267
+ * <br/>
268
+ * NOTE: Override this to implement graceful messaging shut down.
269
+ *
270
+ * @method
271
+ * @returns {Promise}
272
+ * @abstract
273
+ * @public
274
+ */
275
+ disableMessaging() {
276
+ return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.disableMessaging.name } ) );
277
+ }
278
+
279
+ /**
280
+ * Used to configure the message exchange for receiving inbound messages and returning responses to them.
281
+ * Should typically be called from an implemented {@link enableMessaging} method.
282
+ *
283
+ * @method
284
+ * @param {MessageReceiver} messageReceiverRequestsIn A message receiver that will handle the inbound messages.
285
+ * @param {MessageSender} messageSenderResponsesOut A message sender that will handle the sending of responses for the inbound messages.
286
+ * @public
287
+ */
288
+ configureInboundMessaging( messageReceiverRequestsIn, messageSenderResponsesOut ) {
289
+ this.#messageRequestsIn = messageReceiverRequestsIn;
290
+ this.#messageResponsesOut = messageSenderResponsesOut;
291
+ this.#configuredInbound = true;
292
+ }
293
+
294
+ /**
295
+ * Used to configure message exchange for sending messages and receiving responses to them.
296
+ * Should typically be called from an implemented {@link enableMessaging} method.
297
+ *
298
+ * @method
299
+ * @param {MessageSender} messageSenderRequestsOut A message sender that will handle the outbound messages.
300
+ * @param {MessageReceiver} messageReceiverResponsesIn A message receiver that will handle the returned responses for the outbound messages.
301
+ * @public
302
+ */
303
+ configureOutboundMessaging( messageSenderRequestsOut, messageReceiverResponsesIn ) {
304
+ this.#messageRequestsOut = messageSenderRequestsOut;
305
+ this.#messageResponsesIn = messageReceiverResponsesIn;
306
+ this.#configuredOutbound = true;
307
+ }
308
+
309
+ /**
310
+ * Used to add an additional {@link MessageObserver} to the connection for the incoming message requests.
311
+ *
312
+ * @method
313
+ * @param {MessageObserver} messageObserver
314
+ * @public
315
+ */
316
+ addMessageObserverRequestsIn( messageObserver ) {
317
+ this.#messageRequestsIn.addMessageObserver( messageObserver );
318
+ }
319
+
320
+ /**
321
+ * Used to add an additional {@link MessageObserver} to the connection for the incoming message responses.
322
+ *
323
+ * @method
324
+ * @param {MessageObserver} messageObserver
325
+ * @public
326
+ */
327
+ addMessageObserverResponsesIn( messageObserver ) {
328
+ this.#messageResponsesIn.addMessageObserver( messageObserver );
329
+ }
330
+
331
+ /**
332
+ * Used to mark the connection with the provided identifier as disrupted.
333
+ *
334
+ * @method
335
+ * @param {string} identifier The identifier of the observed connection.
336
+ * @override
337
+ * @public
338
+ */
339
+ onConnectionDisrupted( identifier ) {
340
+ this.#disruptedConnections.push( identifier );
341
+ }
342
+
343
+ /**
344
+ * Used to mark the connection with the provided identifier as recovered.
345
+ * <br/>
346
+ * NOTE: This will also result in enabling the message exchange if no connections are currently disrupted.
347
+ *
348
+ * @method
349
+ * @param {string} identifier The identifier of the observed connection.
350
+ * @override
351
+ * @public
352
+ */
353
+ onConnectionRecovered( identifier ) {
354
+ _.pull( this.#disruptedConnections, identifier );
355
+ }
356
+
357
+ /**
358
+ * Used only for the purposes of the message tracer.
359
+ *
360
+ * @method
361
+ * @param {string} identifier The identifier of the observed connection.
362
+ * @param {Message} message The message for processing.
363
+ * @override
364
+ * @public
365
+ */
366
+ onMessage( identifier, message ) {
367
+ message.destination.instanceID = this.#instanceID;
368
+
369
+ if ( MessageExchange.connectionNameRequestsIn === identifier ) {
370
+ messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.RECEIVED, messageTracer.messageState.PENDING );
371
+ } else if ( MessageExchange.connectionNameResponsesIn === identifier ) {
372
+ messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.RECEIVED, messageTracer.messageState.PROCESSED );
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Used to check whether the connection with the provided identifier is currently in recovery mode.
378
+ *
379
+ * @method
380
+ * @param {string} identifier The identifier of the connection.
381
+ * @returns {boolean} Will return 'true' if the connection is currently disrupted and not yet recovered.
382
+ * @public
383
+ */
384
+ isConnectionInRecovery( identifier ) {
385
+ return this.#disruptedConnections.indexOf( identifier ) !== -1;
386
+ }
387
+
388
+ /**
389
+ * Used to send a message request. Override of this method assumes that the message itself contains enough
390
+ * information to determine the send destination.
391
+ *
392
+ * @method
393
+ * @param {Message} message The message request to send.
394
+ * @returns {Promise}
395
+ * @abstract
396
+ * @public
397
+ */
398
+ sendMessageRequest( message ) {
399
+ return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.sendMessageRequest.name } ) );
400
+ }
401
+
402
+ /**
403
+ * Used to send a message response. Override of this method assumes that the message itself contains enough
404
+ * information to determine the send destination.
405
+ *
406
+ * @method
407
+ * @param {Message} message The message response to send.
408
+ * @returns {Promise}
409
+ * @abstract
410
+ * @public
411
+ */
412
+ sendMessageResponse( message ) {
413
+ return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.sendMessageResponse.name } ) );
414
+ }
415
+
416
+ }
417
+
418
+ module.exports = MessageExchange;
@@ -0,0 +1,173 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * SPDX-License-Identifier: ICU
4
+ */
5
+
6
+ const ConnectionObserver = require( "#connection-observer" );
7
+ const _ = require( "lodash" );
8
+ const exceptions = require( "#exceptions" );
9
+ const logger = require( "#logger" );
10
+
11
+ /**
12
+ * An abstract class that defines a basic message handler behavior.
13
+ * <br/>
14
+ * NOTE: This class and its children are designed to be used internally by classes extending the {@link MessageObserver} class.
15
+ *
16
+ * @class MessageHandler
17
+ * @extends ConnectionObserver
18
+ * @abstract
19
+ * @public
20
+ */
21
+ class MessageHandler extends ConnectionObserver {
22
+
23
+ #isAvailable = false;
24
+ #connectionIdentifier;
25
+ #messageObservers = [];
26
+
27
+ /**
28
+ * @constructor
29
+ * @param {string} identifier An identifier for this message handler. Should be unique in the context of the message exchange.
30
+ */
31
+ constructor( identifier ) {
32
+ super();
33
+
34
+ // make sure this abstract class cannot be instantiated:
35
+ if ( new.target === MessageHandler ) {
36
+ throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
37
+ }
38
+
39
+ this.#connectionIdentifier = identifier;
40
+ }
41
+
42
+ /* Public interface */
43
+
44
+ /**
45
+ * Indicates whether the message handler is currently available.
46
+ *
47
+ * @property
48
+ * @returns {boolean}
49
+ * @public
50
+ */
51
+ get isAvailable() { return this.#isAvailable; }
52
+
53
+ /**
54
+ * Used to set the isAvailable flag.
55
+ * <br/>
56
+ * NOTE: For use by implementing classes only!
57
+ *
58
+ * @property
59
+ * @param {boolean} value
60
+ * @public
61
+ */
62
+ set isAvailable( value ) { this.#isAvailable = value; }
63
+
64
+ /**
65
+ * Returns the connection identifier.
66
+ *
67
+ * @property
68
+ * @returns {string}
69
+ * @public
70
+ */
71
+ get connectionIdentifier() { return this.#connectionIdentifier; }
72
+
73
+ /**
74
+ * Used to initialize and enable the communication capabilities of the handler.
75
+ * <br/>
76
+ * NOTE: Override this to add functionality.
77
+ *
78
+ * @method
79
+ * @returns {Promise}
80
+ * @abstract
81
+ * @public
82
+ */
83
+ enable() {
84
+ return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.enable.name } ) );
85
+ }
86
+
87
+ /**
88
+ * Used to shutdown and disable the communication behavior of the handler.
89
+ * <br/>
90
+ * NOTE: Override this to add functionality.
91
+ *
92
+ * @method
93
+ * @returns {Promise}
94
+ * @abstract
95
+ * @public
96
+ */
97
+ disable() {
98
+ return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.disable.name } ) );
99
+ }
100
+
101
+ /**
102
+ * Used to register a new {@link MessageObserver} for events related to the messages passing through this handler.
103
+ *
104
+ * @method
105
+ * @param {MessageObserver} messageObserver The {@link MessageObserver} that will be notified of any changes.
106
+ * @public
107
+ */
108
+ addMessageObserver( messageObserver ) {
109
+ const MessageObserver = require( "#message-observer" );
110
+
111
+ if ( messageObserver instanceof MessageObserver ) {
112
+ this.#messageObservers.push( messageObserver );
113
+ } else {
114
+ logger.log( `Attempting to add '${ messageObserver.constructor.name }' as message observer but it's not a child-class of 'MessageObserver'!`, logger.logSeverity.WARNING );
115
+ }
116
+ }
117
+
118
+ /**
119
+ * An event-triggered method that will notify any observers about a new message for handling.
120
+ *
121
+ * @method
122
+ * @param {Message} message
123
+ * @public
124
+ */
125
+ onMessage( message ) {
126
+ _.forEach( this.#messageObservers, ( messageObserver ) => {
127
+ messageObserver.onMessage( this.#connectionIdentifier, message );
128
+ } );
129
+ }
130
+
131
+ /**
132
+ * An event-triggered method that will notify any observers about primary connection recovered state.
133
+ * <br/>
134
+ * NOTE: You can override this to add custom functionality but make sure to also call the base method
135
+ * using: super.onConnectionRecovered( identifier )
136
+ *
137
+ * @method
138
+ * @param {string} identifier The identifier of the observed connection.
139
+ * @override
140
+ * @private
141
+ */
142
+ onConnectionRecovered( identifier ) {
143
+ if ( this.#isAvailable === false ) {
144
+ this.#isAvailable = true;
145
+ _.forEach( this.#messageObservers, ( messageObserver ) => {
146
+ messageObserver.onConnectionRecovered( this.#connectionIdentifier );
147
+ } );
148
+ }
149
+ }
150
+
151
+ /**
152
+ * An event-triggered method that will notify any observers about primary connection disrupted state.
153
+ * <br/>
154
+ * NOTE: You can override this to add custom functionality but make sure to also call the base method
155
+ * using: super.onConnectionDisrupted( identifier )
156
+ *
157
+ * @method
158
+ * @param {string} identifier The identifier of the observed connection.
159
+ * @override
160
+ * @private
161
+ */
162
+ onConnectionDisrupted( identifier ) {
163
+ if ( this.#isAvailable === true ) {
164
+ this.#isAvailable = false;
165
+ _.forEach( this.#messageObservers, ( messageObserver ) => {
166
+ messageObserver.onConnectionDisrupted( this.#connectionIdentifier );
167
+ } );
168
+ }
169
+ }
170
+
171
+ }
172
+
173
+ module.exports = MessageHandler;