@ti-engine/core 1.2.4 → 1.3.3

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.
@@ -48,15 +48,217 @@ const messageDispatcher = require( "#message-dispatcher" );
48
48
 
49
49
  /**
50
50
  * @typedef {Object} ServiceCallResult
51
- * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise, it will be 'undefined'.
51
+ * @property {Exception|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise, it will be 'undefined'.
52
52
  * @property {boolean} isSuccessful A flag indicating if this service call can be considered successful or not.
53
- * @property {Object|string|undefined} payload The payload containing the results from the service call processing. If string it is ID of the payload in the memory cache instead.
53
+ * @property {Object|string|undefined} payload The payload containing the results from the service call processing. If a string, it is ID of the payload in the memory cache instead.
54
+ */
55
+
56
+ /**
57
+ * Used to assemble and prepare a new {@link ServiceCall} object.
58
+ *
59
+ * @method
60
+ * @param {string} messageID The message ID of the service call. This has to be unique across the whole service call tree, including the current service call, and will be used to identify the service call in the service call tree.
61
+ * @param {ServiceAddress} serviceAddress The service address has to define a valid service domain name, service alias, and optionally a service version.
62
+ * @param {Object} serviceParams Set of named parameters to provide to the called service.
63
+ * @param {ServiceExecContext} serviceExecContext The context in which the service call is being executed.
64
+ * @returns {Promise<ServiceCall>}
65
+ * @private
66
+ */
67
+ let prepareServiceCall = ( messageID, serviceAddress, serviceParams, serviceExecContext ) => {
68
+ return new Promise( ( resolve ) => {
69
+ const ServiceInstance = require( "#service-instance" );
70
+
71
+ // assemble the new service call:
72
+ let chainID = ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.chainID : tools.getUUID();
73
+ let chainLevel = ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.chainLevel + 1 : 0;
74
+ let source = {
75
+ instanceID: ServiceInstance.instanceID,
76
+ route: ServiceInstance.serviceDomainName
77
+ };
78
+ let destination = {
79
+ instanceID: undefined,
80
+ route: serviceAddress.serviceDomainName
81
+ };
82
+ /** @type ServiceCall */
83
+ let serviceCall = {
84
+ authToken: serviceExecContext.authToken,
85
+ chainID: chainID,
86
+ chainLevel: chainLevel,
87
+ createdOn: Date.now(),
88
+ destination: destination,
89
+ executionTime: 0,
90
+ exception: undefined,
91
+ finishedOn: undefined,
92
+ isCompleted: false,
93
+ isSuccessful: undefined,
94
+ messageID: messageID,
95
+ payload: undefined,
96
+ predecessor: ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.messageID : undefined,
97
+ serviceAddress: serviceAddress,
98
+ serviceParams: serviceParams,
99
+ source: source,
100
+ successors: undefined
101
+ };
102
+
103
+ resolve( serviceCall );
104
+ } );
105
+ }
106
+
107
+ /**
108
+ * Used to verify if the service is registered in the service registry.
109
+ *
110
+ * @method
111
+ * @param {ServiceAddress} serviceAddress
112
+ * @returns {Promise}
113
+ * @private
54
114
  */
115
+ let findServiceInRegistry = ( serviceAddress ) => {
116
+ return new Promise( ( resolve, reject ) => {
117
+ let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + serviceAddress.serviceDomainName;
118
+ cache.instance.isSetMember( serviceCatalog, serviceAddress.serviceAlias ).then( ( result ) => {
119
+ if ( result === true ) {
120
+ resolve();
121
+ } else {
122
+ reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_NOT_REGISTERED ) );
123
+ }
124
+ } ).catch( ( error ) => {
125
+ reject( exceptions.raise( error ) );
126
+ } );
127
+ } );
128
+ }
55
129
 
56
130
  /**
57
- * @callback TaskHandler
58
- * @param {ServiceCall} serviceCall The service call for processing.
131
+ * A class defining a service call processor.
132
+ *
133
+ * @class ServiceCallProcessor
134
+ * @private
59
135
  */
