@ti-engine/core 1.7.1 → 1.8.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/CHANGELOG.md +393 -377
- package/LICENSE.md +321 -321
- package/README.md +599 -597
- package/bin/localization/labels.json +122 -122
- package/bin/settings.json +41 -41
- package/bin/start-instance.js +164 -164
- package/components/auditing.js +191 -191
- package/components/connection-observer.js +72 -72
- package/components/definitions.types.js +248 -248
- package/components/exchange/default/default-message-exchange.js +136 -136
- package/components/exchange/default/default-message-receiver.js +101 -101
- package/components/exchange/default/default-message-sender.js +100 -100
- package/components/exchange/message-dispatcher.js +168 -168
- package/components/exchange/message-exchange.js +449 -449
- package/components/exchange/message-handler.js +235 -235
- package/components/exchange/message-memory-cache.js +190 -190
- package/components/exchange/message-observer.js +126 -126
- package/components/exchange/message-receiver.js +181 -181
- package/components/exchange/message-sender.js +143 -143
- package/components/exchange/message-tracer.js +212 -212
- package/components/service-caller.js +370 -370
- package/components/service-consumer.js +131 -131
- package/components/service-executor.js +278 -278
- package/components/service-instance.js +316 -316
- package/components/service-provider.js +251 -251
- package/integrations/redis-integration.js +591 -591
- package/package.json +89 -89
- package/utils/cache.js +772 -772
- package/utils/config.js +103 -103
- package/utils/exceptions.js +368 -368
- package/utils/localization.js +298 -298
- package/utils/logger.js +82 -82
- package/utils/tools.js +632 -632
package/README.md
CHANGED
|
@@ -1,598 +1,600 @@
|
|
|
1
|
-
# ti-engine core
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-

|
|
6
|
-

|
|
7
|
-

|
|
8
|
-
|
|
9
|
-

