@ti-engine/core 1.1.5 → 1.1.8
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/CHANGELOG.md +33 -0
- package/README.md +45 -11
- package/bin/start-instance.js +40 -16
- package/components/auditing.js +3 -3
- package/components/exchange/default/default-message-receiver.js +6 -2
- package/components/exchange/default/default-message-sender.js +6 -2
- package/components/exchange/message-dispatcher.js +13 -9
- package/components/exchange/message-exchange.js +57 -21
- package/components/exchange/message-memory-cache.js +17 -3
- package/components/exchange/message-tracer.js +138 -92
- package/components/service-caller.js +6 -6
- package/components/service-consumer.js +3 -3
- package/components/service-executor.js +38 -16
- package/components/service-instance.js +39 -24
- package/components/service-provider.js +5 -5
- package/integrations/gcloud-integration.js +3 -2
- package/integrations/redis-integration.js +135 -43
- package/package.json +6 -6
- package/utils/cache.js +24 -7
- package/utils/config.js +9 -1
- package/utils/localization.js +2 -2
- package/utils/logger.js +3 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
|
|
3
|
-
* Copyright © 2021-
|
|
3
|
+
* Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
4
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
5
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
6
|
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -82,103 +82,149 @@ let messageStateEnum = tools.enum( {
|
|
|
82
82
|
module.exports.messageState = messageStateEnum;
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Used
|
|
85
|
+
* Used for recording message trace entries.
|
|
86
86
|
*
|
|
87
|
-
* @
|
|
88
|
-
* @
|
|
89
|
-
* @
|
|
90
|
-
* @private
|
|
87
|
+
* @class MessageTracer
|
|
88
|
+
* @singleton
|
|
89
|
+
* @public
|
|
91
90
|
*/
|
|
92
|
-
|
|
93
|
-
logger.log( formatLogEntry( traceEntry ), severity, traceEntry );
|
|
94
|
-
};
|
|
91
|
+
class MessageTracer {
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
* Used to format trace entry into log-suitable string.
|
|
98
|
-
*
|
|
99
|
-
* @method
|
|
100
|
-
* @param {TiTraceEntry} traceEntry
|
|
101
|
-
* @return {string} Prepared trace info.
|
|
102
|
-
* @private
|
|
103
|
-
*/
|
|
104
|
-
let formatLogEntry = ( traceEntry ) => {
|
|
105
|
-
return `Message(${ traceEntry.chainID || traceEntry.messageID }) Trace: '${ traceEntry.messageType } ${ traceEntry.dispatchEvent } ${ traceEntry.messageState }' From: '${ traceEntry.fromAddress }' To: '${ traceEntry.toAddress }'`;
|
|
106
|
-
};
|
|
93
|
+
static #instance = null;
|
|
107
94
|
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
/** @type Message */
|
|
118
|
-
let messageSnapshot = tools.parseJSON( _.replace( tools.stringifyJSON( message ), /("\w*?pin\w*?"|"\w*?pass\w*?"|"\w*?otp\w*?"):"(.*?)"/gmi, "\"SENSITIVE_PROPERTY\":\"OBSCURED_BY_SYSTEM\"" ) );
|
|
119
|
-
delete messageSnapshot.payload;
|
|
120
|
-
return messageSnapshot;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Used to create a trace entry for the provided {@link Message} and parameters.
|
|
125
|
-
* <br/>
|
|
126
|
-
* NOTE: By default all trace events are stored in the memory cache for further processing and analysis. The
|
|
127
|
-
* location is configured in the MESSAGE_EXCHANGE_TRACE_REPOSITORY setting.
|
|
128
|
-
* <br/>
|
|
129
|
-
* NOTE: Trace events are logged with severity level NOTICE or ERROR for failed dispatches. They still might be
|
|
130
|
-
* filtered out if the minimum log level setting is set too high.
|
|
131
|
-
*
|
|
132
|
-
* @method
|
|
133
|
-
* @param {Message} message The message to trace.
|
|
134
|
-
* @param {TiMessageType} messageType The type of the message.
|
|
135
|
-
* @param {TiDispatchEvent} dispatchEvent The event in the dispatch system that triggered the trace entry.
|
|
136
|
-
* @param {TiMessageState} messageState The state of the processing of the message.
|
|
137
|
-
* @public
|
|
138
|
-
*/
|
|
139
|
-
module.exports.recordTraceEntry = ( message, messageType, dispatchEvent, messageState ) => {
|
|
140
|
-
// depending on whether the message comes as request or response, the from and to addresses will be opposite:
|
|
141
|
-
let source = message.source.route + "." + message.source.instanceID;
|
|
142
|
-
let destination = message.destination.route + ( ( message.destination.instanceID != null ) ? "." + message.destination.instanceID : "" );
|
|
143
|
-
let messageSnapshot = obscureSensitiveData( message );
|
|
144
|
-
delete messageSnapshot.chainID;
|
|
145
|
-
delete messageSnapshot.messageID;
|
|
146
|
-
let currentDate = new Date();
|
|
147
|
-
|
|
148
|
-
/** @type TiTraceEntry */
|
|
149
|
-
let traceEntry = {
|
|
150
|
-
chainID: message.chainID,
|
|
151
|
-
dispatchEvent: tools.getEnumName( dispatchEventEnum, dispatchEvent ),
|
|
152
|
-
fromAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? source : destination,
|
|
153
|
-
messageID: message.messageID,
|
|
154
|
-
messageSnapshot: messageSnapshot,
|
|
155
|
-
messageState: tools.getEnumName( messageStateEnum, messageState ),
|
|
156
|
-
messageType: tools.getEnumName( messageTypeEnum, messageType ),
|
|
157
|
-
toAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? destination : source,
|
|
158
|
-
traceTimestamp: currentDate.getTime(),
|
|
159
|
-
traceID: tools.getUUID()
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// only write the trace in the general log if this is enabled:
|
|
163
|
-
if ( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_LOG_ENABLED ) === true ) {
|
|
164
|
-
createLogEntry( traceEntry, ( dispatchEvent === dispatchEventEnum.FAILED ) ? logger.logSeverity.ERROR : logger.logSeverity.NOTICE );
|
|
95
|
+
/**
|
|
96
|
+
* @constructor
|
|
97
|
+
* @return {MessageTracer}
|
|
98
|
+
*/
|
|
99
|
+
constructor() {
|
|
100
|
+
if ( !MessageTracer.#instance ) {
|
|
101
|
+
MessageTracer.#instance = this;
|
|
102
|
+
}
|
|
103
|
+
return MessageTracer.#instance;
|
|
165
104
|
}
|
|
166
105
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
106
|
+
/* Public interface */
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Used to initialize the message tracer.
|
|
110
|
+
*
|
|
111
|
+
* @method
|
|
112
|
+
* @returns {Promise}
|
|
113
|
+
* @public
|
|
114
|
+
*/
|
|
115
|
+
initialize() {
|
|
116
|
+
return new Promise( ( resolve, reject ) => {
|
|
117
|
+
cache.instance.setJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceRoot, "$", 1 ).then( () => {
|
|
118
|
+
resolve();
|
|
119
|
+
} ).catch( ( error ) => {
|
|
120
|
+
reject( exceptions.raise( error ) );
|
|
178
121
|
} );
|
|
179
|
-
}
|
|
180
|
-
|
|
122
|
+
} );
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Used to create a trace entry for the provided {@link Message} and parameters.
|
|
127
|
+
* <br/>
|
|
128
|
+
* NOTE: By default all trace events are stored in the memory cache for further processing and analysis. The
|
|
129
|
+
* location is configured in the MESSAGE_EXCHANGE_TRACE_REPOSITORY setting.
|
|
130
|
+
* <br/>
|
|
131
|
+
* NOTE: Trace events are logged with severity level NOTICE or ERROR for failed dispatches. They still might be
|
|
132
|
+
* filtered out if the minimum log level setting is set too high.
|
|
133
|
+
*
|
|
134
|
+
* @method
|
|
135
|
+
* @param {Message} message The message to trace.
|
|
136
|
+
* @param {TiMessageType} messageType The type of the message.
|
|
137
|
+
* @param {TiDispatchEvent} dispatchEvent The event in the dispatch system that triggered the trace entry.
|
|
138
|
+
* @param {TiMessageState} messageState The state of the message processing.
|
|
139
|
+
* @public
|
|
140
|
+
*/
|
|
141
|
+
recordTraceEntry( message, messageType, dispatchEvent, messageState ) {
|
|
142
|
+
// Depending on whether the message comes as request or response, the from and to addresses will be opposite:
|
|
143
|
+
let source = message.source.route + "." + message.source.instanceID;
|
|
144
|
+
let destination = message.destination.route + ( ( message.destination.instanceID != null ) ? "." + message.destination.instanceID : "" );
|
|
145
|
+
let messageSnapshot = MessageTracer.#obscureSensitiveData( message );
|
|
146
|
+
delete messageSnapshot.chainID;
|
|
147
|
+
delete messageSnapshot.messageID;
|
|
148
|
+
let currentDate = new Date();
|
|
149
|
+
|
|
150
|
+
/** @type TiTraceEntry */
|
|
151
|
+
let traceEntry = {
|
|
152
|
+
chainID: message.chainID,
|
|
153
|
+
dispatchEvent: tools.getEnumName( dispatchEventEnum, dispatchEvent ),
|
|
154
|
+
fromAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? source : destination,
|
|
155
|
+
messageID: message.messageID,
|
|
156
|
+
messageSnapshot: messageSnapshot,
|
|
157
|
+
messageState: tools.getEnumName( messageStateEnum, messageState ),
|
|
158
|
+
messageType: tools.getEnumName( messageTypeEnum, messageType ),
|
|
159
|
+
toAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? destination : source,
|
|
160
|
+
traceTimestamp: currentDate.getTime(),
|
|
161
|
+
traceID: tools.getUUID()
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Only write the trace in the general log if this is enabled:
|
|
165
|
+
if ( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_LOG_ENABLED ) === true ) {
|
|
166
|
+
MessageTracer.#createLogEntry( traceEntry, ( dispatchEvent === dispatchEventEnum.FAILED ) ? logger.logSeverity.ERROR : logger.logSeverity.NOTICE );
|
|
181
167
|
}
|
|
182
|
-
} );
|
|
183
168
|
|
|
184
|
-
|
|
169
|
+
// Add the trace entry to the repository in the memory cache:
|
|
170
|
+
cache.instance.arrayAppendJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry, "$.trace" ).then( () => {
|
|
171
|
+
// This will refresh the expiration time for the trace repository on each new record:
|
|
172
|
+
let expiration = config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME );
|
|
173
|
+
return ( expiration > 0 ) ? cache.instance.expireValue( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), expiration ) : expiration;
|
|
174
|
+
} ).catch( ( error ) => {
|
|
175
|
+
// If JSON is unsupported in Redis server, then try to store the trace entry in a Set:
|
|
176
|
+
if ( error.code === exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ) {
|
|
177
|
+
cache.instance.addToSet( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry ).catch( ( error ) => {
|
|
178
|
+
logger.log( `Failed to add message trace entry to the trace repository. While this will not prevent the application from running, it might still be a sign of a more serious problem!`, logger.logSeverity.WARNING, error );
|
|
179
|
+
} );
|
|
180
|
+
} else {
|
|
181
|
+
logger.log( `Failed to add message trace entry to the trace repository. While this will not prevent the application from running, it might still be a sign of a more serious problem!`, logger.logSeverity.WARNING, error );
|
|
182
|
+
}
|
|
183
|
+
} );
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Private interface */
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Used to create a log entry from the trace entry.
|
|
190
|
+
*
|
|
191
|
+
* @method
|
|
192
|
+
* @param {TiTraceEntry} traceEntry The trace entry to log.
|
|
193
|
+
* @param {TiLogSeverity} severity The log entry severity.
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
static #createLogEntry( traceEntry, severity ) {
|
|
197
|
+
logger.log( MessageTracer.#formatLogEntry( traceEntry ), severity, traceEntry );
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Used to format trace entry into log-suitable string.
|
|
202
|
+
*
|
|
203
|
+
* @method
|
|
204
|
+
* @param {TiTraceEntry} traceEntry
|
|
205
|
+
* @return {string} Prepared trace info.
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
static #formatLogEntry( traceEntry ) {
|
|
209
|
+
return `Message(${ traceEntry.chainID || traceEntry.messageID }) Trace: '${ traceEntry.messageType } ${ traceEntry.dispatchEvent } ${ traceEntry.messageState }' From: '${ traceEntry.fromAddress }' To: '${ traceEntry.toAddress }'`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Used to obscure sensitive data in the message, remove the payload, and return a snapshot.
|
|
214
|
+
*
|
|
215
|
+
* @method
|
|
216
|
+
* @param {Message} message
|
|
217
|
+
* @returns {Message}
|
|
218
|
+
* @private
|
|
219
|
+
*/
|
|
220
|
+
static #obscureSensitiveData( message ) {
|
|
221
|
+
/** @type Message */
|
|
222
|
+
let messageSnapshot = tools.parseJSON( _.replace( tools.stringifyJSON( message ), /("\w*?pin\w*?"|"\w*?pass\w*?"|"\w*?otp\w*?"):"(.*?)"/gmi, "\"SENSITIVE_PROPERTY\":\"OBSCURED_BY_SYSTEM\"" ) );
|
|
223
|
+
delete messageSnapshot.payload;
|
|
224
|
+
return messageSnapshot;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const instance = new MessageTracer();
|
|
230
|
+
module.exports.instance = Object.freeze( instance );
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
|
|
3
|
-
* Copyright © 2021-
|
|
3
|
+
* Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
4
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
5
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
6
|
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -95,7 +95,7 @@ class ServiceCaller extends MessageObserver {
|
|
|
95
95
|
this.#findServiceInRegistry( serviceAddress ).then( () => {
|
|
96
96
|
return this.#prepareServiceCall( serviceAddress, serviceParams, serviceExecContext );
|
|
97
97
|
} ).then( ( serviceCall ) => {
|
|
98
|
-
return messageDispatcher.sendRequest( serviceCall );
|
|
98
|
+
return messageDispatcher.instance.sendRequest( serviceCall );
|
|
99
99
|
} ).then( ( messageID ) => {
|
|
100
100
|
this.#addTaskHandler( messageID, ( serviceCall ) => {
|
|
101
101
|
this.#completeServiceCall( serviceCall ).then( ( serviceCall ) => {
|
|
@@ -187,7 +187,7 @@ class ServiceCaller extends MessageObserver {
|
|
|
187
187
|
#findServiceInRegistry( serviceAddress ) {
|
|
188
188
|
return new Promise( ( resolve, reject ) => {
|
|
189
189
|
let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + serviceAddress.serviceDomainName;
|
|
190
|
-
cache.isSetMember( serviceCatalog, serviceAddress.serviceAlias ).then( ( result ) => {
|
|
190
|
+
cache.instance.isSetMember( serviceCatalog, serviceAddress.serviceAlias ).then( ( result ) => {
|
|
191
191
|
if ( result === true ) {
|
|
192
192
|
resolve();
|
|
193
193
|
} else {
|
|
@@ -210,7 +210,7 @@ class ServiceCaller extends MessageObserver {
|
|
|
210
210
|
* @private
|
|
211
211
|
*/
|
|
212
212
|
#prepareServiceCall( serviceAddress, serviceParams, serviceExecContext ) {
|
|
213
|
-
return new Promise( ( resolve
|
|
213
|
+
return new Promise( ( resolve ) => {
|
|
214
214
|
const ServiceInstance = require( "#service-instance" );
|
|
215
215
|
|
|
216
216
|
// assemble the new service call:
|
|
@@ -258,7 +258,7 @@ class ServiceCaller extends MessageObserver {
|
|
|
258
258
|
* @private
|
|
259
259
|
*/
|
|
260
260
|
#completeServiceCall( serviceCall ) {
|
|
261
|
-
return new Promise( ( resolve
|
|
261
|
+
return new Promise( ( resolve ) => {
|
|
262
262
|
serviceCall.finishedOn = Date.now();
|
|
263
263
|
serviceCall.executionTime = serviceCall.finishedOn - serviceCall.createdOn;
|
|
264
264
|
serviceCall.isCompleted = true;
|
|
@@ -306,4 +306,4 @@ class ServiceCaller extends MessageObserver {
|
|
|
306
306
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
module.exports = ServiceCaller;
|
|
309
|
+
module.exports = ServiceCaller;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
|
|
3
|
-
* Copyright © 2021-
|
|
3
|
+
* Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
4
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
5
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
6
|
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -65,7 +65,7 @@ class ServiceConsumer extends ServiceInstance {
|
|
|
65
65
|
this.#serviceCaller = new ServiceCaller();
|
|
66
66
|
|
|
67
67
|
super.onStart().then( () => {
|
|
68
|
-
messageDispatcher.addMessageObserverResponsesIn( this.#serviceCaller );
|
|
68
|
+
messageDispatcher.instance.addMessageObserverResponsesIn( this.#serviceCaller );
|
|
69
69
|
resolve();
|
|
70
70
|
} ).catch( ( error ) => {
|
|
71
71
|
reject( exceptions.raise( error ) );
|
|
@@ -128,4 +128,4 @@ class ServiceConsumer extends ServiceInstance {
|
|
|
128
128
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
module.exports = ServiceConsumer;
|
|
131
|
+
module.exports = ServiceConsumer;
|
|
@@ -58,6 +58,8 @@ class ServiceExecutor extends MessageObserver {
|
|
|
58
58
|
#serviceInterface = {};
|
|
59
59
|
/** @type VerifyAccessMethod */
|
|
60
60
|
#verifyAccess;
|
|
61
|
+
#registrationTasks = {};
|
|
62
|
+
#registrationRetryInterval = 500;
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* @constructor
|
|
@@ -65,11 +67,12 @@ class ServiceExecutor extends MessageObserver {
|
|
|
65
67
|
constructor() {
|
|
66
68
|
super();
|
|
67
69
|
|
|
70
|
+
// Setup a default empty verify access method:
|
|
68
71
|
this.#verifyAccess = () => {
|
|
69
72
|
return Promise.resolve();
|
|
70
73
|
};
|
|
71
74
|
|
|
72
|
-
cache.addConnectionObserver( this );
|
|
75
|
+
cache.instance.addConnectionObserver( this );
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
/* Public interface */
|
|
@@ -81,7 +84,9 @@ class ServiceExecutor extends MessageObserver {
|
|
|
81
84
|
* @returns {ServiceInterface}
|
|
82
85
|
* @public
|
|
83
86
|
*/
|
|
84
|
-
get serviceInterface() {
|
|
87
|
+
get serviceInterface() {
|
|
88
|
+
return this.#serviceInterface;
|
|
89
|
+
}
|
|
85
90
|
|
|
86
91
|
/**
|
|
87
92
|
*
|
|
@@ -94,7 +99,7 @@ class ServiceExecutor extends MessageObserver {
|
|
|
94
99
|
*/
|
|
95
100
|
onMessage( identifier, message ) {
|
|
96
101
|
this.#processServiceCall( message ).then( ( serviceCall ) => {
|
|
97
|
-
return messageDispatcher.sendResponse( serviceCall );
|
|
102
|
+
return messageDispatcher.instance.sendResponse( serviceCall );
|
|
98
103
|
} ).catch( ( error ) => {
|
|
99
104
|
logger.log( `Failed to send service call response after processing! Service call ID was: '${ message.messageID }'`, logger.logSeverity.ERROR, error );
|
|
100
105
|
} );
|
|
@@ -123,17 +128,15 @@ class ServiceExecutor extends MessageObserver {
|
|
|
123
128
|
onConnectionRecovered( identifier ) {
|
|
124
129
|
super.onConnectionRecovered( identifier );
|
|
125
130
|
|
|
126
|
-
if ( identifier !== cache.connectionIdentifier ) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
} );
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
//TODO: Retry service registration after 0.5 seconds.
|
|
136
|
-
}
|
|
131
|
+
if ( identifier !== cache.instance.connectionIdentifier ) {
|
|
132
|
+
let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + ServiceInstance.serviceDomainName;
|
|
133
|
+
_.forOwn( this.#serviceInterface, ( versions, serviceAlias ) => {
|
|
134
|
+
if ( !this.#registrationTasks[ serviceAlias ] ) {
|
|
135
|
+
this.#registrationTasks[ serviceAlias ] = setInterval( () => {
|
|
136
|
+
this.#registerServiceToCatalog( serviceCatalog, serviceAlias );
|
|
137
|
+
}, this.#registrationRetryInterval );
|
|
138
|
+
}
|
|
139
|
+
} );
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
|
|
@@ -200,6 +203,25 @@ class ServiceExecutor extends MessageObserver {
|
|
|
200
203
|
};
|
|
201
204
|
}
|
|
202
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Used to register a service in the service registry.
|
|
208
|
+
*
|
|
209
|
+
* @method
|
|
210
|
+
* @param {string} serviceCatalog
|
|
211
|
+
* @param {string} serviceAlias
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
#registerServiceToCatalog( serviceCatalog, serviceAlias ) {
|
|
215
|
+
if ( cache.instance.isOperational ) {
|
|
216
|
+
cache.instance.addToSet( serviceCatalog, serviceAlias ).then( () => {
|
|
217
|
+
clearTimeout( this.#registrationTasks[ serviceAlias ] );
|
|
218
|
+
delete this.#registrationTasks[ serviceAlias ];
|
|
219
|
+
} ).catch( ( error ) => {
|
|
220
|
+
logger.log( `Record for service '${ serviceAlias }' could not be added to the service registry. Will retry in '${ this.#registrationRetryInterval }' milliseconds.`, logger.logSeverity.ERROR, error );
|
|
221
|
+
} );
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
203
225
|
/**
|
|
204
226
|
* Used to process the actual service call.
|
|
205
227
|
*
|
|
@@ -209,7 +231,7 @@ class ServiceExecutor extends MessageObserver {
|
|
|
209
231
|
* @private
|
|
210
232
|
*/
|
|
211
233
|
#processServiceCall( serviceCall ) {
|
|
212
|
-
return new Promise( ( resolve
|
|
234
|
+
return new Promise( ( resolve ) => {
|
|
213
235
|
this.#verifyAccess( serviceCall.authToken, serviceCall.serviceAddress ).then( () => {
|
|
214
236
|
return this.#identifyService( serviceCall.serviceAddress );
|
|
215
237
|
} ).then( ( serviceHandler ) => {
|
|
@@ -256,4 +278,4 @@ class ServiceExecutor extends MessageObserver {
|
|
|
256
278
|
|
|
257
279
|
}
|
|
258
280
|
|
|
259
|
-
module.exports = ServiceExecutor;
|
|
281
|
+
module.exports = ServiceExecutor;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
|
|
3
|
-
* Copyright © 2021-
|
|
3
|
+
* Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
4
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
5
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
6
|
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -24,8 +24,6 @@ const messageDispatcher = require( "#message-dispatcher" );
|
|
|
24
24
|
* Abstract class used to define a Service Instance behavior.
|
|
25
25
|
* <br/>
|
|
26
26
|
* NOTE: Inherit this to create a module that can be started as a microservice instance.
|
|
27
|
-
* <br/>
|
|
28
|
-
* NOTE: This class does not
|
|
29
27
|
*
|
|
30
28
|
* @class ServiceInstance
|
|
31
29
|
* @abstract
|
|
@@ -46,12 +44,22 @@ class ServiceInstance {
|
|
|
46
44
|
* @param {Object} [serviceConfig={}] The JSON configuration for this service.
|
|
47
45
|
*/
|
|
48
46
|
constructor( serviceDomainName, serviceConfig = {} ) {
|
|
49
|
-
//
|
|
47
|
+
// Ensure this abstract class cannot be instantiated:
|
|
50
48
|
if ( new.target === ServiceInstance ) {
|
|
51
49
|
throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
// Guard against multiple instances in a single process (not supported):
|
|
53
|
+
if ( ServiceInstance.#instanceID && ServiceInstance.#serviceDomainName ) {
|
|
54
|
+
throw exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, {
|
|
55
|
+
details: "Multiple ServiceInstance initializations per process are not supported."
|
|
56
|
+
} );
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Ensure uniform 'ti-' prefix even if env is missing or custom starter script is used:
|
|
60
|
+
const envID = process.env.TI_INSTANCE_ID;
|
|
61
|
+
ServiceInstance.#instanceID = ( envID && String( envID ).startsWith( "ti-" ) ) ? envID : ( "ti-" + ( envID || tools.getUUID() ) );
|
|
62
|
+
|
|
55
63
|
ServiceInstance.#serviceDomainName = serviceDomainName;
|
|
56
64
|
this.#serviceConfig = ( _.isObjectLike( serviceConfig ) ) ? serviceConfig : { services: [] };
|
|
57
65
|
}
|
|
@@ -65,7 +73,9 @@ class ServiceInstance {
|
|
|
65
73
|
* @returns {string}
|
|
66
74
|
* @public
|
|
67
75
|
*/
|
|
68
|
-
static get instanceID() {
|
|
76
|
+
static get instanceID() {
|
|
77
|
+
return ServiceInstance.#instanceID;
|
|
78
|
+
}
|
|
69
79
|
|
|
70
80
|
/**
|
|
71
81
|
* Property returning the current service domain name.
|
|
@@ -74,7 +84,9 @@ class ServiceInstance {
|
|
|
74
84
|
* @returns {string}
|
|
75
85
|
* @public
|
|
76
86
|
*/
|
|
77
|
-
static get serviceDomainName() {
|
|
87
|
+
static get serviceDomainName() {
|
|
88
|
+
return ServiceInstance.#serviceDomainName;
|
|
89
|
+
}
|
|
78
90
|
|
|
79
91
|
/**
|
|
80
92
|
* Property to indicate that this and every child class is a {@link ServiceInstance}.
|
|
@@ -83,7 +95,9 @@ class ServiceInstance {
|
|
|
83
95
|
* @returns {boolean}
|
|
84
96
|
* @public
|
|
85
97
|
*/
|
|
86
|
-
get isServiceInstance() {
|
|
98
|
+
get isServiceInstance() {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
87
101
|
|
|
88
102
|
/**
|
|
89
103
|
* Property returning the service configuration JSON.
|
|
@@ -92,7 +106,9 @@ class ServiceInstance {
|
|
|
92
106
|
* @returns {ServiceConfiguration}
|
|
93
107
|
* @public
|
|
94
108
|
*/
|
|
95
|
-
get serviceConfig() {
|
|
109
|
+
get serviceConfig() {
|
|
110
|
+
return this.#serviceConfig;
|
|
111
|
+
}
|
|
96
112
|
|
|
97
113
|
/**
|
|
98
114
|
* Initializes the instance.
|
|
@@ -140,8 +156,7 @@ class ServiceInstance {
|
|
|
140
156
|
|
|
141
157
|
let configureInbound = ( this instanceof ServiceProvider );
|
|
142
158
|
let configureOutbound = ( this instanceof ServiceConsumer );
|
|
143
|
-
|
|
144
|
-
messageDispatcher.initialize( new DefaultMessageExchange( ServiceInstance.instanceID, ServiceInstance.serviceDomainName ), configureInbound, configureOutbound ).then( () => {
|
|
159
|
+
messageDispatcher.instance.initialize( new DefaultMessageExchange( ServiceInstance.instanceID, ServiceInstance.serviceDomainName ), configureInbound, configureOutbound ).then( () => {
|
|
145
160
|
resolve();
|
|
146
161
|
} ).catch( ( error ) => {
|
|
147
162
|
reject( exceptions.raise( error ) );
|
|
@@ -160,6 +175,8 @@ class ServiceInstance {
|
|
|
160
175
|
return new Promise( ( resolve, reject ) => {
|
|
161
176
|
this.#preStop().then( () => {
|
|
162
177
|
return this.onStop();
|
|
178
|
+
} ).then( () => {
|
|
179
|
+
return cache.instance.shutDown();
|
|
163
180
|
} ).then( () => {
|
|
164
181
|
return this.#postStop();
|
|
165
182
|
} ).then( () => {
|
|
@@ -175,7 +192,7 @@ class ServiceInstance {
|
|
|
175
192
|
* <br/>
|
|
176
193
|
* NOTE: This method will be invoked automatically.
|
|
177
194
|
* <br/>
|
|
178
|
-
* NOTE: If you need to add more onStop logic you can override this method but make sure to call it in the
|
|
195
|
+
* NOTE: If you need to add more onStop logic, you can override this method but make sure to call it in the
|
|
179
196
|
* overriding method using: super.onStop()
|
|
180
197
|
*
|
|
181
198
|
* @method
|
|
@@ -185,7 +202,7 @@ class ServiceInstance {
|
|
|
185
202
|
*/
|
|
186
203
|
onStop() {
|
|
187
204
|
return new Promise( ( resolve, reject ) => {
|
|
188
|
-
messageDispatcher.shutDown().then( () => {
|
|
205
|
+
messageDispatcher.instance.shutDown().then( () => {
|
|
189
206
|
resolve();
|
|
190
207
|
} ).catch( ( error ) => {
|
|
191
208
|
reject( exceptions.raise( error ) );
|
|
@@ -205,9 +222,9 @@ class ServiceInstance {
|
|
|
205
222
|
* @public
|
|
206
223
|
*/
|
|
207
224
|
reportHealthy() {
|
|
208
|
-
if ( cache.isOperational ) {
|
|
225
|
+
if ( cache.instance.isOperational ) {
|
|
209
226
|
let timestamp = new Date();
|
|
210
|
-
cache.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).catch( ( error ) => {
|
|
227
|
+
cache.instance.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).catch( ( error ) => {
|
|
211
228
|
logger.log( `Error while trying to report for health check from '${ ServiceInstance.instanceID }'!`, logger.logSeverity.WARNING, error );
|
|
212
229
|
} );
|
|
213
230
|
}
|
|
@@ -225,8 +242,8 @@ class ServiceInstance {
|
|
|
225
242
|
* @private
|
|
226
243
|
*/
|
|
227
244
|
#preStart() {
|
|
228
|
-
return new Promise( ( resolve
|
|
229
|
-
this.#serviceHealthCheck = config.getSetting( config.setting.SERVICE_HEALTH_CHECK_ADDRESS ) +
|
|
245
|
+
return new Promise( ( resolve ) => {
|
|
246
|
+
this.#serviceHealthCheck = config.getSetting( config.setting.SERVICE_HEALTH_CHECK_ADDRESS ) + ServiceInstance.serviceDomainName + ":" + ServiceInstance.instanceID;
|
|
230
247
|
resolve();
|
|
231
248
|
} );
|
|
232
249
|
}
|
|
@@ -241,8 +258,8 @@ class ServiceInstance {
|
|
|
241
258
|
* @private
|
|
242
259
|
*/
|
|
243
260
|
#postStart() {
|
|
244
|
-
return new Promise( ( resolve
|
|
245
|
-
//
|
|
261
|
+
return new Promise( ( resolve ) => {
|
|
262
|
+
// Schedule regular health check:
|
|
246
263
|
this.#reportHealthyJob = schedule.scheduleJob( config.getSetting( config.setting.SERVICE_HEALTH_CHECK_INTERVAL ), () => {
|
|
247
264
|
this.reportHealthy();
|
|
248
265
|
} );
|
|
@@ -266,11 +283,10 @@ class ServiceInstance {
|
|
|
266
283
|
* @private
|
|
267
284
|
*/
|
|
268
285
|
#preStop() {
|
|
269
|
-
return new Promise( ( resolve
|
|
286
|
+
return new Promise( ( resolve ) => {
|
|
270
287
|
if ( this.#reportHealthyJob ) {
|
|
271
288
|
this.#reportHealthyJob.cancel();
|
|
272
289
|
}
|
|
273
|
-
|
|
274
290
|
resolve();
|
|
275
291
|
} );
|
|
276
292
|
}
|
|
@@ -285,13 +301,12 @@ class ServiceInstance {
|
|
|
285
301
|
* @private
|
|
286
302
|
*/
|
|
287
303
|
#postStop() {
|
|
288
|
-
return new Promise( ( resolve
|
|
304
|
+
return new Promise( ( resolve ) => {
|
|
289
305
|
logger.log( `Instance '${ ServiceInstance.instanceID }' shut down successfully.`, logger.logSeverity.NOTICE );
|
|
290
|
-
|
|
291
306
|
resolve();
|
|
292
307
|
} );
|
|
293
308
|
}
|
|
294
309
|
|
|
295
310
|
}
|
|
296
311
|
|
|
297
|
-
module.exports = ServiceInstance;
|
|
312
|
+
module.exports = ServiceInstance;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
|
|
3
|
-
* Copyright © 2021-
|
|
3
|
+
* Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
4
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
5
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
6
|
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -74,7 +74,7 @@ class ServiceProvider extends ServiceConsumer {
|
|
|
74
74
|
let serviceDefinitions = this.serviceConfig.services;
|
|
75
75
|
return this.registerServices( serviceDefinitions );
|
|
76
76
|
} ).then( () => {
|
|
77
|
-
messageDispatcher.addMessageObserverRequestsIn( this.#serviceExecutor );
|
|
77
|
+
messageDispatcher.instance.addMessageObserverRequestsIn( this.#serviceExecutor );
|
|
78
78
|
resolve();
|
|
79
79
|
} ).catch( ( error ) => {
|
|
80
80
|
reject( exceptions.raise( error ) );
|
|
@@ -203,10 +203,10 @@ class ServiceProvider extends ServiceConsumer {
|
|
|
203
203
|
// If this happens, a corresponding log entry will be created but the loading process will continue. Therefore, the following
|
|
204
204
|
// promise will always resolve (unless a programming error occurs in it of course).
|
|
205
205
|
let registrationPromise = ( serviceDefinition, defaultServiceHandler ) => {
|
|
206
|
-
return new Promise( ( resolve
|
|
206
|
+
return new Promise( ( resolve ) => {
|
|
207
207
|
this.registerService( serviceDefinition, defaultServiceHandler ).then( () => {
|
|
208
208
|
resolve( true );
|
|
209
|
-
} ).catch( (
|
|
209
|
+
} ).catch( () => {
|
|
210
210
|
resolve( false );
|
|
211
211
|
} );
|
|
212
212
|
} );
|
|
@@ -244,4 +244,4 @@ class ServiceProvider extends ServiceConsumer {
|
|
|
244
244
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
module.exports = ServiceProvider;
|
|
247
|
+
module.exports = ServiceProvider;
|