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,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Discovery - Basic service discovery mechanism
|
|
3
|
+
* v2: Service discovery for microservice architectures
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ServiceDiscovery {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.services = new Map(); // name -> { urls: [], currentIndex: 0, health: {} }
|
|
9
|
+
this.healthCheckInterval = options.healthCheckInterval || 30000; // 30 seconds
|
|
10
|
+
this.healthCheckTimeout = options.healthCheckTimeout || 5000; // 5 seconds
|
|
11
|
+
this.healthCheckPath = options.healthCheckPath || '/health';
|
|
12
|
+
this.enabled = options.enabled !== false; // Enabled by default
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Register a service with multiple endpoints (for load balancing)
|
|
17
|
+
* @param {string} name - Service name
|
|
18
|
+
* @param {string|Array} urls - Service URL(s)
|
|
19
|
+
*/
|
|
20
|
+
register(name, urls) {
|
|
21
|
+
const urlArray = Array.isArray(urls) ? urls : [urls];
|
|
22
|
+
this.services.set(name, {
|
|
23
|
+
urls: urlArray,
|
|
24
|
+
currentIndex: 0,
|
|
25
|
+
health: {},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (this.enabled) {
|
|
29
|
+
this._startHealthCheck(name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get next available service URL (round-robin)
|
|
35
|
+
* @param {string} name - Service name
|
|
36
|
+
* @returns {string|null} - Service URL or null if not found
|
|
37
|
+
*/
|
|
38
|
+
getNext(name) {
|
|
39
|
+
const service = this.services.get(name);
|
|
40
|
+
if (!service || service.urls.length === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Simple round-robin
|
|
45
|
+
const url = service.urls[service.currentIndex];
|
|
46
|
+
service.currentIndex = (service.currentIndex + 1) % service.urls.length;
|
|
47
|
+
|
|
48
|
+
return url;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get all healthy URLs for a service
|
|
53
|
+
* @param {string} name - Service name
|
|
54
|
+
* @returns {Array} - Array of healthy URLs
|
|
55
|
+
*/
|
|
56
|
+
getHealthy(name) {
|
|
57
|
+
const service = this.services.get(name);
|
|
58
|
+
if (!service) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return service.urls.filter(url => {
|
|
63
|
+
const health = service.health[url];
|
|
64
|
+
return !health || health.status === 'healthy' || Date.now() - health.lastCheck > this.healthCheckInterval * 2;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Mark service URL as unhealthy
|
|
70
|
+
* @param {string} name - Service name
|
|
71
|
+
* @param {string} url - Service URL
|
|
72
|
+
*/
|
|
73
|
+
markUnhealthy(name, url) {
|
|
74
|
+
const service = this.services.get(name);
|
|
75
|
+
if (service) {
|
|
76
|
+
service.health[url] = {
|
|
77
|
+
status: 'unhealthy',
|
|
78
|
+
lastCheck: Date.now(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mark service URL as healthy
|
|
85
|
+
* @param {string} name - Service name
|
|
86
|
+
* @param {string} url - Service URL
|
|
87
|
+
*/
|
|
88
|
+
markHealthy(name, url) {
|
|
89
|
+
const service = this.services.get(name);
|
|
90
|
+
if (service) {
|
|
91
|
+
service.health[url] = {
|
|
92
|
+
status: 'healthy',
|
|
93
|
+
lastCheck: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Start health check for a service
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
_startHealthCheck(name) {
|
|
103
|
+
const service = this.services.get(name);
|
|
104
|
+
if (!service) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check health for each URL
|
|
109
|
+
service.urls.forEach(url => {
|
|
110
|
+
this._checkHealth(name, url);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Schedule next health check
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
this._startHealthCheck(name);
|
|
116
|
+
}, this.healthCheckInterval);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check health of a service URL
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
async _checkHealth(name, url) {
|
|
124
|
+
try {
|
|
125
|
+
const http = require('http');
|
|
126
|
+
const https = require('https');
|
|
127
|
+
const parsedUrl = new URL(url);
|
|
128
|
+
const client = parsedUrl.protocol === 'https:' ? https : http;
|
|
129
|
+
|
|
130
|
+
const healthUrl = new URL(this.healthCheckPath, url);
|
|
131
|
+
|
|
132
|
+
const result = await new Promise((resolve, reject) => {
|
|
133
|
+
const req = client.request({
|
|
134
|
+
hostname: parsedUrl.hostname,
|
|
135
|
+
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
136
|
+
path: healthUrl.pathname,
|
|
137
|
+
method: 'GET',
|
|
138
|
+
timeout: this.healthCheckTimeout,
|
|
139
|
+
}, (res) => {
|
|
140
|
+
let body = '';
|
|
141
|
+
res.on('data', chunk => { body += chunk; });
|
|
142
|
+
res.on('end', () => {
|
|
143
|
+
resolve({ statusCode: res.statusCode, body });
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
req.on('error', reject);
|
|
148
|
+
req.on('timeout', () => {
|
|
149
|
+
req.destroy();
|
|
150
|
+
reject(new Error('Health check timeout'));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
req.end();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (result.statusCode === 200) {
|
|
157
|
+
this.markHealthy(name, url);
|
|
158
|
+
} else {
|
|
159
|
+
this.markUnhealthy(name, url);
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.markUnhealthy(name, url);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Unregister a service
|
|
168
|
+
* @param {string} name - Service name
|
|
169
|
+
*/
|
|
170
|
+
unregister(name) {
|
|
171
|
+
this.services.delete(name);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get all registered services
|
|
176
|
+
* @returns {Array} - Array of service names
|
|
177
|
+
*/
|
|
178
|
+
list() {
|
|
179
|
+
return Array.from(this.services.keys());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = ServiceDiscovery;
|
|
184
|
+
|