@onlineapps/service-wrapper 2.0.37 → 2.0.38
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/package.json +2 -1
- package/src/ServiceWrapper.js +223 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/service-wrapper",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.38",
|
|
4
4
|
"description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@onlineapps/conn-orch-orchestrator": "^1.0.1",
|
|
34
34
|
"@onlineapps/conn-orch-registry": "^1.1.16",
|
|
35
35
|
"@onlineapps/conn-orch-validator": "^2.0.0",
|
|
36
|
+
"@onlineapps/infrastructure-tools": "^1.0.3",
|
|
36
37
|
"@onlineapps/monitoring-core": "^1.0.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -330,19 +330,50 @@ class ServiceWrapper {
|
|
|
330
330
|
// CRITICAL: Start workflow listeners ONLY after successful validation and registration with certificate
|
|
331
331
|
// Both workflow.init and service-specific listeners start here
|
|
332
332
|
if (registrationResult.success && registrationResult.certificate) {
|
|
333
|
-
this.logger?.info('✓ Certificate validated,
|
|
333
|
+
this.logger?.info('✓ Certificate validated, verifying infrastructure readiness...');
|
|
334
|
+
|
|
335
|
+
// PHASE 8: Verify infrastructure services are ready before creating business queues
|
|
336
|
+
await this._verifyInfrastructureReady(serviceName);
|
|
337
|
+
|
|
338
|
+
this.logger?.info('✓ Infrastructure verified, creating business queues...');
|
|
334
339
|
try {
|
|
335
|
-
|
|
336
|
-
|
|
340
|
+
// CRITICAL: Create ALL business queues atomically (main, dlq, workflow)
|
|
341
|
+
// This MUST succeed completely - if one queue fails, all fail
|
|
342
|
+
const createdQueues = await this.mqClient.setupServiceQueues(serviceName, { includeWorkflow: true });
|
|
343
|
+
this.logger?.info('✓ Business queues initialized via QueueManager', {
|
|
344
|
+
queues: Object.keys(createdQueues).map(k => createdQueues[k])
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Verify all queues were created
|
|
348
|
+
const requiredQueues = ['main', 'dlq', 'workflow'];
|
|
349
|
+
const missingQueues = requiredQueues.filter(q => !createdQueues[q]);
|
|
350
|
+
if (missingQueues.length > 0) {
|
|
351
|
+
throw new Error(`Failed to create all required business queues. Missing: ${missingQueues.join(', ')}`);
|
|
352
|
+
}
|
|
337
353
|
} catch (queueError) {
|
|
338
|
-
this.logger?.error('Failed to initialize business queues', {
|
|
354
|
+
this.logger?.error('Failed to initialize business queues', {
|
|
355
|
+
error: queueError.message,
|
|
356
|
+
stack: queueError.stack
|
|
357
|
+
});
|
|
339
358
|
throw new Error(`Failed to initialize business queues for ${serviceName}: ${queueError.message}`);
|
|
340
359
|
}
|
|
341
360
|
|
|
342
|
-
// Start workflow
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
361
|
+
// CRITICAL: Start workflow listeners ONLY after all queues are created
|
|
362
|
+
// Queues must exist before we try to consume from them
|
|
363
|
+
this.logger?.info('✓ Starting workflow listeners...');
|
|
364
|
+
try {
|
|
365
|
+
// Start workflow.init listener (for all services)
|
|
366
|
+
await this._startWorkflowInitListener();
|
|
367
|
+
// Start service-specific workflow listener
|
|
368
|
+
await this._startServiceWorkflowListener();
|
|
369
|
+
this.logger?.info('✓ All workflow listeners started successfully');
|
|
370
|
+
} catch (listenerError) {
|
|
371
|
+
this.logger?.error('Failed to start workflow listeners', {
|
|
372
|
+
error: listenerError.message,
|
|
373
|
+
stack: listenerError.stack
|
|
374
|
+
});
|
|
375
|
+
throw new Error(`Failed to start workflow listeners for ${serviceName}: ${listenerError.message}`);
|
|
376
|
+
}
|
|
346
377
|
} else {
|
|
347
378
|
this.logger?.warn('⚠ Registration succeeded but no certificate received - workflow listeners NOT started');
|
|
348
379
|
}
|
|
@@ -368,6 +399,155 @@ class ServiceWrapper {
|
|
|
368
399
|
}, heartbeatInterval);
|
|
369
400
|
}
|
|
370
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Verify infrastructure services are ready before creating business queues
|
|
404
|
+
* Phase 8: Business Services Infrastructure Verification
|
|
405
|
+
*
|
|
406
|
+
* Checks:
|
|
407
|
+
* 1. All infrastructure services are UP (via Redis key infrastructure:health:all)
|
|
408
|
+
* 2. Required infrastructure queues exist (workflow.init, registry.register, etc.)
|
|
409
|
+
*
|
|
410
|
+
* Uses exponential backoff retry mechanism if infrastructure not ready.
|
|
411
|
+
* @private
|
|
412
|
+
*/
|
|
413
|
+
async _verifyInfrastructureReady(serviceName) {
|
|
414
|
+
const maxRetries = parseInt(process.env.INFRASTRUCTURE_VERIFY_MAX_RETRIES) || 12; // 12 * 5s = 60s max
|
|
415
|
+
const baseDelay = parseInt(process.env.INFRASTRUCTURE_VERIFY_BASE_DELAY) || 5000; // 5 seconds
|
|
416
|
+
const maxDelay = parseInt(process.env.INFRASTRUCTURE_VERIFY_MAX_DELAY) || 30000; // 30 seconds max
|
|
417
|
+
const redisUrl = this.config.wrapper?.cache?.url || process.env.REDIS_URL || 'redis://api_node_cache:6379';
|
|
418
|
+
|
|
419
|
+
// Required infrastructure queues that must exist before business queues can be created
|
|
420
|
+
const requiredInfrastructureQueues = [
|
|
421
|
+
'workflow.init', // Gateway responsibility
|
|
422
|
+
'registry.register', // Registry responsibility
|
|
423
|
+
'registry.heartbeats' // Registry responsibility
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
// Optional infrastructure queues (may not exist if services are not running)
|
|
427
|
+
const optionalInfrastructureQueues = [
|
|
428
|
+
'validation.requests', // Validator responsibility
|
|
429
|
+
'workflow.completed', // Delivery Dispatcher responsibility
|
|
430
|
+
'workflow.failed' // Delivery Dispatcher responsibility
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
this.logger?.info('[InfrastructureVerify] Starting infrastructure readiness verification...', {
|
|
434
|
+
maxRetries,
|
|
435
|
+
baseDelay,
|
|
436
|
+
requiredQueues: requiredInfrastructureQueues
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
440
|
+
try {
|
|
441
|
+
// Step 1: Check infrastructure health via Redis
|
|
442
|
+
this.logger?.info(`[InfrastructureVerify] Attempt ${attempt}/${maxRetries}: Checking infrastructure health...`);
|
|
443
|
+
|
|
444
|
+
const { waitForInfrastructureReady } = require('@onlineapps/infrastructure-tools');
|
|
445
|
+
const waitMaxTime = 10000; // 10 seconds per attempt
|
|
446
|
+
const waitCheckInterval = 2000; // Check every 2 seconds
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
await waitForInfrastructureReady({
|
|
450
|
+
redisUrl: redisUrl,
|
|
451
|
+
maxWait: waitMaxTime,
|
|
452
|
+
checkInterval: waitCheckInterval,
|
|
453
|
+
logger: {
|
|
454
|
+
log: (msg) => this.logger?.info(`[InfrastructureVerify] ${msg}`)
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
this.logger?.info('[InfrastructureVerify] ✓ All infrastructure services are UP');
|
|
458
|
+
} catch (healthError) {
|
|
459
|
+
// Infrastructure not ready yet - will retry with exponential backoff
|
|
460
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
461
|
+
this.logger?.warn(`[InfrastructureVerify] Infrastructure not ready (attempt ${attempt}/${maxRetries}), retrying in ${delay}ms...`, {
|
|
462
|
+
error: healthError.message
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
if (attempt < maxRetries) {
|
|
466
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
467
|
+
continue; // Retry
|
|
468
|
+
} else {
|
|
469
|
+
throw new Error(`Infrastructure services not ready after ${maxRetries} attempts: ${healthError.message}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Step 2: Verify required infrastructure queues exist
|
|
474
|
+
this.logger?.info('[InfrastructureVerify] Verifying required infrastructure queues exist...');
|
|
475
|
+
|
|
476
|
+
const transport = this.mqClient._transport;
|
|
477
|
+
if (!transport || !transport._queueChannel) {
|
|
478
|
+
throw new Error('MQ transport not initialized - cannot verify infrastructure queues');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const channel = transport._queueChannel;
|
|
482
|
+
if (channel.closed) {
|
|
483
|
+
throw new Error('MQ channel is closed - cannot verify infrastructure queues');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const missingQueues = [];
|
|
487
|
+
for (const queueName of requiredInfrastructureQueues) {
|
|
488
|
+
try {
|
|
489
|
+
await channel.checkQueue(queueName);
|
|
490
|
+
this.logger?.info(`[InfrastructureVerify] ✓ Queue exists: ${queueName}`);
|
|
491
|
+
} catch (checkErr) {
|
|
492
|
+
if (checkErr.code === 404) {
|
|
493
|
+
missingQueues.push(queueName);
|
|
494
|
+
this.logger?.warn(`[InfrastructureVerify] ✗ Queue missing: ${queueName}`);
|
|
495
|
+
} else {
|
|
496
|
+
// Other error (e.g., channel closed) - treat as missing
|
|
497
|
+
missingQueues.push(queueName);
|
|
498
|
+
this.logger?.warn(`[InfrastructureVerify] ✗ Cannot check queue ${queueName}: ${checkErr.message}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (missingQueues.length > 0) {
|
|
504
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
505
|
+
this.logger?.warn(`[InfrastructureVerify] Missing required infrastructure queues (attempt ${attempt}/${maxRetries}): ${missingQueues.join(', ')}. Retrying in ${delay}ms...`);
|
|
506
|
+
|
|
507
|
+
if (attempt < maxRetries) {
|
|
508
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
509
|
+
continue; // Retry
|
|
510
|
+
} else {
|
|
511
|
+
throw new Error(`Required infrastructure queues missing after ${maxRetries} attempts: ${missingQueues.join(', ')}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Step 3: Log optional queues status (for debugging)
|
|
516
|
+
for (const queueName of optionalInfrastructureQueues) {
|
|
517
|
+
try {
|
|
518
|
+
await channel.checkQueue(queueName);
|
|
519
|
+
this.logger?.info(`[InfrastructureVerify] ✓ Optional queue exists: ${queueName}`);
|
|
520
|
+
} catch (checkErr) {
|
|
521
|
+
if (checkErr.code === 404) {
|
|
522
|
+
this.logger?.debug(`[InfrastructureVerify] Optional queue missing (not critical): ${queueName}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// All checks passed!
|
|
528
|
+
this.logger?.info('[InfrastructureVerify] ✓ Infrastructure verification complete - all required queues exist');
|
|
529
|
+
return;
|
|
530
|
+
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (attempt === maxRetries) {
|
|
533
|
+
// Final attempt failed - throw error
|
|
534
|
+
this.logger?.error('[InfrastructureVerify] ✗ Infrastructure verification failed after all retries', {
|
|
535
|
+
error: error.message,
|
|
536
|
+
stack: error.stack
|
|
537
|
+
});
|
|
538
|
+
throw new Error(`Infrastructure verification failed for ${serviceName}: ${error.message}`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Not final attempt - continue retry loop
|
|
542
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
543
|
+
this.logger?.warn(`[InfrastructureVerify] Verification failed (attempt ${attempt}/${maxRetries}), retrying in ${delay}ms...`, {
|
|
544
|
+
error: error.message
|
|
545
|
+
});
|
|
546
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
371
551
|
/**
|
|
372
552
|
* Initialize cache connector
|
|
373
553
|
* @private
|
|
@@ -488,12 +668,25 @@ class ServiceWrapper {
|
|
|
488
668
|
return;
|
|
489
669
|
}
|
|
490
670
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
});
|
|
671
|
+
const queueName = 'workflow.init';
|
|
672
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] Starting workflow.init listener for queue: ${queueName}`);
|
|
673
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] Service: ${this.config.service?.name || 'unnamed-service'}`);
|
|
674
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] Timestamp: ${new Date().toISOString()}`);
|
|
495
675
|
|
|
496
|
-
|
|
676
|
+
try {
|
|
677
|
+
// Subscribe to workflow.init queue (for all services)
|
|
678
|
+
await this.mqClient.consume(queueName, async (message) => {
|
|
679
|
+
await this._processWorkflowMessage(message, queueName);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] ✓ workflow.init listener started successfully for queue: ${queueName}`);
|
|
683
|
+
} catch (consumeErr) {
|
|
684
|
+
this.logger?.error(`[ServiceWrapper] [CONSUMER] ✗ Failed to start workflow.init listener for queue: ${queueName}`, {
|
|
685
|
+
error: consumeErr.message,
|
|
686
|
+
stack: consumeErr.stack
|
|
687
|
+
});
|
|
688
|
+
throw consumeErr;
|
|
689
|
+
}
|
|
497
690
|
}
|
|
498
691
|
|
|
499
692
|
/**
|
|
@@ -509,12 +702,24 @@ class ServiceWrapper {
|
|
|
509
702
|
const serviceName = this.config.service?.name || 'unnamed-service';
|
|
510
703
|
const serviceQueue = `${serviceName}.workflow`;
|
|
511
704
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
});
|
|
705
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] Starting service-specific workflow listener for queue: ${serviceQueue}`);
|
|
706
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] Service: ${serviceName}`);
|
|
707
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] Timestamp: ${new Date().toISOString()}`);
|
|
516
708
|
|
|
517
|
-
|
|
709
|
+
try {
|
|
710
|
+
// Subscribe to service-specific queue
|
|
711
|
+
await this.mqClient.consume(serviceQueue, async (message) => {
|
|
712
|
+
await this._processWorkflowMessage(message, serviceQueue);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
this.logger?.info(`[ServiceWrapper] [CONSUMER] ✓ Service-specific workflow listener started successfully for queue: ${serviceQueue}`);
|
|
716
|
+
} catch (consumeErr) {
|
|
717
|
+
this.logger?.error(`[ServiceWrapper] [CONSUMER] ✗ Failed to start service-specific workflow listener for queue: ${serviceQueue}`, {
|
|
718
|
+
error: consumeErr.message,
|
|
719
|
+
stack: consumeErr.stack
|
|
720
|
+
});
|
|
721
|
+
throw consumeErr;
|
|
722
|
+
}
|
|
518
723
|
}
|
|
519
724
|
|
|
520
725
|
/**
|