136
+ class ServiceCallProcessor {
137
+
138
+ #messageID;
139
+ #serviceAddress;
140
+ #serviceParams;
141
+ #serviceExecContext;
142
+ #timeoutHandle;
143
+ #taskCompletionHandler;
144
+ #isProcessed = false;
145
+
146
+ /**
147
+ * @constructor
148
+ * @param {ServiceAddress} serviceAddress The service address has to define a valid service domain name, service alias, and optionally a service version.
149
+ * @param {Object} serviceParams Set of named parameters to provide to the called service.
150
+ * @param {ServiceExecContext} serviceExecContext The context in which the service call is being executed.
151
+ * @returns {ServiceCallProcessor}
152
+ */
153
+ constructor( serviceAddress, serviceParams, serviceExecContext ) {
154
+ this.#messageID = tools.getUUID();
155
+ this.#serviceAddress = serviceAddress;
156
+ this.#serviceParams = serviceParams;
157
+ this.#serviceExecContext = serviceExecContext;
158
+ }
159
+
160
+ /* Public interface */
161
+
162
+ /**
163
+ * The unique identifier of the service call.
164
+ *
165
+ * @property
166
+ * @returns {string}
167
+ * @public
168
+ */
169
+ get messageID() {
170
+ return this.#messageID;
171
+ }
172
+
173
+ /**
174
+ * Used to start the execution of the service call.
175
+ * <br/>
176
+ * NOTE: This method will time out after specific preconfigured time, in which case it will resolve with {@link E_COM_SERVICE_EXEC_TIMEOUT} error.
177
+ *
178
+ * @method
179
+ * @returns {Promise<ServiceCallResult>}
180
+ * @public
181
+ */
182
+ process() {
183
+ return Promise.race( [ this.#execute(), this.#timeout() ] ).then( ( result ) => {
184
+ clearTimeout( this.#timeoutHandle );
185
+ this.#isProcessed = true;
186
+ return result;
187
+ } ).catch( ( error ) => {
188
+ clearTimeout( this.#timeoutHandle );
189
+ this.#isProcessed = true;
190
+ logger.log( `Error during service call execution!`, logger.logSeverity.ERROR, error );
191
+ return {
192
+ isSuccessful: false,
193
+ exception: exceptions.raise( error ),
194
+ payload: undefined
195
+ };
196
+ } );
197
+ }
198
+
199
+ /**
200
+ * Used to complete the execution of the service call that was started within the {@link process} method.
201
+ *
202
+ * @method
203
+ * @param {ServiceCall} serviceCall
204
+ * @public
205
+ */
206
+ complete( serviceCall ) {
207
+ if ( this.#isProcessed !== true ) {
208
+ let serviceCallResult = {
209
+ exception: serviceCall.exception,
210
+ isSuccessful: ( serviceCall.isSuccessful !== undefined ) ? tools.toBool( serviceCall.isSuccessful ) : true,
211
+ payload: serviceCall.payload
212
+ };
213
+ this.#taskCompletionHandler( serviceCallResult );
214
+ }
215
+ }
216
+
217
+ /* Private interface */
218
+
219
+ /**
220
+ * Used to execute the service call.
221
+ *
222
+ * @method
223
+ * @returns {Promise<ServiceCallResult>}
224
+ * @private
225
+ */
226
+ #execute() {
227
+ return new Promise( ( resolve, reject ) => {
228
+ findServiceInRegistry( this.#serviceAddress ).then( () => {
229
+ return prepareServiceCall( this.#messageID, this.#serviceAddress, this.#serviceParams, this.#serviceExecContext );
230
+ } ).then( ( serviceCall ) => {
231
+ return messageDispatcher.instance.sendRequest( serviceCall );
232
+ } ).then( () => {
233
+ this.#taskCompletionHandler = ( serviceCallResult ) => {
234
+ resolve( serviceCallResult );
235
+ };
236
+ } ).catch( ( error ) => {
237
+ if ( this.#isProcessed !== true ) {
238
+ reject( exceptions.raise( error ) );
239
+ }
240
+ } );
241
+ } );
242
+ }
243
+
244
+ /**
245
+ * Used to time out the service call execution.
246
+ *
247
+ * @method
248
+ * @returns {Promise<ServiceCallResult>}
249
+ * @private
250
+ */
251
+ #timeout() {
252
+ return new Promise( ( resolve, reject ) => {
253
+ this.#timeoutHandle = setTimeout( () => {
254
+ if ( this.#isProcessed !== true ) {
255
+ reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_EXEC_TIMEOUT ) );
256
+ }
257
+ }, config.getSetting( config.setting.SERVICE_EXECUTION_TIMEOUT ) );
258
+ } );
259
+ }
260
+
261
+ }
60
262
 
61
263
  /**
62
264
  * A class defining a service caller behavior.
@@ -67,13 +269,13 @@ const messageDispatcher = require( "#message-dispatcher" );
67
269
  */