|
|
10
|
-
|
|
11
|
-
Flexible framework for the creation of microservices with [node.js](https://nodejs.org/).
|
|
12
|
-
|
|
13
|
-
## Introduction
|
|
14
|
-
|
|
15
|
-
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**. The architectural concept of the framework is based on a standard _messaging system_ that allows for certain customization but also provides predictability and traceability of its behavior.
|
|
16
|
-
|
|
17
|
-
## Why ti-engine?
|
|
18
|
-
|
|
19
|
-
The framework is created based on a decade of professional experience with the utilized technologies and architectural approach. Its primary goal is to provide you with a lightweight and flexible solution that can help you quickly build a microservice ecosystem with any degree of size and complexity.
|
|
20
|
-
|
|
21
|
-
This is what you gain by using **ti-engine** in your project:
|
|
22
|
-
|
|
23
|
-
* **Simplicity**: Begin productive work within minutes and get to codding your business logic
|
|
24
|
-
* **Flexibility**: Go as complex as you need to in your implementation
|
|
25
|
-
* **Reliability**: Message exchange between the services is constantly tracked across the entire ecosystem
|
|
26
|
-
* **Security**: Messages are encrypted in transit and cannot be modified by external agents
|
|
27
|
-
* **Scalability**: Serve mullions of requests by multiplying stateless service instances (hardware limitations still apply)
|
|
28
|
-
* **Containerization**: Go with containers from the very start as the framework is designed to work in such an environment
|
|
29
|
-
|
|
30
|
-
These are just some benefits **ti-engine** offers. Get to know it better to find out more ways in which it can help you improve productivity.
|
|
31
|
-
|
|
32
|
-
## Prerequisites & installation
|
|
33
|
-
|
|
34
|
-
Being a messaging system, the **ti-engine** relies on a message broker for the actual exchange of messages between microservice instances. The default implementation of the framework uses [Redis](https://redis.io/) cache; however, you could create your own implementation using something like [Rabbit MQ](https://www.rabbitmq.com/). See the [Advanced topics](#advanced-topics) section of this documentation for guides on how to do this. For now let's focus on the default setup.
|
|
35
|
-
|
|
36
|
-
To run the basic **ti-engine** framework, you will need a couple of things:
|
|
37
|
-
|
|
38
|
-
* A local [node.js installation](https://nodejs.org/en/download/) with a minimum version of **20.12.0** (the `core` package requires `process.loadEnvFile`)
|
|
39
|
-
* A local or remote [Redis cache installation](https://redis.io/download) with a minimum version of **
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
[timestamp]: [instance-id] -
|
|
65
|
-
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client '
|
|
66
|
-
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-
|
|
67
|
-
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-
|
|
68
|
-
[timestamp]: [instance-id] - info -
|
|
69
|
-
[timestamp]: [instance-id] - info -
|
|
70
|
-
[timestamp]: [instance-id] -
|
|
71
|
-
|
|
72
|
-
[timestamp]: [instance-id] -
|
|
73
|
-
» {"
|
|
74
|
-
[timestamp]: [instance-id] - info - Execution of 'Test
|
|
75
|
-
» {"isSuccessful":true,"payload":{"s1Timestamp":[timestamp]
|
|
76
|
-
[timestamp]: [instance-id] - info - Execution of 'Test
|
|
77
|
-
» {"isSuccessful":
|
|
78
|
-
[timestamp]: [instance-id] -
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
The
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
The
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
* `
|
|
124
|
-
* `
|
|
125
|
-
* `
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
participant
|
|
155
|
-
participant
|
|
156
|
-
participant
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
SOut
|
|
163
|
-
SOut->>
|
|
164
|
-
|
|
165
|
-
Redis
|
|
166
|
-
RIn->>Redis:
|
|
167
|
-
Redis-->>RIn: payload
|
|
168
|
-
RIn->>
|
|
169
|
-
RIn
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Caller
|
|
177
|
-
Redis
|
|
178
|
-
Caller->>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
* Class `
|
|
195
|
-
* Class `
|
|
196
|
-
* Class `
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
* It needs to
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
↳
|
|
254
|
-
.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
"
|
|
269
|
-
"
|
|
270
|
-
"
|
|
271
|
-
"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
* `
|
|
297
|
-
* `
|
|
298
|
-
* `
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
* Method `
|
|
309
|
-
* Method
|
|
310
|
-
* Method
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
The file `
|
|
315
|
-
|
|
316
|
-
The
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
} )
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
[timestamp]: [instance-id] -
|
|
398
|
-
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client '
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
[timestamp]: [instance-id] - notice -
|
|
410
|
-
» {"
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
:
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
:
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
:
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
:
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
:
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
:
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
:
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
:
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
:
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
:
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
:
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
:
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
:
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
SERVICE_HEALTH_CHECK_INTERVAL
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
:
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
The
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1
|
+
# ti-engine core
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
Flexible framework for the creation of microservices with [node.js](https://nodejs.org/).
|
|
12
|
+
|
|
13
|
+
## Introduction
|
|
14
|
+
|
|
15
|
+
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**. The architectural concept of the framework is based on a standard _messaging system_ that allows for certain customization but also provides predictability and traceability of its behavior.
|
|
16
|
+
|
|
17
|
+
## Why ti-engine?
|
|
18
|
+
|
|
19
|
+
The framework is created based on a decade of professional experience with the utilized technologies and architectural approach. Its primary goal is to provide you with a lightweight and flexible solution that can help you quickly build a microservice ecosystem with any degree of size and complexity.
|
|
20
|
+
|
|
21
|
+
This is what you gain by using **ti-engine** in your project:
|
|
22
|
+
|
|
23
|
+
* **Simplicity**: Begin productive work within minutes and get to codding your business logic
|
|
24
|
+
* **Flexibility**: Go as complex as you need to in your implementation
|
|
25
|
+
* **Reliability**: Message exchange between the services is constantly tracked across the entire ecosystem
|
|
26
|
+
* **Security**: Messages are encrypted in transit and cannot be modified by external agents
|
|
27
|
+
* **Scalability**: Serve mullions of requests by multiplying stateless service instances (hardware limitations still apply)
|
|
28
|
+
* **Containerization**: Go with containers from the very start as the framework is designed to work in such an environment
|
|
29
|
+
|
|
30
|
+
These are just some benefits **ti-engine** offers. Get to know it better to find out more ways in which it can help you improve productivity.
|
|
31
|
+
|
|
32
|
+
## Prerequisites & installation
|
|
33
|
+
|
|
34
|
+
Being a messaging system, the **ti-engine** relies on a message broker for the actual exchange of messages between microservice instances. The default implementation of the framework uses [Redis](https://redis.io/) cache; however, you could create your own implementation using something like [Rabbit MQ](https://www.rabbitmq.com/). See the [Advanced topics](#advanced-topics) section of this documentation for guides on how to do this. For now let's focus on the default setup.
|
|
35
|
+
|
|
36
|
+
To run the basic **ti-engine** framework, you will need a couple of things:
|
|
37
|
+
|
|
38
|
+
* A local [node.js installation](https://nodejs.org/en/download/) with a minimum version of **20.12.0** (the `core` package requires `process.loadEnvFile`)
|
|
39
|
+
* A local or remote [Redis cache installation](https://redis.io/download) with a minimum version of **6.0**
|
|
40
|
+
|
|
41
|
+
> The Redis floor moved from 5.0.14 to 6.0 in `core` 1.8.0, when `ioredis` 6 made RESP3 its default wire protocol. RESP3 arrived in Redis 6.0, and the client negotiates it with a `HELLO 3` on connect — an older server rejects that and the connection fails outright rather than falling back, so this is a hard requirement rather than a recommendation. Replies are unaffected: `ioredis` maps every RESP3 reply back to the same JavaScript value RESP2 produced, so nothing in your service code changes.
|
|
42
|
+
|
|
43
|
+
If you are working under Windows 10+ OS and you need to install Redis, take a look at this [guide](https://redis.com/blog/redis-on-windows-10/). You could also use [Redis Cloud](https://app.redislabs.com/) for development as it offers a free basic account.
|
|
44
|
+
|
|
45
|
+
To get the framework itself, use the command `npm install @ti-engine/core`. And to include it directly in your package.json dependencies execute `npm install @ti-engine/core --save-prod`.
|
|
46
|
+
|
|
47
|
+
## Getting started
|
|
48
|
+
|
|
49
|
+
To start using the **ti-engine**, you will have to make sure that all prerequisites are available and operational. However, before we get to the fun part, you need to also consider a couple of crucial things while working with this framework:
|
|
50
|
+
|
|
51
|
+
1. The runtime configuration of the framework can be customized using ENV variables. These can be provided to node.js in all the standard ways, but there is also an option to include an `.env` file.
|
|
52
|
+
2. It loads your framework-related custom scripts and files dynamically, but it always assumes their provided paths are relative to the _current working directory_ (i.e., it uses `process.cwd()` to resolve the file paths). Be mindful of that whenever you declare relative file paths in the various settings.
|
|
53
|
+
|
|
54
|
+
### Running the tester module
|
|
55
|
+
|
|
56
|
+
Once you have everything else ready, you should download the **ti-engine** tester module with the command `npm install @ti-engine/tester`. The tester module packages an example microservice that shows the basic approach for using the framework. To make sure everything is working properly, you should try and start the tester service:
|
|
57
|
+
|
|
58
|
+
1. Open a command prompt and navigate to the directory of the tester module; it should be something like that:
|
|
59
|
+
`[path to your project]/node_modules/@ti-engine/tester`
|
|
60
|
+
2. Execute the following command `node ../core/bin/start-instance.js`. Keep in mind that the working directory for the node process has to be the one specified in point 1. Otherwise, you'll get errors that certain files cannot be found and loaded. Also, this configuration assumes that you have an unprotected local Redis server running on the default port. If you have a different setup, you can provide the host and port via ENV variables. We'll cover that in the section [Configuring for a remote Redis server](#configuring-for-a-remote-redis-server).
|
|
61
|
+
3. If everything was done properly, you should see the following output:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
[timestamp]: [instance-id] - notice - Starting new instance of type 'ti-tester-service' with instance ID '[instance-id]'.
|
|
65
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'system-cache' and is ready to be used.
|
|
66
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-responses-out' and is ready to be used.
|
|
67
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-requests-in' and is ready to be used.
|
|
68
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-requests-out' and is ready to be used.
|
|
69
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-responses-in' and is ready to be used.
|
|
70
|
+
[timestamp]: [instance-id] - info - Starting service registration process. There is NO default service handler provided.
|
|
71
|
+
[timestamp]: [instance-id] - info - Registration of defined services completed with 2 successful out of 2 total.
|
|
72
|
+
[timestamp]: [instance-id] - notice - Instance '[instance-id]' started successfully.
|
|
73
|
+
» {"nodeVersion":[node-version],"operationMode":[mode]}
|
|
74
|
+
[timestamp]: [instance-id] - info - Execution of 'Test 1: Service call to a simple service without chained services' successful.
|
|
75
|
+
» {"isSuccessful":true,"payload":{"s1Timestamp":[timestamp]}}
|
|
76
|
+
[timestamp]: [instance-id] - info - Execution of 'Test 2: Service call to a simple service with one chained service' successful.
|
|
77
|
+
» {"isSuccessful":true,"payload":{"s1Timestamp":[timestamp],"s2TimestampStart":[timestamp],"s2TimestampEnd":[timestamp]}}
|
|
78
|
+
[timestamp]: [instance-id] - info - Execution of 'Test 3: Service call to a non-existent service' successful.
|
|
79
|
+
» {"isSuccessful":false,"exception":{"exceptionID":[exception-id],"description":"The specified service is not found in the service registry."}}
|
|
80
|
+
[timestamp]: [instance-id] - notice - All service tests completed. Passed 3 out of 3.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Now let's analyze that output. For the sake of completeness, the `[timestamp]` and `[instance-id]` are placeholders of the actual values you'll see there. The timestamps are in UTC and show a date followed by time.
|
|
84
|
+
|
|
85
|
+
At the start of the output log, you can see a `NOTICE` that tells you a couple of important things:
|
|
86
|
+
|
|
87
|
+
* The instance name - in this case `ti-tester-service`. In the terminology of the framework, this is also known as a _service domain name_.
|
|
88
|
+
* The _instance identifier_. It is an uuid string with a `ti-` prefix, that is generated by the framework at process start. It can and will be used to trace the messages during their movement through the microservice ecosystem. But more on that later.
|
|
89
|
+
|
|
90
|
+
The following five `INFO` lines inform you about the successful connections to Redis. Remember, the default configuration assumes that your Redis is running on localhost, requires no password, and uses the default port.
|
|
91
|
+
|
|
92
|
+
Following that come a couple of `INFO` lines that inform you about the microservice interface state. The framework starts with the process of _business services_ registration within the service domain of the microservice `ti-tester-service` and successfully adds two such services. The necessary information for this is read from a JSON config file included in the package. We'll get into more details on what this all means in the section [Creating a microservice](#creating-a-microservice).
|
|
93
|
+
|
|
94
|
+
Once the initialization sequence has completed, the framework informs you that the microservice instance has started successfully with a `NOTICE` entry.
|
|
95
|
+
|
|
96
|
+
NOTE: If the framework encountered an error during initialization, you would see something like this instead:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
[timestamp]: [instance-id] - notice - Starting new instance of type 'ti-tester-service' with instance ID '[instance-id]'.
|
|
100
|
+
[timestamp]: [instance-id] - alert - Error detected in the instance startup script!
|
|
101
|
+
» [information about the error]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Finally, you should see a sequence of test execution statements with their results in JSON format. These are the results of three business service calls that are part of the default tester microservice. The final `NOTICE` should indicate that all three tests out of three have been completed successfully.
|
|
105
|
+
|
|
106
|
+
You can now kill the node process which should show you the following two lines:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
[timestamp]: [instance-id] - notice - [signal-code] event detected in main instance process.
|
|
110
|
+
[timestamp]: [instance-id] - notice - Instance '[instance-id]' shut down successfully.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The framework will always try to capture the shut-down event and log it with the correct `[signal-code]`. This should work even in a container environment, but it might depend on your setup whether the last two entries will reach the logging system or not.
|
|
114
|
+
|
|
115
|
+
The tester module gets its starting configuration from an `.env` file included in the package. You can find more information about it later in the [Creating a microservice](#creating-a-microservice) section.
|
|
116
|
+
|
|
117
|
+
Before moving on, also take a good look at the file `bin/start-instance.js`. It should give you an idea of how the process of starting and stopping a microservice operates. In most cases this file should be enough as a starting script for your **ti-engine** based microservice applications. You can, of course, create your own starting script, but then you'll have to consider all necessary steps to properly handle the microservice instance.
|
|
118
|
+
|
|
119
|
+
### Configuring for a remote Redis server
|
|
120
|
+
|
|
121
|
+
You can configure your connection to a remote Redis server using the following ENV variables:
|
|
122
|
+
|
|
123
|
+
* `TI_MEMORY_CACHE_AUTH_KEY` can be used to provide the Redis password if there is any at all.
|
|
124
|
+
* `TI_MEMORY_CACHE_REDIS_DB` can be used to specify the Redis DB you want to use. Make sure to set the correct number as, for example, Redis Cloud only uses DB `0`.
|
|
125
|
+
* `TI_MEMORY_CACHE_REDIS_HOST` can be used to provide the remote host. This can be an IP or URL depending on your setup.
|
|
126
|
+
* `TI_MEMORY_CACHE_REDIS_PORT` can be used to provide the remote port. By default, Redis uses `6379` however many implementations might use a custom port that needs to be specified in the connection settings.
|
|
127
|
+
* `TI_MEMORY_CACHE_USER` can be used to specify the Redis username if this is supported by your Redis implementation.
|
|
128
|
+
|
|
129
|
+
The easiest way to configure these variables is to edit the `.env` file included in the package. You might have to do that if you want to run the tester module successfully with a remote Redis server.
|
|
130
|
+
|
|
131
|
+
## Architecture
|
|
132
|
+
|
|
133
|
+
The architectural approach for the **ti-engine** is done in _tiers_ with lower tiers being unaware of the tiers above them. The framework prefers a high level of abstraction in all its tiers and provides many options for customization and extension. While the language is JavaScript, the structuring of the framework follows the OOP principles, and you will find a lot of abstract classes and methods that require you to implement them. These are always marked with the `@abstract` annotation but if you happen to miss one, the framework will raise an `E_GEN_ABSTRACT_METHOD_CALL` exception when you try to use it in your solution.
|
|
134
|
+
|
|
135
|
+
There are three general tiers in the **ti-engine**:
|
|
136
|
+
|
|
137
|
+
1. Message exchange
|
|
138
|
+
2. Service domains
|
|
139
|
+
3. Solution implementation
|
|
140
|
+
|
|
141
|
+
See the following sections for more information on each of them.
|
|
142
|
+
|
|
143
|
+
### Tier 1 - Message exchange
|
|
144
|
+
|
|
145
|
+
This is the lowest framework tier, unless we count the actual data objects processed by the framework. As you already know, the foundational **ti-engine** concept is that of a messaging system. Therefore, the first tier provides an abstraction over a chosen message broker (Redis by default). That abstraction makes it easy to switch between message brokers whenever you want to without having to change anything above tier 1. It also provides several bonuses that can speed up your work—message integrity hashing, message tracing, message observers, and others. More details about each of these features will be covered in the section [Using the framework](#using-the-framework).
|
|
146
|
+
|
|
147
|
+
Another important aspect for you to remember is that the message exchange is entirely _asynchronous_. This helps reduce the system load and optimizes the usage of the available resources. Even so, each node.js process can handle a limited load. Therefore, you should plan for running multiple identical senders and receives to scale your solution. But more on that later.
|
|
148
|
+
|
|
149
|
+
The sequence below shows a full service-call round trip through the default Redis exchange. Each message is split into a lightweight **envelope** (metadata, carrying the integrity hash) and a **payload** (the operational data): the payload is parked in a shared Redis hash while only the envelope travels through the queue.
|
|
150
|
+
|
|
151
|
+
```mermaid
|
|
152
|
+
sequenceDiagram
|
|
153
|
+
autonumber
|
|
154
|
+
participant Caller as ServiceConsumer<br/>(ServiceCaller)
|
|
155
|
+
participant SOut as MessageSender<br/>(requests-out)
|
|
156
|
+
participant Redis as Redis<br/>(list queues + payload hash)
|
|
157
|
+
participant RIn as MessageReceiver<br/>(requests-in)
|
|
158
|
+
participant Exec as ServiceProvider<br/>(ServiceExecutor)
|
|
159
|
+
|
|
160
|
+
rect rgb(232, 243, 255)
|
|
161
|
+
Note over Caller,Exec: Request path (blue)
|
|
162
|
+
Caller->>SOut: callService() builds the request envelope
|
|
163
|
+
SOut->>SOut: stamp HMAC-SHA256 hash over the message
|
|
164
|
+
SOut->>Redis: HSET payload into ti:messages:store (field = storeID)
|
|
165
|
+
SOut->>Redis: LPUSH envelope onto ti:messages:pending:{destination}
|
|
166
|
+
RIn->>Redis: BRPOP ti:messages:pending:{domain} (blocks until a message)
|
|
167
|
+
Redis-->>RIn: envelope (payload = storeID)
|
|
168
|
+
RIn->>Redis: HGET + HDEL storeID from ti:messages:store
|
|
169
|
+
Redis-->>RIn: payload, reassembled into the full message
|
|
170
|
+
RIn->>RIn: recompute HMAC, constant-time verify
|
|
171
|
+
RIn->>Exec: deliver the verified message
|
|
172
|
+
Exec->>Exec: resolve handler by alias + version, then run it
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
rect rgb(255, 235, 235)
|
|
176
|
+
Note over Caller,Exec: Response path (red, mirrored route)
|
|
177
|
+
Exec->>Redis: HSET result payload, LPUSH onto ti:messages:processed:{source}:{instanceID}
|
|
178
|
+
Caller->>Redis: BRPOP ti:messages:processed:{source}:{instanceID} (blocks)
|
|
179
|
+
Redis-->>Caller: response envelope, reassembled and verified
|
|
180
|
+
Caller->>Caller: resolve the awaiting Promise with the ServiceCallResult
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
If the recomputed hash does not match the one on the envelope, the receiver rejects the message with `E_SEC_MESSAGE_TAMPERING_DETECTED` instead of delivering it.
|
|
185
|
+
|
|
186
|
+
The same queue can be consumed by _n_ identical receivers, which is how you scale a service domain horizontally. The sender splits each message into an _envelope_ and a _payload_, then stores the payload in the shared cache and enqueues the envelope in the requests (destination) queue. Receivers can subscribe to that queue to fetch enqueued messages and process their contents. During the fetch sequence a receiver assembles the full message by getting the payload from the storage. This is the blue (request) path in the diagram above.
|
|
187
|
+
|
|
188
|
+
After the processing is done, the message payload is modified, and the receiver sends the message back to the original sender using the same mechanism. It again splits the message into an envelope and a payload, stores the payload in the storage, and enqueues the envelope in the sender response (source) queue. The sender will then assemble the message back and process the contained results. This is the red (response) path in the diagram above.
|
|
189
|
+
|
|
190
|
+
In this scenario the framework uses _Redis lists_ as queues for the message envelopes and _Redis hash_ as message payload storage. The splitting between envelope and payload is done to avoid unnecessary transportation of potentially large volumes of operational data between the microservices. Other message brokers might use a slightly different approach, but they should still adhere to the same logical flow.
|
|
191
|
+
|
|
192
|
+
The modules associated with this tier are all located in the `components/exchange/` folder. This is a short list of some terminology used here and in the JDoc inside the sourcecode itself:
|
|
193
|
+
|
|
194
|
+
* Class `Message`: a message is the actual data object processed by the framework. It consists of two parts—an envelope containing service information and a payload containing the actual data to be processed.
|
|
195
|
+
* Class `MessageSender`: a sender a specialized connector that is responsible for sending a message on its way to its destination. It does not handle the actual dispatch and delivery.
|
|
196
|
+
* Class `MessageReceiver`: a receiver is a specialized connector that is responsible for receiving messages at a predefined destination.
|
|
197
|
+
* Class `MessageExchange`: the exchange is the actual message processing engine. It handles sending and receiving messages via preconfigured message senders and message receivers.
|
|
198
|
+
* Class `MessageObserver`: an observer is a custom event listener that can be used to react on message `sent` and `received` events.
|
|
199
|
+
|
|
200
|
+
The exchange classes form the hierarchy below. `MessageHandler` is the shared abstract base that owns the integrity hash; the concrete `Default*` classes are the Redis implementation you replace to swap brokers:
|
|
201
|
+
|
|
202
|
+
```mermaid
|
|
203
|
+
classDiagram
|
|
204
|
+
direction LR
|
|
205
|
+
MessageHandler <|-- MessageSender
|
|
206
|
+
MessageHandler <|-- MessageReceiver
|
|
207
|
+
MessageObserver <|-- MessageExchange
|
|
208
|
+
MessageSender <|-- DefaultMessageSender
|
|
209
|
+
MessageReceiver <|-- DefaultMessageReceiver
|
|
210
|
+
MessageExchange <|-- DefaultMessageExchange
|
|
211
|
+
class MessageHandler {
|
|
212
|
+
+createMessageHash()
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Tier 2 - Service domains
|
|
217
|
+
|
|
218
|
+
This tier focuses on hosting and executing the _business logic_ of your application. It consists of _business services_ that process input data and return the result of the processing as output data. The business services are grouped in _service domains_, which are in turn hosted inside stateless _microservices_ also named _service instances_. All microservices are based on the `ServiceInstance` class, which establishes the basic framework structure and provides the basic functionality for the microservice lifecycle. It should not be used directly, however. Instead, there are two child types of `ServiceInstance` in **ti-engine** that you should use to implement your solution:
|
|
219
|
+
|
|
220
|
+
* Class `ServiceConsumer`: consumers are service instances that can call business services in any connected and available service domain.
|
|
221
|
+
* Class `ServiceProvider`: providers are service instances that host and run a set of business services in a particular service domain. Every service provider is also a service consumer.
|
|
222
|
+
|
|
223
|
+
The various service instances in a solution represent a network of interconnected service domains that contain the business logic of your application. All business services exchange data via _service calls_ using abstract _service addresses_. These service calls are transported from one address in the microservice network to another via the underlying message exchange tier. This, however, is completely transparent to the service instances. In essence, tier 2 does not care about the actual data transportation method or protocol. You could, in fact, completely change the tier 1 approach without having to modify anything in your business logic and business flow.
|
|
224
|
+
|
|
225
|
+
This tier is the place to use any databases, file storages, integrations with other applications, scheduling jobs, and so on. In general, it should focus on executing any granular tasks that are essential to the backbone operation of your application. The business logic here should remain **stateless** and any user context should be provided at runtime to each invoked business service. We'll see more concrete examples for that later in the section [Using the framework](#using-the-framework).
|
|
226
|
+
|
|
227
|
+
### Tier 3 - Solution implementation
|
|
228
|
+
|
|
229
|
+
This tier comprises the actual implementation of your application. Its structure and behavior depend entirely on your vision and business goals. There are still a couple of points that remain constant while using **ti-engine**:
|
|
230
|
+
|
|
231
|
+
* It needs to use the business logic defined in tier 2 by calling the business services.
|
|
232
|
+
* It needs to take care of any type of stateful behavior like user sessions or transactions.
|
|
233
|
+
* It needs to act as the primary interface between users and your application, thus handling access management and user interactions.
|
|
234
|
+
|
|
235
|
+
Depending on the type of software you are building, tier 3 can be an API Gateway, a Web application, backend for a Mobile application, or anything like that.
|
|
236
|
+
|
|
237
|
+
## Creating a microservice
|
|
238
|
+
|
|
239
|
+
Now let's walk through the process of creating a microservice with **ti-engine**. We'll start with analyzing the contents of the tester module. Then we'll proceed with creating a new microservice that can call one of the business services in the default `ti-tester-service`.
|
|
240
|
+
|
|
241
|
+
### Deconstructing the ti-tester microservice
|
|
242
|
+
|
|
243
|
+
If you managed to execute the initial framework test as explained in the [Running the tester module](#running-the-tester-module) section, you should already be familiar with the default ti-tester microservice. Here we'll dissect its contents even further.
|
|
244
|
+
|
|
245
|
+
Let's take a look at the files and file structure first (only relevant items are shown):
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
bin
|
|
249
|
+
↳ services
|
|
250
|
+
↳ v1
|
|
251
|
+
↳ service1.js
|
|
252
|
+
↳ service2.js
|
|
253
|
+
↳ more-labels.json
|
|
254
|
+
↳ tester-service.js
|
|
255
|
+
↳ tester-service.json
|
|
256
|
+
.env
|
|
257
|
+
package.json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
You don't have to follow the exact same folder structure as most of the paths can be defined via ENV parameters and in the configuration file. However, having a good clean structure helps when organizing your work in more complex projects.
|
|
261
|
+
|
|
262
|
+
In the tester module all application files are located inside the `bin` folder. Outside you have only the `package.json` and the `.env` files which can be considered more of a configuration for the node process rather than part of the application itself. Nevertheless, let's start with them:
|
|
263
|
+
|
|
264
|
+
#### package.json contents
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"name": "@ti-engine/tester",
|
|
269
|
+
"version": "[tester module version]",
|
|
270
|
+
"description": "[tester module description]",
|
|
271
|
+
"author": "[tester module author]",
|
|
272
|
+
"license": "GPL-3.0-or-later",
|
|
273
|
+
"dependencies": {
|
|
274
|
+
"@ti-engine/core": "latest"
|
|
275
|
+
},
|
|
276
|
+
"engines": {
|
|
277
|
+
"node": "[min required node version]"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Apart from the standard information properties there are only two important entries here: `"@ti-engine/core": "[min required core version]"` and `"node": "[min required node version]"`. The dependency on the core of the framework is set to `latest`, but as with any other npm library you should set this to a specific version when releasing on production. The minimum node version should also reflect the minimum requirements of your application and can be adjusted accordingly, but it should not go below the minimum version required by the **ti-engine** itself.
|
|
283
|
+
|
|
284
|
+
#### .env contents
|
|
285
|
+
|
|
286
|
+
```text
|
|
287
|
+
TI_INSTANCE_NAME=ti-tester-service
|
|
288
|
+
TI_INSTANCE_CLASS=bin/tester-service.js
|
|
289
|
+
TI_INSTANCE_CONFIG=bin/tester-service.json
|
|
290
|
+
TI_AUDITING_LOG_MIN_LEVEL=200
|
|
291
|
+
TI_LOCALIZATION_LABELS_PATH=bin/more-labels.json
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The ENV initialization file provides the minimal settings for the proper tester microservice operation. The first three are usually _mandatory_ for every microservice you create, while the last two are provided for the needs of the tester demonstration. Let's review them and see what they do:
|
|
295
|
+
|
|
296
|
+
* `TI_INSTANCE_NAME` is the _service domain_ name provided for the microservice. It has to be _unique_ in the context of the microservice ecosystem. If not provided, the framework will attempt to extract this information from the name of the implementation file. That is not a recommended approach, though, as it might cause hard to identify errors later.
|
|
297
|
+
* `TI_INSTANCE_CLASS` specifies the relative path to the implementation of the `ServiceInstance` framework class—in this case a `ServiceProvider`. As stated above, the path is relative to the working directory of the `node.js` process. This variable is mandatory for every microservice you create with the **ti-engine**. If it is not provided, the microservice won't be able to start at all, and you will get an exception.
|
|
298
|
+
* `TI_INSTANCE_CONFIG` specifies the relative path to the configuration data for the microservice. We'll delve into the specific settings below. Technically, you can omit this variable, and the microservice will still start successfully with an empty configuration. There are very few cases, however, where this would be applicable.
|
|
299
|
+
* `TI_AUDITING_LOG_MIN_LEVEL` specifies the minimum log level that should be sent to the log output stream. With a setting of `200` (corresponding to `INFO`) we filter out all `DEFAULT (0)` and `DEBUG (100)` entries as we don't need them for the tester microservice.
|
|
300
|
+
* `TI_LOCALIZATION_LABELS_PATH` specifies the relative path to the additional localization labels file. This is optional and can be omitted if you don't need custom labels for your microservice.
|
|
301
|
+
|
|
302
|
+
You can find the full list of available ENV variables and what they do below in [Using the framework](#using-the-framework) section.
|
|
303
|
+
|
|
304
|
+
#### Application-specific files
|
|
305
|
+
|
|
306
|
+
Now let's look inside the `bin` folder. The two files there are the ones specified in the `.env` file. The `tester-service.js` contains the implementation of the `ServiceProvider` class. It has just a few methods that contain its behavior:
|
|
307
|
+
|
|
308
|
+
* Method `onStart` overrides the base one from the parent class and is invoked automatically by the framework once initialization of the microservice is complete. In this case the method invokes the execution of the test sequence just once, and then the microservice remains dormant but active.
|
|
309
|
+
* Method `reportHealthy` overrides but essentially just calls the same base method. Its only purpose here is to draw your attention to its existence and the possibility to implement your own health status reporting functionality if you want.
|
|
310
|
+
* Method `verifyAccess` also overrides the base method and shows a very basic example of how to implement user access verification on business service level. Each time a service in the `ti-tester-service` is called, the framework will trigger this method and will only allow processing if there is a non-undefined value inside the `authToken` variable.
|
|
311
|
+
* Method `#executeTests` is a custom private method that contains the test execution sequence itself. It is called by the `onStart` method just once per microservice start.
|
|
312
|
+
* Method `#assertService` is an assertion wrapper around a standard service call request. Inside it, you can see how a business service is invoked and how the results should be handled.
|
|
313
|
+
|
|
314
|
+
The file `tester-service.json` contains framework configuration for the tester microservice. It will be automatically loaded inside the `ServiceInstance` class during initialization and will already be available inside the `onStart` method for usage. In this case the configuration is related to the two business services that will be provided by the microservice. More on this topic will be covered in the section [Using the framework](#using-the-framework). For now pay attention to the `serviceFile` parameter and that it once again provides a relative path to the actual file containing the business logic.
|
|
315
|
+
|
|
316
|
+
The file `more-labels.json` contains one additional localization label for the tester microservice and is only used to demonstrate how to add custom labels to the microservice. For more information on localization, see the section [Localization](#localization).
|
|
317
|
+
|
|
318
|
+
The final two files are located in `bin/services/v1/` folder. They contain the definitions and business logic of the two business services that will be loaded at initialization time and provided by the tester microservice. In this case `service1.js` contains a basic service that returns the current timestamp. The `service2.js` file contains a slightly more complex example of a service calling another service (in this case `service1`) before also returning two timestamps taken at the beginning and end of execution. Pay attention to the way the methods inside are declared and exported as this is the proper way to do this while using the **ti-engine** framework. Once again, we'll delve into the details and specifics of creating business services in the section [Using the framework](#using-the-framework).
|
|
319
|
+
|
|
320
|
+
### Creating your own microservice
|
|
321
|
+
|
|
322
|
+
Now that we've seen the structure of the tester microservice, let's create a new one and make it call `service2`. Let's use the same file structure as for the tester. Create a folder `my-service` and in it create a `package.json` file. Make sure to include a dependency to `"@ti-engine/core": "latest"` in it. After that create a `.env` file and add the following entries in it:
|
|
323
|
+
|
|
324
|
+
```text
|
|
325
|
+
TI_INSTANCE_NAME=my-service
|
|
326
|
+
TI_INSTANCE_CLASS=bin/my-service.js
|
|
327
|
+
TI_INSTANCE_CONFIG=bin/my-service.json
|
|
328
|
+
TI_AUDITING_LOG_MIN_LEVEL=200
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Create the `my-service.json` file in a `my-service/bin/` folder. For now enter an empty JSON object `{}` inside and leave at that. Next create the `my-service.js` file in the same location and let's start entering some code in it. Since this will be a microservice that only uses other's services, we'll inherit the `ServiceConsumer` class instead:
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
const ServiceConsumer = require( "@ti-engine/core/service-consumer" );
|
|
335
|
+
|
|
336
|
+
class MyService extends ServiceConsumer {}
|
|
337
|
+
|
|
338
|
+
module.exports = MyService;
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Remember to also export your new class at the end, otherwise the framework won't be able to initialize it.
|
|
342
|
+
|
|
343
|
+
To make use of all the inherited features of the `ServiceConsumer` class we have to add a `constructor` that invokes the base one in the parent class:
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
const ServiceConsumer = require( "@ti-engine/core/service-consumer" );
|
|
347
|
+
|
|
348
|
+
class MyService extends ServiceConsumer {
|
|
349
|
+
constructor( serviceDomainName, serviceConfig ) {
|
|
350
|
+
super( serviceDomainName, serviceConfig );
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = MyService;
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
And now let's add a service call executed at microservice start after `500` milliseconds timeout:
|
|
358
|
+
|
|
359
|
+
```js
|
|
360
|
+
const ServiceConsumer = require( "@ti-engine/core/service-consumer" );
|
|
361
|
+
const logger = require( "@ti-engine/core/logger" );
|
|
362
|
+
const exceptions = require( "@ti-engine/core/exceptions" );
|
|
363
|
+
const { setTimeout: setTimeoutPromise } = require( "node:timers/promises" );
|
|
364
|
+
|
|
365
|
+
class MyService extends ServiceConsumer {
|
|
366
|
+
constructor( serviceDomainName, serviceConfig ) {
|
|
367
|
+
super( serviceDomainName, serviceConfig );
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
onStart() {
|
|
371
|
+
return new Promise( ( resolve, reject ) => {
|
|
372
|
+
super.onStart().then( () => {
|
|
373
|
+
return setTimeoutPromise( 500 );
|
|
374
|
+
} ).then( () => {
|
|
375
|
+
return this.callService( {
|
|
376
|
+
serviceAlias: "service2",
|
|
377
|
+
serviceDomainName: "ti-tester-service"
|
|
378
|
+
}, {}, {
|
|
379
|
+
authToken: "auth"
|
|
380
|
+
} );
|
|
381
|
+
} ).then( ( result ) => {
|
|
382
|
+
logger.log( "Execution of service2 result:", logger.logSeverity.NOTICE, result );
|
|
383
|
+
resolve();
|
|
384
|
+
} ).catch( ( error ) => {
|
|
385
|
+
reject( exceptions.raise( error ) );
|
|
386
|
+
} );
|
|
387
|
+
} );
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
module.exports = MyService;
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Now let's start the new microservice with `node .\node_modules\@ti-engine\core\bin\start-instance.js` command. Remember, you need to execute this inside the `my-service` folder you created for this exercise. If everything was configured correctly, you should get the following output:
|
|
395
|
+
|
|
396
|
+
```text
|
|
397
|
+
[timestamp]: [instance-id] - notice - Starting new instance of type 'my-service' with instance ID '[instance-id]'.
|
|
398
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'system-cache' and is ready to be used.
|
|
399
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-responses-in' and is ready to be used.
|
|
400
|
+
[timestamp]: [instance-id] - info - Connection to Redis server '127.0.0.1:6379' established by client 'connection-msg-requests-out' and is ready to be used.
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
If you haven't started anything else, this is all you should see at this point.
|
|
404
|
+
|
|
405
|
+
Now without exiting this node process, let's start the original tester microservice as well. Once it initializes and does its work take a look at the output logs of the `my-service` process:
|
|
406
|
+
|
|
407
|
+
```text
|
|
408
|
+
...
|
|
409
|
+
[timestamp]: [instance-id] - notice - Instance [instance-id] started successfully.
|
|
410
|
+
» {"nodeVersion":[node-version]}
|
|
411
|
+
[timestamp]: [instance-id] - notice - Execution of service2 result:
|
|
412
|
+
» {"isSuccessful":true,"payload":{"s1Timestamp":[timestamp],"s2TimestampStart":[timestamp],"s2TimestampEnd":[timestamp]}}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
This means the service call processing was successful and a result was returned to `my-service`. Because we made the receiving of the result blocking and part of the initialization sequence, the new microservice did not report successful startup until it received that response from `ti-tester-service`.
|
|
416
|
+
|
|
417
|
+
And with this step we are done. The new microservice is now operational. You can continue to tweak and play with it to understand better how it all works. For more details on the **ti-engine** inner working, please see the following sections.
|
|
418
|
+
|
|
419
|
+
## Using the framework
|
|
420
|
+
|
|
421
|
+
### Environment variables
|
|
422
|
+
|
|
423
|
+
These are the system-level ENV variables that can be used to configure the framework. Use an `.env` file or corresponding containerization features to set the variables you need. Not all of these are mandatory or even required for every microservice you create. Consult the documentation for more details.
|
|
424
|
+
|
|
425
|
+
TI_AUDITING_LOG_MIN_LEVEL
|
|
426
|
+
: Type: Optional
|
|
427
|
+
: Specifies the minimum log level that should be sent to the log output stream. The framework will filter out all log entries with a level below this value. The default value is `200` (corresponding to INFO).
|
|
428
|
+
|
|
429
|
+
TI_FAIL_FAST_ON_UNHANDLED_OFF
|
|
430
|
+
: Type: Optional
|
|
431
|
+
: Specifies whether the framework fail-fast on unhandled promise behavior should be turned off. Set this to `true` to disable the default behavior. The default value is `false`.
|
|
432
|
+
|
|
433
|
+
TI_INSTANCE_CLASS
|
|
434
|
+
: Type: Required
|
|
435
|
+
: Specifies the relative path to the implementation of the `ServiceInstance` framework class—in this case a `ServiceProvider`. As stated above, the path is relative to the working directory of the `node` process. This variable is mandatory for every microservice you create with the **ti-engine**. If it is not provided, the microservice won't be able to start at all, and you will get an exception.
|
|
436
|
+
|
|
437
|
+
TI_INSTANCE_CONFIG
|
|
438
|
+
: Type: Optional
|
|
439
|
+
: Specifies the relative path to the configuration data for the microservice. Technically, you can omit this variable, and the microservice will still start successfully with an empty configuration. There are very few cases, however, where this would be applicable.
|
|
440
|
+
|
|
441
|
+
TI_INSTANCE_NAME
|
|
442
|
+
: Type: Required
|
|
443
|
+
: Specifies the _service domain_ name provided for the microservice. It has to be _unique_ in the context of the microservice ecosystem. If not provided, the framework will attempt to extract this information from the name of the implementation file. That is not a recommended approach, though, as it might cause hard to identify errors later.
|
|
444
|
+
|
|
445
|
+
### Framework settings
|
|
446
|
+
|
|
447
|
+
Here you can find all settings used by **ti-engine** together with information on what they do. They are defined inside the `config` module and the full list can be accessed through the public `setting` enum. To get the current value of a setting, you can use the public method `getSetting` from the same module. Some settings can be overridden by providing ENV variables as specified below at node application startup.
|
|
448
|
+
|
|
449
|
+
AUDITING_LOG_CONSOLE_ENABLED
|
|
450
|
+
: JSON path `auditing.logConsoleEnabled`, type `boolean`, default `true`
|
|
451
|
+
: ENV variable `TI_AUDITING_LOG_CONSOLE_ENABLED`
|
|
452
|
+
: This setting controls whether the `auditing` module will send the log entries to the OS console or not. In some cases, like Cloud environments, you might want to disable this, especially if the OS console is not made available. This functions independently of other logging outputs like, for example, GCloud error reporting.
|
|
453
|
+
|
|
454
|
+
AUDITING_LOG_DETAILS
|
|
455
|
+
: JSON path `auditing.logDetails`, type `boolean`, default `true`
|
|
456
|
+
: ENV variable `TI_AUDITING_LOG_DETAILS`
|
|
457
|
+
: This setting controls whether the `auditing` module will include the log entry details (located in the `data` property) in the final log output. You might want to disable this if you want a leaner log output, or the log entry details are not something you plan to use for analysis later.
|
|
458
|
+
|
|
459
|
+
AUDITING_LOG_MIN_LEVEL
|
|
460
|
+
: JSON path `auditing.logMinLevel`, type `number`, default `0`
|
|
461
|
+
: ENV variable `TI_AUDITING_LOG_MIN_LEVEL`
|
|
462
|
+
: This setting controls the minimum log severity level that the framework will log in the log output. You can and should set this to `200` (INFO) for production environments to filter out the DEBUG and the low-level DEFAULT entries.
|
|
463
|
+
|
|
464
|
+
AUDITING_LOG_USES_JSON
|
|
465
|
+
: JSON path `auditing.logUsesJSON`, type `boolean`, default `false`
|
|
466
|
+
: ENV variable `TI_AUDITING_LOG_USES_JSON`
|
|
467
|
+
: This setting controls whether the log entries would be sent to output formatted as JSONs or not. By default, the framework outputs log entries as prettified text. In some cases, however, you might want to have the entire entry as a JSON for further processing (for example, if you're sending all logs to Elasticsearch).
|
|
468
|
+
|
|
469
|
+
GCLOUD_API_KEY (Alpha)
|
|
470
|
+
: JSON path `gcloudIntegration.apiKey`
|
|
471
|
+
: This setting holds the API key for the GCloud integration module.
|
|
472
|
+
|
|
473
|
+
GCLOUD_PROJECT_ID (Alpha)
|
|
474
|
+
: JSON path `gcloudIntegration.projectID`
|
|
475
|
+
: This setting holds the project ID for the GCloud integration module.
|
|
476
|
+
|
|
477
|
+
LOCALIZATION_LABELS_PATH
|
|
478
|
+
: JSON path `localization.labelsPath`, type `Array<string>`, default `[]`
|
|
479
|
+
: ENV variable `TI_LOCALIZATION_LABELS_PATH`
|
|
480
|
+
: This setting holds a list of paths to custom `.json` files containing additional localization information. By default, the framework also provides such a file with English texts that can be customized further. All additional JSONs in these files have to follow the rules and structure of the `localization` module. The ENV variable currently supports providing only a single custom path.
|
|
481
|
+
|
|
482
|
+
LOCALIZATION_LANGUAGE
|
|
483
|
+
: JSON path `localization.language`, type `string`, default `en`
|
|
484
|
+
: ENV variable `TI_LOCALIZATION_LANGUAGE`
|
|
485
|
+
: This setting specifies the default framework language. It will be used when translating labels into a localized text.
|
|
486
|
+
|
|
487
|
+
MEMORY_CACHE_AUTH_KEY
|
|
488
|
+
: JSON path `memoryCache.authKey`, type `string`, default `undefined`
|
|
489
|
+
: ENV variable `TI_MEMORY_CACHE_AUTH_KEY`
|
|
490
|
+
: This setting holds the Redis password for accessing the Redis server if such a password is required.
|
|
491
|
+
|
|
492
|
+
MEMORY_CACHE_REDIS_DB
|
|
493
|
+
: JSON path `memoryCache.redisDB`, type `number`, default `0`
|
|
494
|
+
: ENV variable `TI_MEMORY_CACHE_REDIS_DB`
|
|
495
|
+
: This setting specifies the Redis DB to be used for all operations. When setting this make sure that the Redis server actually supports multiple DBs (for example, Redis Cloud has only one DB with ID `0`).
|
|
496
|
+
|
|
497
|
+
MEMORY_CACHE_REDIS_HOST
|
|
498
|
+
: JSON path `memoryCache.redisHost`, type `string`, default `127.0.0.1`
|
|
499
|
+
: ENV variable `TI_MEMORY_CACHE_REDIS_HOST`
|
|
500
|
+
: This setting holds the Redis server hostname. It can be an IP or URL depending on your configuration.
|
|
501
|
+
|
|
502
|
+
MEMORY_CACHE_REDIS_PORT
|
|
503
|
+
: JSON path `memoryCache.redisPort`, type `number`, default `6379`
|
|
504
|
+
: ENV variable `TI_MEMORY_CACHE_REDIS_PORT`
|
|
505
|
+
: This setting holds the Redis server port. By default, Redis uses `6379` however many implementations might use a custom port that needs to be specified in the connection settings.
|
|
506
|
+
|
|
507
|
+
MEMORY_CACHE_RETRY_MAX_ATTEMPTS
|
|
508
|
+
: JSON path `memoryCache.retryMaxAttempts`, type `number`, default `undefined`
|
|
509
|
+
: ENV variable `TI_MEMORY_CACHE_RETRY_MAX_ATTEMPTS`
|
|
510
|
+
: This setting holds the maximum number of attempts to (re)connect to the Redis server. By default, there is no limit.
|
|
511
|
+
|
|
512
|
+
MEMORY_CACHE_RETRY_MAX_INTERVAL
|
|
513
|
+
: JSON path `memoryCache.retryMaxInterval`, type `number`, default `1000`
|
|
514
|
+
: ENV variable `TI_MEMORY_CACHE_RETRY_MAX_INTERVAL`
|
|
515
|
+
: This setting holds the maximum interval in milliseconds between retries when the Redis server is not available.
|
|
516
|
+
|
|
517
|
+
MEMORY_CACHE_USER
|
|
518
|
+
: JSON path `memoryCache.user`, type `string`, default `default`
|
|
519
|
+
: ENV variable `TI_MEMORY_CACHE_USER`
|
|
520
|
+
: This setting holds the Redis username for accessing the Redis server if this is supported by the Redis version (it will be ignored otherwise).
|
|
521
|
+
|
|
522
|
+
MESSAGE_EXCHANGE_QUEUE_PREFIX (Advanced)
|
|
523
|
+
: JSON path `messageExchange.messageQueuePrefix`, type `string`, default `ti:messages:`
|
|
524
|
+
: This setting holds the Redis key prefix for the queues that will hold the messages of the message exchange. This is not something you should modify unless you are making a customized implementation of the tier 1 architectural layer.
|
|
525
|
+
|
|
526
|
+
MESSAGE_EXCHANGE_MESSAGE_STORE (Advanced)
|
|
527
|
+
: JSON path `messageExchange.messageStore`, type `string`, default `ti:messages:store`
|
|
528
|
+
: This setting holds the Redis key name of the hash table that will hold the message payloads of the message exchange. This is not something you should modify unless you are making a customized implementation of the tier 1 architectural layer.
|
|
529
|
+
|
|
530
|
+
MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED (Advanced)
|
|
531
|
+
: JSON path `messageExchange.securityHashEnabled`, type `boolean`, default `true`
|
|
532
|
+
: ENV variable `TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED`
|
|
533
|
+
: This setting controls whether the message exchange will use a control hash mechanism to ensure there is no tampering with the messages in between service calls. In most cases you would want to keep this enabled since it ensures the integrity of your data. If you are concerned about performance (the keyed `HMAC-SHA256` hash is rapid, but it still eats some milliseconds) you might want to try and disable this to see if it makes any notable difference.
|
|
534
|
+
|
|
535
|
+
MESSAGE_EXCHANGE_SECURITY_HASH_KEY (Advanced)
|
|
536
|
+
: JSON path `messageExchange.securityHashKey`, type `string`, default `empty (must be set)`
|
|
537
|
+
: ENV variable `TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY`
|
|
538
|
+
: This setting holds the HMAC key used by the message exchange control hash mechanism. By default it is empty, which leaves tamper protection ineffective and logs a startup warning. You absolutely must provide your own private key via the ENV variable in any real deployment. Depending on your configuration and infrastructure, it might come from a secure storage, HSM, key vault, etc.
|
|
539
|
+
|
|
540
|
+
MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME
|
|
541
|
+
: JSON path `messageExchange.traceExpirationTime`, type `number`, default `3600`
|
|
542
|
+
: This setting specifies the expiration time in seconds of the Redis key that will hold the message trace entries. Set this to `0` to disable expiration altogether.
|
|
543
|
+
|
|
544
|
+
MESSAGE_EXCHANGE_TRACE_LOG_ENABLED
|
|
545
|
+
: JSON path `messageExchange.traceLogEnabled`, type `boolean`, default `false`
|
|
546
|
+
: ENV variable `TI_MESSAGE_EXCHANGE_TRACE_LOG_ENABLED`
|
|
547
|
+
: This setting controls whether the `auditing` module should output all trace messages as normal log entries or not. Normally, you don't want that since it will clutter the standard log quite a lot. All traces go to their own storage and can be reviewed and processed separately from the log entries. In some cases, however, as in debugging, enabling this can help you identify a hard to track problem.
|
|
548
|
+
|
|
549
|
+
MESSAGE_EXCHANGE_TRACE_REPOSITORY (Advanced)
|
|
550
|
+
: JSON path `messageExchange.traceRepository`, type `string`, default `ti:messages:trace`
|
|
551
|
+
: This setting holds the Redis key name for the message trace cache storage. This is not something you should modify unless you are making a customized implementation of the tier 1 architectural layer.
|
|
552
|
+
|
|
553
|
+
SERVICE_EXECUTION_TIMEOUT
|
|
554
|
+
: JSON path `serviceConfig.executionTimeout`, type `number`, default `180000`
|
|
555
|
+
: This setting specifies the timeout in milliseconds of the service call executions at tier 2 of the architecture. Any service call that hasn't received response within this time will interrupt the wait and raise an `E_COM_SERVICE_EXEC_TIMEOUT` exception. Please keep in mind that reaching the timeout does not mean the remote service did not process the request. You might want to tweak this setting if you have many time-consuming operations in business services, or you plan to integrate with slow APIs.
|
|
556
|
+
|
|
557
|
+
SERVICE_HEALTH_CHECK_ADDRESS (Advanced)
|
|
558
|
+
: JSON path `serviceConfig.healthCheckAddress`, type `string`, default `ti:services:registry:health:`
|
|
559
|
+
: This setting specifies the address of the health check report endpoint for the microservice. In the default implementation this is a prefix for a Redis key that gets updated once at every `SERVICE_HEALTH_CHECK_INTERVAL`. If you override the `reportHealthy` method of the microservice, this setting can contain a URL or another type of destination that can be used by your custom implementation.
|
|
560
|
+
|
|
561
|
+
SERVICE_HEALTH_CHECK_INTERVAL (Advanced)
|
|
562
|
+
: JSON path `serviceConfig.healthCheckInterval`, type `CRON string`, default `*/1 * * * * *`
|
|
563
|
+
: This setting specifies the health check report interval at which the endpoint in `SERVICE_HEALTH_CHECK_ADDRESS` is notified.
|
|
564
|
+
|
|
565
|
+
SERVICE_HEALTH_CHECK_TIMEOUT (Advanced)
|
|
566
|
+
: JSON path `serviceConfig.healthCheckTimeout`, type `number`, default `3`
|
|
567
|
+
: This setting specifies the timeout in seconds after which a microservice is no longer considered healthy. In the default implementation this represents an expiration parameter to the Redis key defined in `SERVICE_HEALTH_CHECK_ADDRESS`. Essentially, if the microservice does not update the Redis key within this time interval, it will expire and the monitoring application will lose the healthy status of the microservice. If you override the `reportHealthy` method of the microservice, this setting can be used for your custom implementation as needed.
|
|
568
|
+
|
|
569
|
+
SERVICE_REGISTRY_ADDRESS (Advanced)
|
|
570
|
+
: JSON path `serviceConfig.serviceRegistryAddress`, type `string`, default `ti:services:registry:catalog:`
|
|
571
|
+
: This setting holds the prefix of the Redis key name used as a business service registry. If the microservice is a `ServiceProvider`, on start up it will register its business service portfolio in that Redis set. Also, on each service call that same registry will be searched for the existence of the called business service. This is not something you should modify unless you are making a customized implementation of the tier 1 architectural layer.
|
|
572
|
+
|
|
573
|
+
OPERATION_MODE
|
|
574
|
+
: JSON path `operationMode`, type `string`, default `production`
|
|
575
|
+
: ENV variable `NODE_ENV`
|
|
576
|
+
: This setting holds the current operation mode of the node application. It will inherit the value from the `NODE_ENV` variable if it exists, otherwise will use its default.
|
|
577
|
+
|
|
578
|
+
## Advanced topics
|
|
579
|
+
|
|
580
|
+
### Localization
|
|
581
|
+
|
|
582
|
+
The **ti-engine** framework provides a localization mechanism that allows you to translate labels into localized text. The framework comes with a default set of labels that can be found in the `localization` module. You can add your own custom labels to this set by providing one or more JSON files with the same structure as the default one (see below). The path to these files should be specified in the `LOCALIZATION_LABELS_PATH` setting. On startup, the framework will load all the JSON files and merge them into a single repository.
|
|
583
|
+
|
|
584
|
+
The following is an example of a custom localization file. The names in brackets can be replaced with your own values. The depth of the three is unlimited and can be used to create a hierarchy of labels.
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
{
|
|
588
|
+
"[category]": {
|
|
589
|
+
"[sub-category]": {
|
|
590
|
+
"[label]": {
|
|
591
|
+
"[language code]": "[localized label text]"
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
The individual languages are specified with a two-letter code according to ISO 639-1. The default language is `en` (English). If you want to use a different language, you can set the `LOCALIZATION_LANGUAGE` setting to the desired language code.
|
|
599
|
+
|
|
598
600
|
If you want to localize the system labels of the framework, you can do so by providing a custom localization file with the same structure as the default one but containing only the languages you want to add. For example, the default English label for `E_UNKNOWN_ERROR` is found in JSON path `system.exceptions.0.en`. To add an entry for German, your file needs to contain a `system.exceptions.0.de` property. The localization module will handle the rest. You can use the same approach to modify the existing English labels as well. The full list of system labels is found in the `bin/localization/labels.json` file. Avoid modifying the default file since it might get overwritten by future updates.
|