@ti-engine/core 1.1.5 → 1.1.9
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 +39 -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 +31 -4
- 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 +46 -28
- package/components/service-provider.js +5 -5
- package/integrations/gcloud-integration.js +3 -2
- package/integrations/redis-integration.js +257 -138
- package/package.json +6 -6
- package/testRedis.js +22 -0
- package/utils/cache.js +44 -14
- 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.
|
|
@@ -134,14 +150,16 @@ class ServiceInstance {
|
|
|
134
150
|
*/
|
|
135
151
|
onStart() {
|
|
136
152
|
return new Promise( ( resolve, reject ) => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
cache.instance.initialize().then( () => {
|
|
154
|
+
const DefaultMessageExchange = require( "#default-message-exchange" );
|
|
155
|
+
const ServiceProvider = require( "#service-provider" );
|
|
156
|
+
const ServiceConsumer = require( "#service-consumer" );
|
|
140
157
|
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
let configureInbound = ( this instanceof ServiceProvider );
|
|
159
|
+
let configureOutbound = ( this instanceof ServiceConsumer );
|
|
143
160
|
|
|
144
|
-
|
|
161
|
+
return messageDispatcher.instance.initialize( new DefaultMessageExchange( ServiceInstance.instanceID, ServiceInstance.serviceDomainName ), configureInbound, configureOutbound );
|
|
162
|
+
} ).then( () => {
|
|
145
163
|
resolve();
|
|
146
164
|
} ).catch( ( error ) => {
|
|
147
165
|
reject( exceptions.raise( error ) );
|
|
@@ -160,6 +178,8 @@ class ServiceInstance {
|
|
|
160
178
|
return new Promise( ( resolve, reject ) => {
|
|
161
179
|
this.#preStop().then( () => {
|
|
162
180
|
return this.onStop();
|
|
181
|
+
} ).then( () => {
|
|
182
|
+
return cache.instance.shutDown();
|
|
163
183
|
} ).then( () => {
|
|
164
184
|
return this.#postStop();
|
|
165
185
|
} ).then( () => {
|
|
@@ -175,7 +195,7 @@ class ServiceInstance {
|
|
|
175
195
|
* <br/>
|
|
176
196
|
* NOTE: This method will be invoked automatically.
|
|
177
197
|
* <br/>
|
|
178
|
-
* NOTE: If you need to add more onStop logic you can override this method but make sure to call it in the
|
|
198
|
+
* NOTE: If you need to add more onStop logic, you can override this method but make sure to call it in the
|
|
179
199
|
* overriding method using: super.onStop()
|
|
180
200
|
*
|
|
181
201
|
* @method
|
|
@@ -185,7 +205,7 @@ class ServiceInstance {
|
|
|
185
205
|
*/
|
|
186
206
|
onStop() {
|
|
187
207
|
return new Promise( ( resolve, reject ) => {
|
|
188
|
-
messageDispatcher.shutDown().then( () => {
|
|
208
|
+
messageDispatcher.instance.shutDown().then( () => {
|
|
189
209
|
resolve();
|
|
190
210
|
} ).catch( ( error ) => {
|
|
191
211
|
reject( exceptions.raise( error ) );
|
|
@@ -205,9 +225,9 @@ class ServiceInstance {
|
|
|
205
225
|
* @public
|
|
206
226
|
*/
|
|
207
227
|
reportHealthy() {
|
|
208
|
-
if ( cache.isOperational ) {
|
|
228
|
+
if ( cache.instance.isOperational ) {
|
|
209
229
|
let timestamp = new Date();
|
|
210
|
-
cache.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).catch( ( error ) => {
|
|
230
|
+
cache.instance.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).catch( ( error ) => {
|
|
211
231
|
logger.log( `Error while trying to report for health check from '${ ServiceInstance.instanceID }'!`, logger.logSeverity.WARNING, error );
|
|
212
232
|
} );
|
|
213
233
|
}
|
|
@@ -225,8 +245,8 @@ class ServiceInstance {
|
|
|
225
245
|
* @private
|
|
226
246
|
*/
|
|
227
247
|
#preStart() {
|
|
228
|
-
return new Promise( ( resolve
|
|
229
|
-
this.#serviceHealthCheck = config.getSetting( config.setting.SERVICE_HEALTH_CHECK_ADDRESS ) +
|
|
248
|
+
return new Promise( ( resolve ) => {
|
|
249
|
+
this.#serviceHealthCheck = config.getSetting( config.setting.SERVICE_HEALTH_CHECK_ADDRESS ) + ServiceInstance.serviceDomainName + ":" + ServiceInstance.instanceID;
|
|
230
250
|
resolve();
|
|
231
251
|
} );
|
|
232
252
|
}
|
|
@@ -241,8 +261,8 @@ class ServiceInstance {
|
|
|
241
261
|
* @private
|
|
242
262
|
*/
|
|
243
263
|
#postStart() {
|
|
244
|
-
return new Promise( ( resolve
|
|
245
|
-
//
|
|
264
|
+
return new Promise( ( resolve ) => {
|
|
265
|
+
// Schedule regular health check:
|
|
246
266
|
this.#reportHealthyJob = schedule.scheduleJob( config.getSetting( config.setting.SERVICE_HEALTH_CHECK_INTERVAL ), () => {
|
|
247
267
|
this.reportHealthy();
|
|
248
268
|
} );
|
|
@@ -266,11 +286,10 @@ class ServiceInstance {
|
|
|
266
286
|
* @private
|
|
267
287
|
*/
|
|
268
288
|
#preStop() {
|
|
269
|
-
return new Promise( ( resolve
|
|
289
|
+
return new Promise( ( resolve ) => {
|
|
270
290
|
if ( this.#reportHealthyJob ) {
|
|
271
291
|
this.#reportHealthyJob.cancel();
|
|
272
292
|
}
|
|
273
|
-
|
|
274
293
|
resolve();
|
|
275
294
|
} );
|
|
276
295
|
}
|
|
@@ -285,13 +304,12 @@ class ServiceInstance {
|
|
|
285
304
|
* @private
|
|
286
305
|
*/
|
|
287
306
|
#postStop() {
|
|
288
|
-
return new Promise( ( resolve
|
|
307
|
+
return new Promise( ( resolve ) => {
|
|
289
308
|
logger.log( `Instance '${ ServiceInstance.instanceID }' shut down successfully.`, logger.logSeverity.NOTICE );
|
|
290
|
-
|
|
291
309
|
resolve();
|
|
292
310
|
} );
|
|
293
311
|
}
|
|
294
312
|
|
|
295
313
|
}
|
|
296
314
|
|
|
297
|
-
module.exports = ServiceInstance;
|
|
315
|
+
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;
|