68
270
  class ServiceCaller extends MessageObserver {
69
271
 
70
- #serviceCallTasks = {};
272
+ #serviceCallProcessors = {};
71
273
 
72
274
  /**
73
275
  * @constructor
74
276
  */
75
277
  constructor() {
76
- super();
278
+ super( 10 );
77
279
  }
78
280
 
79
281
  /* Public interface */
@@ -91,44 +293,14 @@ class ServiceCaller extends MessageObserver {
91
293
  * @public
92
294
  */
93
295
  executeServiceCall( serviceAddress, serviceParams, serviceExecContext ) {
94
- let execution = new Promise( ( resolve, reject ) => {
95
- this.#findServiceInRegistry( serviceAddress ).then( () => {
96
- return this.#prepareServiceCall( serviceAddress, serviceParams, serviceExecContext );
97
- } ).then( ( serviceCall ) => {
98
- return messageDispatcher.instance.sendRequest( serviceCall );
99
- } ).then( ( messageID ) => {
100
- this.#addTaskHandler( messageID, ( serviceCall ) => {
101
- this.#completeServiceCall( serviceCall ).then( ( serviceCall ) => {
102
- let serviceCallResult = {
103
- exception: serviceCall.exception,
104
- isSuccessful: ( serviceCall.isSuccessful !== undefined ) ? serviceCall.isSuccessful : true,
105
- payload: serviceCall.payload
106
- };
107
-
108
- resolve( serviceCallResult );
109
- } ).catch( ( error ) => {
110
- reject( exceptions.raise( error ) );
111
- } );
112
- } );
113
- } ).catch( ( error ) => {
114
- reject( exceptions.raise( error ) );
296
+ return new Promise( ( resolve ) => {
297
+ let processor = new ServiceCallProcessor( serviceAddress, serviceParams, serviceExecContext );
298
+ this.#addProcessor( processor.messageID, processor );
299
+ processor.process().then( ( serviceCallResult ) => {
300
+ this.#removeProcessor( processor.messageID );
301
+ resolve( serviceCallResult );
115
302
  } );
116
303
  } );
117
- let timeoutHandle;
118
- let timeout = new Promise( ( resolve, reject ) => {
119
- timeoutHandle = setTimeout( () => reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_EXEC_TIMEOUT ) ), config.getSetting( config.setting.SERVICE_EXECUTION_TIMEOUT ) );
120
- } );
121
-
122
- return Promise.race( [ execution, timeout ] ).then( ( result ) => {
123
- clearTimeout( timeoutHandle );
124
- return result;
125
- } ).catch( ( error ) => {
126
- logger.log( `Error during service call execution!`, logger.logSeverity.ERROR, error );
127
- return {
128
- isSuccessful: false,
129
- exception: exceptions.raise( error )
130
- };
131
- } );
132
304
  }
133
305
 
134
306
  /**
@@ -136,18 +308,26 @@ class ServiceCaller extends MessageObserver {
136
308
  *
137
309
  * @method
138
310
  * @param {string} identifier The identifier of the observed connection.
139
- * @param {Message} message The message for processing.
311
+ * @param {ServiceCall} serviceCall The service call message for processing.
312
+ * @returns {ServiceCall} The service call message that was received.
140
313
  * @override
141
314
  * @public
142
315
  */
