navis.js 1.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -20
- package/bin/generators/service.js +167 -0
- package/bin/navis.js +56 -2
- package/examples/v2-features-demo.js +190 -0
- package/examples/v3-features-demo.js +226 -0
- package/package.json +1 -1
- package/src/index.js +50 -12
- package/src/messaging/base-messaging.js +82 -0
- package/src/messaging/kafka-adapter.js +152 -0
- package/src/messaging/nats-adapter.js +141 -0
- package/src/messaging/sqs-adapter.js +175 -0
- package/src/observability/logger.js +141 -0
- package/src/observability/metrics.js +193 -0
- package/src/observability/tracer.js +205 -0
- package/src/utils/circuit-breaker.js +107 -0
- package/src/utils/retry.js +88 -0
- package/src/utils/service-client.js +234 -104
- package/src/utils/service-config.js +88 -0
- package/src/utils/service-discovery.js +184 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit Breaker - Prevents cascading failures in microservices
|
|
3
|
+
* v2: Circuit breaker pattern implementation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class CircuitBreaker {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.failureThreshold = options.failureThreshold || 5; // Open circuit after 5 failures
|
|
9
|
+
this.resetTimeout = options.resetTimeout || 60000; // 60 seconds
|
|
10
|
+
this.monitoringWindow = options.monitoringWindow || 10000; // 10 seconds
|
|
11
|
+
|
|
12
|
+
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
|
|
13
|
+
this.failureCount = 0;
|
|
14
|
+
this.successCount = 0;
|
|
15
|
+
this.lastFailureTime = null;
|
|
16
|
+
this.nextAttemptTime = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Record a successful request
|
|
21
|
+
*/
|
|
22
|
+
recordSuccess() {
|
|
23
|
+
if (this.state === 'HALF_OPEN') {
|
|
24
|
+
this.successCount++;
|
|
25
|
+
if (this.successCount >= 2) {
|
|
26
|
+
// Reset to CLOSED after 2 successes in HALF_OPEN
|
|
27
|
+
this.state = 'CLOSED';
|
|
28
|
+
this.failureCount = 0;
|
|
29
|
+
this.successCount = 0;
|
|
30
|
+
}
|
|
31
|
+
} else if (this.state === 'CLOSED') {
|
|
32
|
+
this.failureCount = 0; // Reset failure count on success
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Record a failed request
|
|
38
|
+
*/
|
|
39
|
+
recordFailure() {
|
|
40
|
+
this.failureCount++;
|
|
41
|
+
this.lastFailureTime = Date.now();
|
|
42
|
+
|
|
43
|
+
if (this.state === 'CLOSED' && this.failureCount >= this.failureThreshold) {
|
|
44
|
+
// Open the circuit
|
|
45
|
+
this.state = 'OPEN';
|
|
46
|
+
this.nextAttemptTime = Date.now() + this.resetTimeout;
|
|
47
|
+
} else if (this.state === 'HALF_OPEN') {
|
|
48
|
+
// Failed in half-open, go back to open
|
|
49
|
+
this.state = 'OPEN';
|
|
50
|
+
this.nextAttemptTime = Date.now() + this.resetTimeout;
|
|
51
|
+
this.successCount = 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if request should be allowed
|
|
57
|
+
* @returns {boolean} - true if request should proceed
|
|
58
|
+
*/
|
|
59
|
+
canAttempt() {
|
|
60
|
+
if (this.state === 'CLOSED') {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.state === 'OPEN') {
|
|
65
|
+
if (Date.now() >= this.nextAttemptTime) {
|
|
66
|
+
// Transition to HALF_OPEN
|
|
67
|
+
this.state = 'HALF_OPEN';
|
|
68
|
+
this.successCount = 0;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false; // Circuit is open, reject request
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.state === 'HALF_OPEN') {
|
|
75
|
+
return true; // Allow limited requests in half-open state
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get current circuit state
|
|
83
|
+
*/
|
|
84
|
+
getState() {
|
|
85
|
+
return {
|
|
86
|
+
state: this.state,
|
|
87
|
+
failureCount: this.failureCount,
|
|
88
|
+
successCount: this.successCount,
|
|
89
|
+
lastFailureTime: this.lastFailureTime,
|
|
90
|
+
nextAttemptTime: this.nextAttemptTime,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reset circuit breaker
|
|
96
|
+
*/
|
|
97
|
+
reset() {
|
|
98
|
+
this.state = 'CLOSED';
|
|
99
|
+
this.failureCount = 0;
|
|
100
|
+
this.successCount = 0;
|
|
101
|
+
this.lastFailureTime = null;
|
|
102
|
+
this.nextAttemptTime = null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = CircuitBreaker;
|
|
107
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry utility with exponential backoff
|
|
3
|
+
* v2: Retry logic for resilient service calls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sleep for specified milliseconds
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
function sleep(ms) {
|
|
11
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Calculate exponential backoff delay
|
|
16
|
+
* @param {number} attempt - Current attempt number (0-indexed)
|
|
17
|
+
* @param {number} baseDelay - Base delay in milliseconds
|
|
18
|
+
* @param {number} maxDelay - Maximum delay in milliseconds
|
|
19
|
+
* @param {number} jitter - Random jitter factor (0-1)
|
|
20
|
+
*/
|
|
21
|
+
function calculateBackoff(attempt, baseDelay = 1000, maxDelay = 30000, jitter = 0.1) {
|
|
22
|
+
const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
23
|
+
const jitterAmount = exponentialDelay * jitter * Math.random();
|
|
24
|
+
return Math.floor(exponentialDelay + jitterAmount);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Retry a function with exponential backoff
|
|
29
|
+
* @param {Function} fn - Function to retry (must return a Promise)
|
|
30
|
+
* @param {Object} options - Retry options
|
|
31
|
+
* @param {number} options.maxRetries - Maximum number of retry attempts (default: 3)
|
|
32
|
+
* @param {number} options.baseDelay - Base delay in milliseconds (default: 1000)
|
|
33
|
+
* @param {number} options.maxDelay - Maximum delay in milliseconds (default: 30000)
|
|
34
|
+
* @param {number} options.jitter - Jitter factor 0-1 (default: 0.1)
|
|
35
|
+
* @param {Function} options.shouldRetry - Function to determine if error should be retried (default: retry on all errors)
|
|
36
|
+
* @returns {Promise} - Result of the function
|
|
37
|
+
*/
|
|
38
|
+
async function retry(fn, options = {}) {
|
|
39
|
+
const {
|
|
40
|
+
maxRetries = 3,
|
|
41
|
+
baseDelay = 1000,
|
|
42
|
+
maxDelay = 30000,
|
|
43
|
+
jitter = 0.1,
|
|
44
|
+
shouldRetry = () => true, // Retry on all errors by default
|
|
45
|
+
} = options;
|
|
46
|
+
|
|
47
|
+
let lastError;
|
|
48
|
+
|
|
49
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
50
|
+
try {
|
|
51
|
+
const result = await fn();
|
|
52
|
+
return result;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
lastError = error;
|
|
55
|
+
|
|
56
|
+
// Check if we should retry this error
|
|
57
|
+
if (!shouldRetry(error, attempt)) {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Don't wait after the last attempt
|
|
62
|
+
if (attempt < maxRetries) {
|
|
63
|
+
const delay = calculateBackoff(attempt, baseDelay, maxDelay, jitter);
|
|
64
|
+
await sleep(delay);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw lastError;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if HTTP status code should be retried
|
|
74
|
+
* @param {number} statusCode - HTTP status code
|
|
75
|
+
* @returns {boolean} - true if status code should be retried
|
|
76
|
+
*/
|
|
77
|
+
function shouldRetryHttpStatus(statusCode) {
|
|
78
|
+
// Retry on 5xx errors and 429 (Too Many Requests)
|
|
79
|
+
return statusCode >= 500 || statusCode === 429;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
retry,
|
|
84
|
+
calculateBackoff,
|
|
85
|
+
shouldRetryHttpStatus,
|
|
86
|
+
sleep,
|
|
87
|
+
};
|
|
88
|
+
|
|
@@ -1,105 +1,235 @@
|
|
|
1
|
-
const http = require('http');
|
|
2
|
-
const https = require('https');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const CircuitBreaker = require('./circuit-breaker');
|
|
4
|
+
const { retry, shouldRetryHttpStatus } = require('./retry');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ServiceClient - Lightweight HTTP client for service-to-service calls
|
|
8
|
+
* v2: Enhanced with retry logic, circuit breaker, and additional HTTP methods
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class ServiceClient {
|
|
12
|
+
constructor(baseUrl, options = {}) {
|
|
13
|
+
this.baseUrl = baseUrl;
|
|
14
|
+
this.timeout = options.timeout || 5000; // Default 5s timeout
|
|
15
|
+
|
|
16
|
+
// Retry configuration
|
|
17
|
+
this.retryConfig = {
|
|
18
|
+
maxRetries: options.maxRetries !== undefined ? options.maxRetries : 3,
|
|
19
|
+
baseDelay: options.retryBaseDelay || 1000,
|
|
20
|
+
maxDelay: options.retryMaxDelay || 30000,
|
|
21
|
+
enabled: options.retry !== false, // Enabled by default
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Circuit breaker configuration
|
|
25
|
+
this.circuitBreakerEnabled = options.circuitBreaker !== false; // Enabled by default
|
|
26
|
+
if (this.circuitBreakerEnabled) {
|
|
27
|
+
this.circuitBreaker = new CircuitBreaker({
|
|
28
|
+
failureThreshold: options.circuitBreakerThreshold || 5,
|
|
29
|
+
resetTimeout: options.circuitBreakerResetTimeout || 60000,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Make HTTP request (internal, without retry/circuit breaker)
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
_requestInternal(method, path, data = null, options = {}) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const url = new URL(path, this.baseUrl);
|
|
41
|
+
const isHttps = url.protocol === 'https:';
|
|
42
|
+
const client = isHttps ? https : http;
|
|
43
|
+
|
|
44
|
+
const requestOptions = {
|
|
45
|
+
hostname: url.hostname,
|
|
46
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
47
|
+
path: url.pathname + url.search,
|
|
48
|
+
method,
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
...options.headers,
|
|
52
|
+
},
|
|
53
|
+
timeout: options.timeout || this.timeout,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const req = client.request(requestOptions, (res) => {
|
|
57
|
+
let body = '';
|
|
58
|
+
|
|
59
|
+
res.on('data', (chunk) => {
|
|
60
|
+
body += chunk;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
res.on('end', () => {
|
|
64
|
+
try {
|
|
65
|
+
const parsedBody = body ? JSON.parse(body) : {};
|
|
66
|
+
const response = {
|
|
67
|
+
statusCode: res.statusCode,
|
|
68
|
+
headers: res.headers,
|
|
69
|
+
data: parsedBody,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Check if response indicates failure
|
|
73
|
+
if (res.statusCode >= 400) {
|
|
74
|
+
const error = new Error(`HTTP ${res.statusCode}: ${res.statusMessage || 'Request failed'}`);
|
|
75
|
+
error.statusCode = res.statusCode;
|
|
76
|
+
error.response = response;
|
|
77
|
+
reject(error);
|
|
78
|
+
} else {
|
|
79
|
+
resolve(response);
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
resolve({
|
|
83
|
+
statusCode: res.statusCode,
|
|
84
|
+
headers: res.headers,
|
|
85
|
+
data: body,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
req.on('error', (err) => {
|
|
92
|
+
reject(err);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
req.on('timeout', () => {
|
|
96
|
+
req.destroy();
|
|
97
|
+
reject(new Error('Request timeout'));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (data) {
|
|
101
|
+
req.write(JSON.stringify(data));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
req.end();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Make HTTP request with retry and circuit breaker
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
async _request(method, path, data = null, options = {}) {
|
|
113
|
+
// Check circuit breaker
|
|
114
|
+
if (this.circuitBreakerEnabled && this.circuitBreaker) {
|
|
115
|
+
if (!this.circuitBreaker.canAttempt()) {
|
|
116
|
+
const error = new Error('Circuit breaker is OPEN - service unavailable');
|
|
117
|
+
error.circuitBreakerOpen = true;
|
|
118
|
+
error.circuitState = this.circuitBreaker.getState();
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Execute request with retry if enabled
|
|
124
|
+
const executeRequest = async () => {
|
|
125
|
+
try {
|
|
126
|
+
const result = await this._requestInternal(method, path, data, options);
|
|
127
|
+
|
|
128
|
+
// Record success in circuit breaker
|
|
129
|
+
if (this.circuitBreakerEnabled && this.circuitBreaker) {
|
|
130
|
+
this.circuitBreaker.recordSuccess();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Record failure in circuit breaker
|
|
136
|
+
if (this.circuitBreakerEnabled && this.circuitBreaker) {
|
|
137
|
+
this.circuitBreaker.recordFailure();
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (this.retryConfig.enabled) {
|
|
144
|
+
return retry(executeRequest, {
|
|
145
|
+
maxRetries: this.retryConfig.maxRetries,
|
|
146
|
+
baseDelay: this.retryConfig.baseDelay,
|
|
147
|
+
maxDelay: this.retryConfig.maxDelay,
|
|
148
|
+
shouldRetry: (error) => {
|
|
149
|
+
// Don't retry if circuit breaker is open
|
|
150
|
+
if (error.circuitBreakerOpen) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
// Retry on network errors or 5xx/429 status codes
|
|
154
|
+
if (error.statusCode) {
|
|
155
|
+
return shouldRetryHttpStatus(error.statusCode);
|
|
156
|
+
}
|
|
157
|
+
// Retry on network/timeout errors
|
|
158
|
+
return true;
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return executeRequest();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* GET request
|
|
168
|
+
* @param {string} path - Request path
|
|
169
|
+
* @param {Object} options - Request options
|
|
170
|
+
*/
|
|
171
|
+
async get(path, options = {}) {
|
|
172
|
+
return this._request('GET', path, null, options);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* POST request
|
|
177
|
+
* @param {string} path - Request path
|
|
178
|
+
* @param {Object} data - Request body data
|
|
179
|
+
* @param {Object} options - Request options
|
|
180
|
+
*/
|
|
181
|
+
async post(path, data, options = {}) {
|
|
182
|
+
return this._request('POST', path, data, options);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* PUT request
|
|
187
|
+
* @param {string} path - Request path
|
|
188
|
+
* @param {Object} data - Request body data
|
|
189
|
+
* @param {Object} options - Request options
|
|
190
|
+
*/
|
|
191
|
+
async put(path, data, options = {}) {
|
|
192
|
+
return this._request('PUT', path, data, options);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* DELETE request
|
|
197
|
+
* @param {string} path - Request path
|
|
198
|
+
* @param {Object} options - Request options
|
|
199
|
+
*/
|
|
200
|
+
async delete(path, options = {}) {
|
|
201
|
+
return this._request('DELETE', path, null, options);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* PATCH request
|
|
206
|
+
* @param {string} path - Request path
|
|
207
|
+
* @param {Object} data - Request body data
|
|
208
|
+
* @param {Object} options - Request options
|
|
209
|
+
*/
|
|
210
|
+
async patch(path, data, options = {}) {
|
|
211
|
+
return this._request('PATCH', path, data, options);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get circuit breaker state
|
|
216
|
+
* @returns {Object} - Circuit breaker state information
|
|
217
|
+
*/
|
|
218
|
+
getCircuitBreakerState() {
|
|
219
|
+
if (!this.circuitBreakerEnabled || !this.circuitBreaker) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return this.circuitBreaker.getState();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Reset circuit breaker
|
|
227
|
+
*/
|
|
228
|
+
resetCircuitBreaker() {
|
|
229
|
+
if (this.circuitBreakerEnabled && this.circuitBreaker) {
|
|
230
|
+
this.circuitBreaker.reset();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
105
235
|
module.exports = ServiceClient;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Configuration Manager
|
|
3
|
+
* v2: Config-based service management for microservices
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ServiceConfig {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.services = config.services || {};
|
|
9
|
+
this.defaultOptions = config.defaultOptions || {
|
|
10
|
+
timeout: 5000,
|
|
11
|
+
retry: {
|
|
12
|
+
maxRetries: 3,
|
|
13
|
+
baseDelay: 1000,
|
|
14
|
+
maxDelay: 30000,
|
|
15
|
+
},
|
|
16
|
+
circuitBreaker: {
|
|
17
|
+
failureThreshold: 5,
|
|
18
|
+
resetTimeout: 60000,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Register a service
|
|
25
|
+
* @param {string} name - Service name
|
|
26
|
+
* @param {string} baseUrl - Service base URL
|
|
27
|
+
* @param {Object} options - Service-specific options
|
|
28
|
+
*/
|
|
29
|
+
register(name, baseUrl, options = {}) {
|
|
30
|
+
this.services[name] = {
|
|
31
|
+
baseUrl,
|
|
32
|
+
...this.defaultOptions,
|
|
33
|
+
...options,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get service configuration
|
|
39
|
+
* @param {string} name - Service name
|
|
40
|
+
* @returns {Object|null} - Service configuration or null if not found
|
|
41
|
+
*/
|
|
42
|
+
get(name) {
|
|
43
|
+
return this.services[name] || null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get all registered services
|
|
48
|
+
* @returns {Object} - All service configurations
|
|
49
|
+
*/
|
|
50
|
+
getAll() {
|
|
51
|
+
return { ...this.services };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Remove a service
|
|
56
|
+
* @param {string} name - Service name
|
|
57
|
+
*/
|
|
58
|
+
unregister(name) {
|
|
59
|
+
delete this.services[name];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load services from configuration object
|
|
64
|
+
* @param {Object} config - Configuration object with services
|
|
65
|
+
*/
|
|
66
|
+
load(config) {
|
|
67
|
+
if (config.services) {
|
|
68
|
+
this.services = { ...this.services, ...config.services };
|
|
69
|
+
}
|
|
70
|
+
if (config.defaultOptions) {
|
|
71
|
+
this.defaultOptions = { ...this.defaultOptions, ...config.defaultOptions };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Export current configuration
|
|
77
|
+
* @returns {Object} - Current configuration
|
|
78
|
+
*/
|
|
79
|
+
export() {
|
|
80
|
+
return {
|
|
81
|
+
services: this.services,
|
|
82
|
+
defaultOptions: this.defaultOptions,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = ServiceConfig;
|
|
88
|
+
|