@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,306 @@
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 tools = require( "#tools" );
8
+ const exceptions = require( "#exceptions" );
9
+ const logger = require( "#logger" );
10
+ const config = require( "#config" );
11
+ const cache = require( "#cache" );
12
+ const messageDispatcher = require( "#message-dispatcher" );
13
+
14
+ /**
15
+ * @typedef {Object} ServiceAddress
16
+ * @property {string} serviceAlias A valid service alias.
17
+ * @property {string} serviceDomainName A valid service domain name.
18
+ * @property {number|undefined} serviceVersion Optional service version. If not provided, the latest version will be assumed as a target.
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} ServiceExecContext
23
+ * @property {string} authToken A valid authentication token that initialized the service call.
24
+ * @property {ServiceCallPredecessor|undefined} previousServiceCall The previous service call in the execution chain (if such exists).
25
+ */
26
+
27
+ /**
28
+ * @typedef {Message} ServiceCallPredecessor
29
+ * @property {string} predecessor The {@link Message.messageID} of the predecessor in the service call tree.
30
+ * @property {ServiceAddress} serviceAddress The address of the service that has to process the service call.
31
+ * @property {Object|undefined} serviceParams The named params to be provided to the API service.
32
+ */
33
+
34
+ /**
35
+ * @typedef {ServiceCallPredecessor} ServiceCall
36
+ * @property {string} authToken A valid authentication token that initialized the service call.
37
+ * @property {number} createdOn A unix timestamp taken at creation time of the service call.
38
+ * @property {number} executionTime The total execution time of this service call in milliseconds.
39
+ * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise it will be 'undefined'.
40
+ * @property {number|undefined} finishedOn A unix timestamp taken at finish time of the service call.
41
+ * @property {boolean} isCompleted Flag to indicate if this service call has been completed.
42
+ * @property {boolean|undefined} isSuccessful A flag indicating if this service call can be considered successful or not. Will be 'undefined' until the service call is processed.
43
+ * @property {string[]} successors The service call IDs of the successors in the service call tree.
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} ServiceCallResult
48
+ * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise it will be 'undefined'.
49
+ * @property {boolean} isSuccessful A flag indicating if this service call can be considered successful or not.
50
+ * @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.
51
+ */
52
+
53
+ /**
54
+ * @callback TaskHandler
55
+ * @param {ServiceCall} serviceCall The service call for processing.
56
+ */
57
+
58
+ /**
59
+ * A class defining a service caller behavior.
60
+ *
61
+ * @class ServiceCaller
62
+ * @extends MessageObserver
63
+ * @public
64
+ */
65
+ class ServiceCaller extends MessageObserver {
66
+
67
+ #serviceCallTasks = {};
68
+
69
+ /**
70
+ * @constructor
71
+ */
72
+ constructor() {
73
+ super();
74
+ }
75
+
76
+ /* Public interface */
77
+
78
+ /**
79
+ * Used to call a service in the service ecosystem asynchronously.
80
+ * <br/>
81
+ * NOTE: This method will timeout after specific preconfigured time, in which case it will resolve with {@link E_COM_SERVICE_EXEC_TIMEOUT} error.
82
+ *
83
+ * @method
84
+ * @param {ServiceAddress} serviceAddress The service address has to define a valid service domain name, service alias, and optionally a service version.
85
+ * @param {Object} serviceParams Set of named parameters to provide to the called service.
86
+ * @param {ServiceExecContext} serviceExecContext The context in which the service call is being executed.
87
+ * @returns {Promise<ServiceCallResult>} Will always return a service call result that can be either successful or not.
88
+ * @public
89
+ */
90
+ executeServiceCall( serviceAddress, serviceParams, serviceExecContext ) {
91
+ let execution = new Promise( ( resolve, reject ) => {
92
+ this.#findServiceInRegistry( serviceAddress ).then( () => {
93
+ return this.#prepareServiceCall( serviceAddress, serviceParams, serviceExecContext );
94
+ } ).then( ( serviceCall ) => {
95
+ return messageDispatcher.sendRequest( serviceCall );
96
+ } ).then( ( messageID ) => {
97
+ this.#addTaskHandler( messageID, ( serviceCall ) => {
98
+ this.#completeServiceCall( serviceCall ).then( ( serviceCall ) => {
99
+ let serviceCallResult = {
100
+ exception: serviceCall.exception,
101
+ isSuccessful: ( serviceCall.isSuccessful !== undefined ) ? serviceCall.isSuccessful : true,
102
+ payload: serviceCall.payload
103
+ };
104
+
105
+ resolve( serviceCallResult );
106
+ } ).catch( ( error ) => {
107
+ reject( exceptions.raise( error ) );
108
+ } );
109
+ } );
110
+ } ).catch( ( error ) => {
111
+ reject( exceptions.raise( error ) );
112
+ } );
113
+ } );
114
+ let timeoutHandle;
115
+ let timeout = new Promise( ( resolve, reject ) => {
116
+ timeoutHandle = setTimeout( () => reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_EXEC_TIMEOUT ) ), config.getSetting( config.setting.SERVICE_EXECUTION_TIMEOUT ) );
117
+ } );
118
+
119
+ return Promise.race( [ execution, timeout ] ).then( ( result ) => {
120
+ clearTimeout( timeoutHandle );
121
+ return result;
122
+ } ).catch( ( error ) => {
123
+ logger.log( `Error during service call execution!`, logger.logSeverity.ERROR, error );
124
+ return {
125
+ isSuccessful: false,
126
+ exception: exceptions.raise( error )
127
+ };
128
+ } );
129
+ }
130
+
131
+ /**
132
+ * Once the proper message is received this method will trigger the completion of the pending {@link ServiceCall} execution started in {@link #executeServiceCall}.
133
+ *
134
+ * @method
135
+ * @param {string} identifier The identifier of the observed connection.
136
+ * @param {Message} message The message for processing.
137
+ * @override
138
+ * @public
139
+ */
140
+ onMessage( identifier, message ) {
141
+ let execution = this.#getTaskHandler( message.messageID );
142
+ if ( typeof ( execution ) === "function" ) {
143
+ execution( message );
144
+ this.#removeTaskHandler( message.messageID );
145
+ } else {
146
+ logger.log( `Received message with ID '${ message.messageID }' in ServiceCaller that has no registered handler. This is probably a software bug!`, logger.logSeverity.WARNING );
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Needs to be invoked by the connection handler when the connection is disrupted.
152
+ *
153
+ * @method
154
+ * @param {string} identifier The identifier of the observed connection.
155
+ * @override
156
+ * @public
157
+ */
158
+ onConnectionDisrupted( identifier ) {
159
+ super.onConnectionDisrupted( identifier );
160
+ }
161
+
162
+ /**
163
+ * Needs to be invoked by the connection handler when the connection is recovered.
164
+ *
165
+ * @method
166
+ * @param {string} identifier The identifier of the observed connection.
167
+ * @override
168
+ * @public
169
+ */
170
+ onConnectionRecovered( identifier ) {
171
+ super.onConnectionRecovered( identifier );
172
+ }
173
+
174
+ /* Private interface */
175
+
176
+ /**
177
+ * Used to verify if the service is registered in the service registry.
178
+ *
179
+ * @method
180
+ * @param {ServiceAddress} serviceAddress
181
+ * @returns {Promise}
182
+ * @private
183
+ */
184
+ #findServiceInRegistry( serviceAddress ) {
185
+ return new Promise( ( resolve, reject ) => {
186
+ let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + serviceAddress.serviceDomainName;
187
+ cache.isSetMember( serviceCatalog, serviceAddress.serviceAlias ).then( ( result ) => {
188
+ if ( result === true ) {
189
+ resolve();
190
+ } else {
191
+ reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_NOT_REGISTERED ) );
192
+ }
193
+ } ).catch( ( error ) => {
194
+ reject( exceptions.raise( error ) );
195
+ } );
196
+ } );
197
+ }
198
+
199
+ /**
200
+ * Used to assemble and prepare a new {@link ServiceCall} object.
201
+ *
202
+ * @method
203
+ * @param {ServiceAddress} serviceAddress The service address has to define a valid service domain name, service alias, and optionally a service version.
204
+ * @param {Object} serviceParams Set of named parameters to provide to the called service.
205
+ * @param {ServiceExecContext} serviceExecContext The context in which the service call is being executed.
206
+ * @returns {Promise<ServiceCall>}
207
+ * @private
208
+ */
209
+ #prepareServiceCall( serviceAddress, serviceParams, serviceExecContext ) {
210
+ return new Promise( ( resolve, reject ) => {
211
+ const ServiceInstance = require( "#service-instance" );
212
+
213
+ // assemble the new service call:
214
+ let chainID = ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.chainID : tools.getUUID();
215
+ let chainLevel = ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.chainLevel + 1 : 0;
216
+ let source = {
217
+ instanceID: ServiceInstance.instanceID,
218
+ route: ServiceInstance.serviceDomainName
219
+ };
220
+ let destination = {
221
+ instanceID: undefined,
222
+ route: serviceAddress.serviceDomainName
223
+ };
224
+ /** @type ServiceCall */
225
+ let serviceCall = {
226
+ authToken: serviceExecContext.authToken,
227
+ chainID: chainID,
228
+ chainLevel: chainLevel,
229
+ createdOn: Date.now(),
230
+ destination: destination,
231
+ executionTime: 0,
232
+ exception: undefined,
233
+ finishedOn: undefined,
234
+ isCompleted: false,
235
+ isSuccessful: undefined,
236
+ messageID: tools.getUUID(),
237
+ payload: undefined,
238
+ predecessor: ( serviceExecContext.previousServiceCall ) ? serviceExecContext.previousServiceCall.messageID : undefined,
239
+ serviceAddress: serviceAddress,
240
+ serviceParams: serviceParams,
241
+ source: source,
242
+ successors: undefined
243
+ };
244
+
245
+ resolve( serviceCall );
246
+ } );
247
+ }
248
+
249
+ /**
250
+ * Used to complete the provided {@link ServiceCall} object by setting all required properties to their correct values.
251
+ *
252
+ * @method
253
+ * @param {ServiceCall} serviceCall
254
+ * @returns {Promise<ServiceCall>}
255
+ * @private
256
+ */
257
+ #completeServiceCall( serviceCall ) {
258
+ return new Promise( ( resolve, reject ) => {
259
+ serviceCall.finishedOn = Date.now();
260
+ serviceCall.executionTime = serviceCall.finishedOn - serviceCall.createdOn;
261
+ serviceCall.isCompleted = true;
262
+
263
+ resolve( serviceCall );
264
+ } );
265
+ }
266
+
267
+ /**
268
+ * Used to add a new task handler to the list of current tasks.
269
+ *
270
+ * @method
271
+ * @param {string} taskID
272
+ * @param {TaskHandler} taskHandler
273
+ * @private
274
+ */
275
+ #addTaskHandler( taskID, taskHandler ) {
276
+ this.#serviceCallTasks[ taskID ] = taskHandler;
277
+ }
278
+
279
+ /**
280
+ * Used to fetch a task handler from the list of current tasks.
281
+ *
282
+ * @method
283
+ * @param {string} taskID
284
+ * @returns {TaskHandler}
285
+ * @private
286
+ */
287
+ #getTaskHandler( taskID ) {
288
+ return this.#serviceCallTasks[ taskID ];
289
+ }
290
+
291
+ /**
292
+ * Used to remove a task handler from the list of current tasks.
293
+ *
294
+ * @method
295
+ * @param {string} taskID
296
+ * @private
297
+ */
298
+ #removeTaskHandler( taskID ) {
299
+ if ( this.#serviceCallTasks[ taskID ] ) {
300
+ delete this.#serviceCallTasks[ taskID ];
301
+ }
302
+ }
303
+
304
+ }
305
+
306
+ module.exports = ServiceCaller;
@@ -0,0 +1,112 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * SPDX-License-Identifier: ICU
4
+ */
5
+
6
+ const ServiceInstance = require( "#service-instance" );
7
+ const exceptions = require( "#exceptions" );
8
+ const messageDispatcher = require( "#message-dispatcher" );
9
+
10
+ /**
11
+ * Abstract class used to define a Service Consumer behavior.
12
+ * <br/>
13
+ * NOTE: Inherit this to create an a module that can be started as a microservice consumer instance.
14
+ * <br/>
15
+ * NOTE: A service consumer is a microservice that can invoke named business services in the APIs of other
16
+ * microservices using {@link ServiceCall} objects. The consumer does not need to know the specifics of
17
+ * the business logic in these services but only the service address and the inbound parameters (if any).
18
+ * The result of the execution will be returned to the consumer in a {@link ServiceCallResult} object.
19
+ *
20
+ * @class ServiceConsumer
21
+ * @extends ServiceInstance
22
+ * @abstract
23
+ * @public
24
+ */
25
+ class ServiceConsumer extends ServiceInstance {
26
+
27
+ #serviceCaller;
28
+
29
+ /**
30
+ * @constructor
31
+ * @param {string} serviceDomainName The service domain name for this service instance.
32
+ * @param {Object} [serviceConfig={}] The JSON configuration for this service.
33
+ */
34
+ constructor( serviceDomainName, serviceConfig = {} ) {
35
+ super( serviceDomainName, serviceConfig );
36
+
37
+ // make sure this abstract class cannot be instantiated:
38
+ if ( new.target === ServiceConsumer ) {
39
+ throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
40
+ }
41
+ }
42
+
43
+ /* Public interface */
44
+
45
+ /**
46
+ * Perform initialization tasks when the service consumer starts.
47
+ * <br/>
48
+ * NOTE: This method will be invoked automatically.
49
+ * <br/>
50
+ * NOTE: If you need to add more onStart logic you can override this method but make sure to call it in the
51
+ * overriding method using: super.onStart()
52
+ *
53
+ * @method
54
+ * @returns {Promise}
55
+ * @override
56
+ * @public
57
+ */
58
+ onStart() {
59
+ return new Promise( ( resolve, reject ) => {
60
+ const ServiceCaller = require( "#service-caller" );
61
+
62
+ this.#serviceCaller = new ServiceCaller();
63
+
64
+ super.onStart().then( () => {
65
+ messageDispatcher.addMessageObserverResponsesIn( this.#serviceCaller );
66
+ resolve();
67
+ } ).catch( ( error ) => {
68
+ reject( exceptions.raise( error ) );
69
+ } );
70
+ } );
71
+ }
72
+
73
+ /**
74
+ * Perform shut down and cleanup tasks when the service consumer stops.
75
+ * <br/>
76
+ * NOTE: This method will be invoked automatically.
77
+ * <br/>
78
+ * NOTE: If you need to add more onStop logic you can override this method but make sure to call it in the
79
+ * overriding method using: super.onStop()
80
+ *
81
+ * @method
82
+ * @returns {Promise}
83
+ * @override
84
+ * @public
85
+ */
86
+ onStop() {
87
+ return new Promise( ( resolve, reject ) => {
88
+ super.onStop().then( () => {
89
+ resolve();
90
+ } ).catch( ( error ) => {
91
+ reject( exceptions.raise( error ) );
92
+ } );
93
+ } );
94
+ }
95
+
96
+ /**
97
+ * Used to invoke a business service.
98
+ *
99
+ * @method
100
+ * @param {ServiceAddress} serviceAddress
101
+ * @param {Object} serviceParams
102
+ * @param {ServiceExecContext} serviceExecContext
103
+ * @returns {Promise<ServiceCallResult>}
104
+ * @public
105
+ */
106
+ callService( serviceAddress, serviceParams, serviceExecContext ) {
107
+ return this.#serviceCaller.executeServiceCall( serviceAddress, serviceParams, serviceExecContext );
108
+ }
109
+
110
+ }
111
+
112
+ module.exports = ServiceConsumer;
@@ -0,0 +1,231 @@
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 ServiceInstance = require( "#service-instance" );
8
+ const _ = require( "lodash" );
9
+ const exceptions = require( "#exceptions" );
10
+ const logger = require( "#logger" );
11
+ const config = require( "#config" );
12
+ const cache = require( "#cache" );
13
+ const messageDispatcher = require( "#message-dispatcher" );
14
+
15
+ /**
16
+ * @typedef {Object} ServiceDefinition
17
+ * @property {string} serviceAlias Service alias.
18
+ * @property {string} serviceFile The JS file containing the service itself. This has to be exposed via package.json import structure!
19
+ * @property {number} [serviceVersion] Service version.
20
+ */
21
+
22
+ /**
23
+ * @callback VerifyAccess
24
+ * @param {string} authToken
25
+ * @param {ServiceAddress} serviceAddress
26
+ * @returns {Promise}
27
+ */
28
+
29
+ /**
30
+ * @callback ServiceHandler
31
+ * @param {ServiceDefinition} serviceDefinition The service definition as provided during the service registration.
32
+ * @param {Object} serviceParams Set of named parameters provided to the called service.
33
+ * @param {ServiceExecContext} serviceExecContext The context in which the service call is being executed.
34
+ * @returns {Promise<Object|undefined>} Optional payload to be returned to the service caller.
35
+ */
36
+
37
+ /**
38
+ * A class defining a service executor behavior.
39
+ *
40
+ * @class ServiceExecutor
41
+ * @extends MessageObserver
42
+ * @public
43
+ */
44
+ class ServiceExecutor extends MessageObserver {
45
+
46
+ #serviceInterface = {};
47
+ /** @type VerifyAccess */
48
+ #verifyAccess;
49
+
50
+ /**
51
+ * @constructor
52
+ */
53
+ constructor() {
54
+ super();
55
+
56
+ this.#verifyAccess = () => {
57
+ return Promise.resolve();
58
+ };
59
+
60
+ cache.addConnectionObserver( this );
61
+ }
62
+
63
+ /* Public interface */
64
+
65
+ /**
66
+ *
67
+ *
68
+ * @method
69
+ * @param {string} identifier The identifier of the observed connection.
70
+ * @param {Message} message The message for processing.
71
+ * @override
72
+ * @public
73
+ */
74
+ onMessage( identifier, message ) {
75
+ this.#processServiceCall( message ).then( ( serviceCall ) => {
76
+ return messageDispatcher.sendResponse( serviceCall );
77
+ } ).catch( ( error ) => {
78
+ logger.log( `Failed to send service call response after processing! Service call ID was: '${ message.messageID }'`, logger.logSeverity.ERROR, error );
79
+ } );
80
+ }
81
+
82
+ /**
83
+ * Needs to be invoked by the connection handler when the connection is disrupted.
84
+ *
85
+ * @method
86
+ * @param {string} identifier The identifier of the observed connection.
87
+ * @override
88
+ * @public
89
+ */
90
+ onConnectionDisrupted( identifier ) {
91
+ super.onConnectionDisrupted( identifier );
92
+ }
93
+
94
+ /**
95
+ * Needs to be invoked by the connection handler when the connection is recovered.
96
+ *
97
+ * @method
98
+ * @param {string} identifier The identifier of the observed connection.
99
+ * @override
100
+ * @public
101
+ */
102
+ onConnectionRecovered( identifier ) {
103
+ super.onConnectionRecovered( identifier );
104
+
105
+ let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + ServiceInstance.serviceDomainName;
106
+ _.forOwn( this.#serviceInterface, ( versions, serviceAlias ) => {
107
+ cache.addToSet( serviceCatalog, serviceAlias ).catch( ( error ) => {
108
+ logger.log( `Record for service '${ serviceAlias }' could not be added to the service registry.`, logger.logSeverity.ERROR, error );
109
+ } );
110
+ } );
111
+ }
112
+
113
+ /**
114
+ * Used to setup the method for service access verification.
115
+ *
116
+ * @method
117
+ * @param {VerifyAccess} verifyAccess
118
+ * @public
119
+ */
120
+ configureVerifyAccess( verifyAccess ) {
121
+ if ( typeof ( verifyAccess ) === "function" ) {
122
+ this.#verifyAccess = verifyAccess;
123
+ } else {
124
+ logger.log( `Attempting to setup service verification method that is not a function!`, logger.logSeverity.WARNING );
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Used to add a service handler to the service interface.
130
+ * <br/>
131
+ * NOTE: If the same version of the service handler already exists, it will be overridden!
132
+ *
133
+ * @method
134
+ * @param {ServiceHandler} serviceHandler
135
+ * @param {ServiceDefinition} serviceDefinition
136
+ * @public
137
+ */
138
+ addServiceHandler( serviceHandler, serviceDefinition ) {
139
+ if ( !this.#serviceInterface[ serviceDefinition.serviceAlias ] ) {
140
+ this.#serviceInterface[ serviceDefinition.serviceAlias ] = {};
141
+ }
142
+ if ( this.#serviceInterface[ serviceDefinition.serviceAlias ][ serviceDefinition.serviceVersion ] ) {
143
+ logger.log( `Service handler for '${ serviceDefinition.serviceAlias }' version '${ serviceDefinition.serviceVersion }' already existed and will be overridden.`, logger.logSeverity.WARNING );
144
+ }
145
+ this.#serviceInterface[ serviceDefinition.serviceAlias ][ serviceDefinition.serviceVersion ] = serviceHandler.bind( this, _.cloneDeep( serviceDefinition ) );
146
+ }
147
+
148
+ /* Private interface */
149
+
150
+ /**
151
+ * Used to assemble {@link ServiceExecContext} from the provided service call object.
152
+ *
153
+ * @method
154
+ * @param {ServiceCall} serviceCall
155
+ * @returns {ServiceExecContext}
156
+ * @private
157
+ */
158
+ static #assembleServiceExecContext( serviceCall ) {
159
+ return {
160
+ authToken: serviceCall.authToken,
161
+ previousServiceCall: {
162
+ chainID: serviceCall.chainID,
163
+ chainLevel: serviceCall.chainLevel,
164
+ destination: serviceCall.destination,
165
+ messageID: serviceCall.messageID,
166
+ payload: serviceCall.payload,
167
+ predecessor: serviceCall.predecessor,
168
+ serviceAddress: serviceCall.serviceAddress,
169
+ serviceParams: serviceCall.serviceParams,
170
+ source: serviceCall.source
171
+ }
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Used to process the actual service call.
177
+ *
178
+ * @method
179
+ * @param {ServiceCall} serviceCall
180
+ * @returns {Promise<ServiceCall>}
181
+ * @private
182
+ */
183
+ #processServiceCall( serviceCall ) {
184
+ return new Promise( ( resolve, reject ) => {
185
+ this.#verifyAccess( serviceCall.authToken, serviceCall.serviceAddress ).then( () => {
186
+ return this.#identifyService( serviceCall.serviceAddress );
187
+ } ).then( ( serviceHandler ) => {
188
+ return serviceHandler( serviceCall.serviceParams, ServiceExecutor.#assembleServiceExecContext( serviceCall ) );
189
+ } ).then( ( payload ) => {
190
+ serviceCall.isSuccessful = true;
191
+ serviceCall.payload = payload;
192
+ resolve( serviceCall );
193
+ } ).catch( ( error ) => {
194
+ serviceCall.isSuccessful = false;
195
+ serviceCall.exception = exceptions.raise( error ).asJSON();
196
+ resolve( serviceCall );
197
+ } );
198
+ } );
199
+ }
200
+
201
+ /**
202
+ * Used to identify the service in the service interface and retrieve its definition.
203
+ *
204
+ * @method
205
+ * @param {ServiceAddress} serviceAddress
206
+ * @returns {Promise<ServiceHandler>}
207
+ * @private
208
+ */
209
+ #identifyService( serviceAddress ) {
210
+ return new Promise( ( resolve, reject ) => {
211
+ if ( this.#serviceInterface[ serviceAddress.serviceAlias ] ) {
212
+ let serviceVersion = serviceAddress.serviceVersion;
213
+ if ( !serviceVersion ) {
214
+ serviceVersion = _.last( _.sortBy( _.keys( this.#serviceInterface[ serviceAddress.serviceAlias ] ) ) );
215
+ }
216
+
217
+ let serviceHandler = this.#serviceInterface[ serviceAddress.serviceAlias ][ serviceVersion ];
218
+ if ( serviceHandler ) {
219
+ resolve( serviceHandler );
220
+ } else {
221
+ reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_HANDLER_NOT_FOUND ) );
222
+ }
223
+ } else {
224
+ reject( exceptions.raise( exceptions.exceptionCode.E_COM_SERVICE_NOT_FOUND ) );
225
+ }
226
+ } );
227
+ }
228
+
229
+ }
230
+
231
+ module.exports = ServiceExecutor;