143
- onMessage( identifier, message ) {
144
- let execution = this.#getTaskHandler( message.messageID );
145
- if ( typeof ( execution ) === "function" ) {
146
- execution( message );
147
- this.#removeTaskHandler( message.messageID );
316
+ onMessage( identifier, serviceCall ) {
317
+ // Complete the service call:
318
+ serviceCall.finishedOn = Date.now();
319
+ serviceCall.executionTime = serviceCall.finishedOn - serviceCall.createdOn;
320
+ serviceCall.isCompleted = true;
321
+
322
+ let processor = this.#getProcessor( serviceCall.messageID );
323
+ if ( processor ) {
324
+ this.#removeProcessor( serviceCall.messageID );
325
+ processor.complete( serviceCall );
148
326
  } else {
149
- logger.log( `Received message with ID '${ message.messageID }' in ServiceCaller that has no registered handler. This is probably a software bug!`, logger.logSeverity.WARNING );
327
+ logger.log( `Received service call message with ID '${ serviceCall.messageID }' without registered processor! This may be caused by a service call timeout.`, logger.logSeverity.DEBUG, serviceCall.exception || undefined );
150
328
  }
329
+
330
+ return serviceCall;
151
331
  }
152
332
 
153
333
  /**
@@ -174,133 +354,54 @@ class ServiceCaller extends MessageObserver {
174
354
  super.onConnectionRecovered( identifier );
175
355
  }
176
356
 
177
- /* Private interface */
178
-
179
- /**
180
- * Used to verify if the service is registered in the service registry.
181
- *
182
- * @method
183
- * @param {ServiceAddress} serviceAddress
184
- * @returns {Promise}
185
- * @private
186
- */
187
- #findServiceInRegistry( serviceAddress ) {
188
- return new Promise( ( resolve, reject ) => {
189
- let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + serviceAddress.serviceDomainName;
190
- cache.instance.isSetMember( serviceCatalog, serviceAddress.serviceAlias ).then( ( result ) => {
191
- if ( result === true ) {
192
- resolve();
193
- } else {
194
- reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_NOT_REGISTERED ) );
195
- }
196
- } ).catch( ( error ) => {
197
- reject( exceptions.raise( error ) );
198
- } );
199
- } );
200
- }
201
-
202
357
  /**
203
- * Used to assemble and prepare a new {@link ServiceCall} object.
358
+ * Needs to be invoked by the connection handler when the connection is irrevocably lost.
204
359
  *
205
360
  * @method
206
- * @param {ServiceAddress} serviceAddress The service address has to define a valid service domain name, service alias, and optionally a service version.
207
- * @param {Object} serviceParams Set of named parameters to provide to the called service.
208
- * @param {ServiceExecContext} serviceExecContext The context in which the service call is being executed.
209
- * @returns {Promise<ServiceCall>}
210
- * @private
361
+ * @param {string} identifier The identifier of the observed connection.
362
+ * @override
363
+ * @public
211
364
  */
212
- #prepareServiceCall( serviceAddress, serviceParams, serviceExecContext ) {
213
- return new Promise( ( resolve ) => {
214
- const ServiceInstance = require( "#service-instance" );
215
-
216
- // assemble the new service call:
217
- let chainID = ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.chainID : tools.getUUID();
218
- let chainLevel = ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.chainLevel + 1 : 0;
219
- let source = {
220
- instanceID: ServiceInstance.instanceID,
221
- route: ServiceInstance.serviceDomainName
222
- };
223
- let destination = {
224
- instanceID: undefined,
225
- route: serviceAddress.serviceDomainName
226
- };
227
- /** @type ServiceCall */
228
- let serviceCall = {
229
- authToken: serviceExecContext.authToken,
230
- chainID: chainID,
231
- chainLevel: chainLevel,
232
- createdOn: Date.now(),
233
- destination: destination,
234
- executionTime: 0,
235
- exception: undefined,
236
- finishedOn: undefined,
237
- isCompleted: false,
238
- isSuccessful: undefined,
239
- messageID: tools.getUUID(),
240
- payload: undefined,
241
- predecessor: ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.messageID : undefined,
242
- serviceAddress: serviceAddress,
243
- serviceParams: serviceParams,
244
- source: source,
245
- successors: undefined
246
- };
247
-
248
- resolve( serviceCall );
249
- } );
365
+ onConnectionLost( identifier ) {
366
+ super.onConnectionLost( identifier );
250
367
  }
251
368
 
252
- /**
253
- * Used to complete the provided {@link ServiceCall} object by setting all required properties to their correct values.
254
- *
255
- * @method
256
- * @param {ServiceCall} serviceCall
257
- * @returns {Promise<ServiceCall>}
258
- * @private
259
- */
260
- #completeServiceCall( serviceCall ) {
261
- return new Promise( ( resolve ) => {
262
- serviceCall.finishedOn = Date.now();
263
- serviceCall.executionTime = serviceCall.finishedOn - serviceCall.createdOn;
264
- serviceCall.isCompleted = true;
265
-
266
- resolve( serviceCall );
267
- } );
268
- }
369
+ /* Private interface */
269
370
 
270
371
  /**
271
372
  * Used to add a new task handler to the list of current tasks.
272
373
  *
273
374
  * @method
274
- * @param {string} taskID
275
- * @param {TaskHandler} taskHandler
375
+ * @param {string} messageID
376
+ * @param {ServiceCallProcessor} processor
276
377
  * @private
277
378
  */
278
- #addTaskHandler( taskID, taskHandler ) {
279
- this.#serviceCallTasks[ taskID ] = taskHandler;
379
+ #addProcessor( messageID, processor ) {
380
+ this.#serviceCallProcessors[ messageID ] = processor;
280
381
  }
