@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.
- package/components/auditing.js +163 -0
- package/components/connection-observer.js +53 -0
- package/components/exchange/default/default-message-exchange.js +133 -0
- package/components/exchange/default/default-message-receiver.js +89 -0
- package/components/exchange/default/default-message-sender.js +90 -0
- package/components/exchange/message-dispatcher.js +162 -0
- package/components/exchange/message-exchange.js +418 -0
- package/components/exchange/message-handler.js +173 -0
- package/components/exchange/message-memory-cache.js +150 -0
- package/components/exchange/message-observer.js +76 -0
- package/components/exchange/message-receiver.js +113 -0
- package/components/exchange/message-sender.js +133 -0
- package/components/exchange/message-tracer.js +146 -0
- package/components/service-caller.js +306 -0
- package/components/service-consumer.js +112 -0
- package/components/service-executor.js +231 -0
- package/components/service-instance.js +280 -0
- package/components/service-provider.js +221 -0
- package/integrations/gcloud-integration.js +61 -0
- package/integrations/redis-integration.js +261 -0
- package/package.json +54 -0
- package/settings.json +33 -0
- package/utils/cache.js +507 -0
- package/utils/config.js +146 -0
- package/utils/exceptions.js +241 -0
- package/utils/logger.js +69 -0
- package/utils/tools.js +537 -0
|
@@ -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;
|