281
382
 
282
383
  /**
283
384
  * Used to fetch a task handler from the list of current tasks.
284
385
  *
285
386
  * @method
286
- * @param {string} taskID
287
- * @returns {TaskHandler}
387
+ * @param {string} messageID
388
+ * @returns {ServiceCallProcessor}
288
389
  * @private
289
390
  */
290
- #getTaskHandler( taskID ) {
291
- return this.#serviceCallTasks[ taskID ];
391
+ #getProcessor( messageID ) {
392
+ return this.#serviceCallProcessors[ messageID ];
292
393
  }
293
394
 
294
395
  /**
295
396
  * Used to remove a task handler from the list of current tasks.
296
397
  *
297
398
  * @method
298
- * @param {string} taskID
399
+ * @param {string} messageID
299
400
  * @private
300
401
  */
301
- #removeTaskHandler( taskID ) {
302
- if ( this.#serviceCallTasks[ taskID ] ) {
303
- delete this.#serviceCallTasks[ taskID ];
402
+ #removeProcessor( messageID ) {
403
+ if ( this.#serviceCallProcessors[ messageID ] ) {
404
+ delete this.#serviceCallProcessors[ messageID ];
304
405
  }
305
406
  }
306
407
 
@@ -91,16 +91,19 @@ class ServiceExecutor extends MessageObserver {
91
91
  *
92
92
  * @method
93
93
  * @param {string} identifier The identifier of the observed connection.
94
- * @param {Message} message The message for processing.
94
+ * @param {ServiceCall} serviceCall The service call message for processing.
95
+ * @returns {ServiceCall} The service call message that was received.
95
96
  * @override
96
97
  * @public
97
98
  */
98
- onMessage( identifier, message ) {
99
- this.#processServiceCall( message ).then( ( serviceCall ) => {
99
+ onMessage( identifier, serviceCall ) {
100
+ this.#processServiceCall( serviceCall ).then( ( serviceCall ) => {
100
101
  return messageDispatcher.instance.sendResponse( serviceCall );
101
102
  } ).catch( ( error ) => {
102
- logger.log( `Failed to send service call response after processing! Service call ID was: '${ message.messageID }'`, logger.logSeverity.ERROR, error );
103
+ logger.log( `Failed to send service call response after processing! Service call message ID was: '${ serviceCall.messageID }'`, logger.logSeverity.ERROR, error );
103
104
  } );
105
+
106
+ return serviceCall;
104
107
  }
105
108
 
106
109
  /**
@@ -127,6 +130,18 @@ class ServiceExecutor extends MessageObserver {
127
130
  super.onConnectionRecovered( identifier );
128
131
  }
129
132
 
133
+ /**
134
+ * Needs to be invoked by the connection handler when the connection is irrevocably lost.
135
+ *
136
+ * @method
137
+ * @param {string} identifier The identifier of the observed connection.
138
+ * @override
139
+ * @public
140
+ */
141
+ onConnectionLost( identifier ) {
142
+ super.onConnectionLost( identifier );
143
+ }
144
+
130
145
  /**
131
146
  * Used to set up the method for service access verification.
132
147
  *
@@ -37,6 +37,7 @@ class ServiceInstance {
37
37
  #serviceConfig;
38
38
  #serviceHealthCheck;
39
39
  #reportHealthyJob;
40
+ #healthReportUnderway = false;
40
41
 
41
42
  /**
42
43
  * @constructor
@@ -225,10 +226,14 @@ class ServiceInstance {
225
226
  * @public
226
227
  */
227
228
  reportHealthy() {
228
- if ( cache.instance.isOperational ) {
229
+ if ( cache.instance.isOperational && !this.#healthReportUnderway ) {
230
+ this.#healthReportUnderway = true;
229
231
  let timestamp = new Date();
230
- cache.instance.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).catch( ( error ) => {
231
- logger.log( `Error while trying to report for health check from '${ ServiceInstance.instanceID }'!`, logger.logSeverity.WARNING, error );
232
+ cache.instance.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).then( () => {
233
+ this.#healthReportUnderway = false;
234
+ } ).catch( ( error ) => {
235
+ this.#healthReportUnderway = false;
236
+ logger.log( `Failed to report for health check from instance '${ ServiceInstance.instanceID }'!`, logger.logSeverity.WARNING, error );
232
237
  } );
233
238
  }
234
239
  }