@onlineapps/conn-orch-registry 1.1.4
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/LICENSE +22 -0
- package/README.md +151 -0
- package/docs/REGISTRY_CLIENT_GUIDE.md +240 -0
- package/examples/basicUsage.js +85 -0
- package/examples/event-consumer-example.js +108 -0
- package/package.json +66 -0
- package/src/config.js +52 -0
- package/src/events.js +28 -0
- package/src/index.js +37 -0
- package/src/queueManager.js +88 -0
- package/src/registryClient.js +397 -0
- package/src/registryEventConsumer.js +422 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ONLINE APPS s.r.o.; info@onlineapps.cz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @onlineapps/connector-registry-client
|
|
2
|
+
|
|
3
|
+
[](https://github.com/onlineapps/connector-registry-client/actions)
|
|
4
|
+
[](https://codecov.io/gh/onlineapps/connector-registry-client)
|
|
5
|
+
[](https://www.npmjs.com/package/@onlineapps/connector-registry-client)
|
|
6
|
+
|
|
7
|
+
> A lightweight client for microservice registration, heartbeat, and API description exchange via RabbitMQ.
|
|
8
|
+
|
|
9
|
+
## 🚀 Features
|
|
10
|
+
|
|
11
|
+
* Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `registry_office`)
|
|
12
|
+
* Periodic heartbeat messages with metadata
|
|
13
|
+
* API description request/response flow
|
|
14
|
+
* Event-driven API using `EventEmitter`
|
|
15
|
+
* Fully configurable via environment variables or constructor options
|
|
16
|
+
|
|
17
|
+
## 📦 Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @onlineapps/connector-registry-client
|
|
21
|
+
# or
|
|
22
|
+
yarn add @onlineapps/connector-registry-client
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🔧 Quick Start
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
// Load environment, if using .env
|
|
29
|
+
require('dotenv').config();
|
|
30
|
+
|
|
31
|
+
const { ServiceRegistryClient, EVENTS } = require('@onlineapps/connector-registry-client');
|
|
32
|
+
|
|
33
|
+
const client = new ServiceRegistryClient({
|
|
34
|
+
amqpUrl: process.env.AMQP_URL,
|
|
35
|
+
serviceName: 'invoicing',
|
|
36
|
+
version: '1.0.0'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Listen for API description requests
|
|
40
|
+
client.on(EVENTS.API_DESCRIPTION_REQUEST, async () => {
|
|
41
|
+
const apiSpec = await loadOpenApiSpec();
|
|
42
|
+
await client.sendApiDescription(apiSpec);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Initialize and start heartbeats
|
|
46
|
+
await client.init();
|
|
47
|
+
client.startHeartbeat();
|
|
48
|
+
|
|
49
|
+
// Graceful shutdown
|
|
50
|
+
process.on('SIGINT', async () => {
|
|
51
|
+
await client.close();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 📄 Configuration
|
|
57
|
+
|
|
58
|
+
Configuration can be provided via environment variables or constructor options:
|
|
59
|
+
|
|
60
|
+
| Variable | Description | Default |
|
|
61
|
+
| -------------------- | --------------------------------------------- | -------------------- |
|
|
62
|
+
| `AMQP_URL` | RabbitMQ connection string (required) | — |
|
|
63
|
+
| `SERVICE_NAME` | Logical name of your service (required) | — |
|
|
64
|
+
| `SERVICE_VERSION` | Service version in SemVer format (required) | — |
|
|
65
|
+
| `HEARTBEAT_INTERVAL` | Interval in ms between heartbeats | `10000` |
|
|
66
|
+
| `API_QUEUE` | Queue name for heartbeat/API messages | `api_services_queuer` |
|
|
67
|
+
| `REGISTRY_QUEUE` | Queue name for registry requests/descriptions | `registry_office` |
|
|
68
|
+
|
|
69
|
+
## 📨 Message Formats
|
|
70
|
+
|
|
71
|
+
### Register Request
|
|
72
|
+
```javascript
|
|
73
|
+
{
|
|
74
|
+
type: "register",
|
|
75
|
+
serviceName: "hello-service",
|
|
76
|
+
version: "1.0.0",
|
|
77
|
+
token: "pre-validation-token",
|
|
78
|
+
endpoints: [...],
|
|
79
|
+
timestamp: "ISO-8601"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Register Confirmed
|
|
84
|
+
```javascript
|
|
85
|
+
{
|
|
86
|
+
type: "register.confirmed",
|
|
87
|
+
serviceName: "hello-service",
|
|
88
|
+
success: true,
|
|
89
|
+
certificate: {
|
|
90
|
+
id: "cert-uuid",
|
|
91
|
+
signature: "cryptographic-signature",
|
|
92
|
+
validUntil: "ISO-8601"
|
|
93
|
+
},
|
|
94
|
+
cached: false
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Heartbeat
|
|
99
|
+
```javascript
|
|
100
|
+
{
|
|
101
|
+
type: "heartbeat",
|
|
102
|
+
serviceName: "hello-service",
|
|
103
|
+
status: "UP",
|
|
104
|
+
timestamp: "ISO-8601"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 🛠️ API Reference
|
|
109
|
+
|
|
110
|
+
See [docs/api.md](https://github.com/onlineapps/connector-registry-client/blob/main/docs/api.md) for full details on classes, methods, and events.
|
|
111
|
+
|
|
112
|
+
## 📖 Documentation
|
|
113
|
+
|
|
114
|
+
* Architecture overview: [docs/architecture.md](https://github.com/onlineapps/connector-registry-client/blob/main/docs/architecture.md)
|
|
115
|
+
* API reference: [docs/api.md](https://github.com/onlineapps/connector-registry-client/blob/main/docs/api.md)
|
|
116
|
+
* Examples: [examples/basicUsage.js](https://github.com/onlineapps/agent-registry-client/blob/main/examples/basicUsage.js)
|
|
117
|
+
|
|
118
|
+
## 🔗 Related Documentation
|
|
119
|
+
|
|
120
|
+
- **[⭐ Complete Two-Tier Validation System](/docs/modules/validator.md)** - Full validation architecture and flow
|
|
121
|
+
- [Registry Module Overview](/docs/modules/registry.md) - High-level registry system architecture
|
|
122
|
+
- [api_services_registry](/api/api_services_registry/README.md) - Registry service implementation
|
|
123
|
+
|
|
124
|
+
## ✅ Testing
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Run unit and integration tests
|
|
128
|
+
yarn test
|
|
129
|
+
# or
|
|
130
|
+
npm test
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 🎨 Coding Standards
|
|
134
|
+
|
|
135
|
+
* **Linting**: ESLint (Airbnb style)
|
|
136
|
+
* **Formatting**: Prettier
|
|
137
|
+
* **Testing**: Jest
|
|
138
|
+
|
|
139
|
+
## 🤝 Contributing
|
|
140
|
+
|
|
141
|
+
Please read [CONTRIBUTING.md](https://github.com/onlineapps/agent-registry-client/blob/main/CONTRIBUTING.md) for details on submitting issues and pull requests.
|
|
142
|
+
|
|
143
|
+
## 📜 License
|
|
144
|
+
|
|
145
|
+
This project is licensed under the MIT License. See [LICENSE](https://github.com/onlineapps/agent-registry-client/blob/main/LICENSE) for details.
|
|
146
|
+
|
|
147
|
+
## 📚 Documentation
|
|
148
|
+
|
|
149
|
+
- [Complete Connectors Documentation](../../../docs/modules/connector.md)
|
|
150
|
+
- [Testing Standards](../../../docs/modules/connector.md#testing-standards)
|
|
151
|
+
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Registry Client Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `ServiceRegistryClient` is a connector library that enables microservices to communicate with the API Services Registry. It handles service registration, validation, heartbeats, and specification sharing.
|
|
6
|
+
|
|
7
|
+
## Registration Flow
|
|
8
|
+
|
|
9
|
+
### New Registration Process (v1.1+)
|
|
10
|
+
|
|
11
|
+
The registration flow has been updated to include validation before activation:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Microservice Registry Backend
|
|
15
|
+
| | |
|
|
16
|
+
|-- init() --------------->| |
|
|
17
|
+
| (setup connection) | |
|
|
18
|
+
| | |
|
|
19
|
+
|-- register() ----------->| |
|
|
20
|
+
| (send service info) | |
|
|
21
|
+
| |-- validate() --------->|
|
|
22
|
+
| | (check if tested) |
|
|
23
|
+
| |<-- validation result --|
|
|
24
|
+
| | |
|
|
25
|
+
|<-- registration result --| |
|
|
26
|
+
| | |
|
|
27
|
+
|-- startHeartbeat() ----->| |
|
|
28
|
+
| (if validated OK) | |
|
|
29
|
+
| | |
|
|
30
|
+
|-- sendApiDescription() ->| |
|
|
31
|
+
| (on request) | |
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### 1. Basic Setup
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
const { ServiceRegistryClient } = require('@onlineapps/connector-registry-client');
|
|
40
|
+
|
|
41
|
+
const registryClient = new ServiceRegistryClient({
|
|
42
|
+
amqpUrl: 'amqp://localhost:5672',
|
|
43
|
+
serviceName: 'my-service',
|
|
44
|
+
version: '1.0.0',
|
|
45
|
+
specificationEndpoint: '/api/v1/specification',
|
|
46
|
+
heartbeatInterval: 10000
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Service Initialization and Registration
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
async function initializeService() {
|
|
54
|
+
// Step 1: Initialize connection
|
|
55
|
+
await registryClient.init();
|
|
56
|
+
|
|
57
|
+
// Step 2: Register service with registry
|
|
58
|
+
const registrationResult = await registryClient.register({
|
|
59
|
+
endpoints: [
|
|
60
|
+
{ path: '/api/users', method: 'GET' },
|
|
61
|
+
{ path: '/api/users', method: 'POST' },
|
|
62
|
+
{ path: '/api/users/:id', method: 'GET' },
|
|
63
|
+
{ path: '/api/users/:id', method: 'PUT' },
|
|
64
|
+
{ path: '/api/users/:id', method: 'DELETE' }
|
|
65
|
+
],
|
|
66
|
+
metadata: {
|
|
67
|
+
description: 'User management service',
|
|
68
|
+
author: 'Development Team',
|
|
69
|
+
documentation: 'https://docs.example.com/users'
|
|
70
|
+
},
|
|
71
|
+
health: '/health',
|
|
72
|
+
spec: openApiSpec // Optional: OpenAPI specification object
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Step 3: If registration successful, start heartbeat
|
|
76
|
+
if (registrationResult.success) {
|
|
77
|
+
registryClient.startHeartbeat();
|
|
78
|
+
console.log('Service registered and activated');
|
|
79
|
+
} else {
|
|
80
|
+
console.error('Registration failed:', registrationResult.message);
|
|
81
|
+
// Handle registration failure (service not tested, invalid config, etc.)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Handling API Description Requests
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Listen for requests to send API specification
|
|
90
|
+
registryClient.on('apiDescriptionRequest', async (request) => {
|
|
91
|
+
const apiSpec = await loadApiSpecification();
|
|
92
|
+
await registryClient.sendApiDescription(apiSpec);
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 4. Event Subscription (Optional)
|
|
97
|
+
|
|
98
|
+
For services that need to be notified about registry changes:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
await registryClient.subscribeToChanges();
|
|
102
|
+
|
|
103
|
+
// Will receive events when services are added/removed/updated
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 5. Cleanup
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
async function shutdown() {
|
|
110
|
+
registryClient.stopHeartbeat();
|
|
111
|
+
await registryClient.close();
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## API Reference
|
|
116
|
+
|
|
117
|
+
### Constructor Options
|
|
118
|
+
|
|
119
|
+
| Option | Type | Default | Description |
|
|
120
|
+
|--------|------|---------|-------------|
|
|
121
|
+
| `amqpUrl` | string | required | AMQP connection URL |
|
|
122
|
+
| `serviceName` | string | required | Unique service identifier |
|
|
123
|
+
| `version` | string | required | Service version (semver) |
|
|
124
|
+
| `specificationEndpoint` | string | '/api/v1/specification' | Endpoint for API spec |
|
|
125
|
+
| `heartbeatInterval` | number | 10000 | Heartbeat interval in ms |
|
|
126
|
+
| `apiQueue` | string | 'api_services_queuer' | Queue for API traffic |
|
|
127
|
+
| `registryQueue` | string | 'registry_office' | Registry queue name |
|
|
128
|
+
|
|
129
|
+
### Methods
|
|
130
|
+
|
|
131
|
+
#### `async init()`
|
|
132
|
+
Initializes the connection to RabbitMQ and sets up queues.
|
|
133
|
+
|
|
134
|
+
#### `async register(serviceInfo)`
|
|
135
|
+
Registers the service with the registry. The registry will validate that the service is properly tested and approved before activation.
|
|
136
|
+
|
|
137
|
+
**Parameters:**
|
|
138
|
+
- `serviceInfo.endpoints` - Array of API endpoints
|
|
139
|
+
- `serviceInfo.metadata` - Service metadata object
|
|
140
|
+
- `serviceInfo.health` - Health check endpoint
|
|
141
|
+
- `serviceInfo.spec` - OpenAPI specification (optional)
|
|
142
|
+
|
|
143
|
+
**Returns:** Registration result object with `success` status
|
|
144
|
+
|
|
145
|
+
#### `startHeartbeat()`
|
|
146
|
+
Starts sending periodic heartbeat messages. Should only be called after successful registration.
|
|
147
|
+
|
|
148
|
+
#### `stopHeartbeat()`
|
|
149
|
+
Stops the heartbeat timer.
|
|
150
|
+
|
|
151
|
+
#### `async sendApiDescription(description)`
|
|
152
|
+
Sends the API specification to the registry when requested.
|
|
153
|
+
|
|
154
|
+
#### `async subscribeToChanges()`
|
|
155
|
+
Subscribes to registry change events (optional).
|
|
156
|
+
|
|
157
|
+
#### `async close()`
|
|
158
|
+
Closes all connections and cleans up resources.
|
|
159
|
+
|
|
160
|
+
### Events
|
|
161
|
+
|
|
162
|
+
| Event | Payload | Description |
|
|
163
|
+
|-------|---------|-------------|
|
|
164
|
+
| `registerSent` | Registration message | Emitted when registration is sent |
|
|
165
|
+
| `heartbeatSent` | Heartbeat message | Emitted on each heartbeat |
|
|
166
|
+
| `apiDescriptionRequest` | Request details | Registry requests API spec |
|
|
167
|
+
| `apiDescriptionSent` | Description message | API spec was sent |
|
|
168
|
+
| `error` | Error object | Error occurred |
|
|
169
|
+
|
|
170
|
+
## Registration Validation
|
|
171
|
+
|
|
172
|
+
The registry performs several validation checks during registration:
|
|
173
|
+
|
|
174
|
+
1. **Service Testing Status** - Verifies the service has passed required tests
|
|
175
|
+
2. **Version Compatibility** - Checks version against registry requirements
|
|
176
|
+
3. **Endpoint Validation** - Ensures endpoints follow API standards
|
|
177
|
+
4. **Dependencies Check** - Validates required services are available
|
|
178
|
+
|
|
179
|
+
Only after passing all validation checks will the service be activated and allowed to send heartbeats.
|
|
180
|
+
|
|
181
|
+
## Migration from v1.0
|
|
182
|
+
|
|
183
|
+
If you're upgrading from v1.0 where services immediately started heartbeats:
|
|
184
|
+
|
|
185
|
+
**Old flow (v1.0):**
|
|
186
|
+
```javascript
|
|
187
|
+
await registryClient.init();
|
|
188
|
+
registryClient.startHeartbeat(); // Started immediately
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**New flow (v1.1+):**
|
|
192
|
+
```javascript
|
|
193
|
+
await registryClient.init();
|
|
194
|
+
const result = await registryClient.register(serviceInfo); // NEW: Registration required
|
|
195
|
+
if (result.success) {
|
|
196
|
+
registryClient.startHeartbeat(); // Only after validation
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Troubleshooting
|
|
201
|
+
|
|
202
|
+
### Registration Fails
|
|
203
|
+
|
|
204
|
+
If registration fails, check:
|
|
205
|
+
- Service has passed all required tests
|
|
206
|
+
- Version number is correctly formatted
|
|
207
|
+
- All required endpoints are documented
|
|
208
|
+
- Service metadata is complete
|
|
209
|
+
|
|
210
|
+
### No Heartbeats Sent
|
|
211
|
+
|
|
212
|
+
Ensure:
|
|
213
|
+
- Registration was successful before starting heartbeats
|
|
214
|
+
- RabbitMQ connection is stable
|
|
215
|
+
- Correct queue names are configured
|
|
216
|
+
|
|
217
|
+
### API Description Not Received
|
|
218
|
+
|
|
219
|
+
Verify:
|
|
220
|
+
- Event listener is properly set up
|
|
221
|
+
- Service name and version match exactly
|
|
222
|
+
- Queue consumption is active
|
|
223
|
+
|
|
224
|
+
## Testing
|
|
225
|
+
|
|
226
|
+
### Unit Tests
|
|
227
|
+
```bash
|
|
228
|
+
npm test:unit
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Integration Tests
|
|
232
|
+
```bash
|
|
233
|
+
# Requires RabbitMQ and MinIO running
|
|
234
|
+
npm test:integration
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Full Test Suite
|
|
238
|
+
```bash
|
|
239
|
+
npm test
|
|
240
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* basicUsage.js
|
|
3
|
+
*
|
|
4
|
+
* Example demonstrating full lifecycle of ServiceRegistryClient:
|
|
5
|
+
* 1. Load environment variables
|
|
6
|
+
* 2. Instantiate client
|
|
7
|
+
* 3. Initialize (setup queues and start listening for requests)
|
|
8
|
+
* 4. Register event listeners (apiDescriptionRequest, heartbeatSent, apiDescriptionSent, error)
|
|
9
|
+
* 5. Start heartbeat loop
|
|
10
|
+
* 6. Graceful shutdown on process signals
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Load environment variables from .env file
|
|
14
|
+
require('dotenv').config();
|
|
15
|
+
|
|
16
|
+
const { ServiceRegistryClient, EVENTS } = require('@onlineapps/connector-registry-client');
|
|
17
|
+
|
|
18
|
+
// Step 1: Create a new ServiceRegistryClient instance
|
|
19
|
+
const registryClient = new ServiceRegistryClient({
|
|
20
|
+
amqpUrl: process.env.AMQP_URL,
|
|
21
|
+
serviceName: process.env.SERVICE_NAME || 'invoicing',
|
|
22
|
+
version: process.env.SERVICE_VERSION || '1.0.0',
|
|
23
|
+
specificationEndpoint: process.env.SPECIFICATION_ENDPOINT || '/api/v1/specification',
|
|
24
|
+
heartbeatInterval: parseInt(process.env.HEARTBEAT_INTERVAL, 10) || 10000,
|
|
25
|
+
apiQueue: process.env.API_QUEUE,
|
|
26
|
+
registryQueue: process.env.REGISTRY_QUEUE
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Step 2: Register event listeners
|
|
30
|
+
registryClient.on(EVENTS.HEARTBEAT_SENT, (msg) => {
|
|
31
|
+
console.log(`[Heartbeat] Sent at ${msg.timestamp} for ${msg.serviceName}@${msg.version}`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
registryClient.on(EVENTS.API_DESCRIPTION_REQUEST, async (payload) => {
|
|
35
|
+
console.log(`[API Description Request] for ${payload.serviceName}@${payload.version}`);
|
|
36
|
+
// Load local API description (from file or in-memory)
|
|
37
|
+
const apiDesc = await loadLocalApiDescription();
|
|
38
|
+
await registryClient.sendApiDescription(apiDesc);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
registryClient.on(EVENTS.API_DESCRIPTION_SENT, (msg) => {
|
|
42
|
+
console.log(`[API Description Sent] at ${msg.timestamp} for ${msg.serviceName}@${msg.version}`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
registryClient.on(EVENTS.ERROR, (err) => {
|
|
46
|
+
console.error('[RegistryClient Error]', err);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
(async () => {
|
|
50
|
+
try {
|
|
51
|
+
// Step 3: Initialize client (setup queues and listeners)
|
|
52
|
+
await registryClient.init();
|
|
53
|
+
console.log('RegistryClient initialized: queues are ready.');
|
|
54
|
+
|
|
55
|
+
// Step 4: Start heartbeat loop
|
|
56
|
+
registryClient.startHeartbeat();
|
|
57
|
+
console.log('Heartbeat loop started.');
|
|
58
|
+
|
|
59
|
+
// Step 5: Graceful shutdown on SIGINT/SIGTERM
|
|
60
|
+
const shutdown = async () => {
|
|
61
|
+
console.log('Shutting down RegistryClient...');
|
|
62
|
+
await registryClient.close();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
};
|
|
65
|
+
process.on('SIGINT', shutdown);
|
|
66
|
+
process.on('SIGTERM', shutdown);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error('Failed to initialize RegistryClient', err);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Helper: Load local API description
|
|
75
|
+
* In a real application, this could read from a JSON file,
|
|
76
|
+
* introspect OpenAPI spec, or build dynamically.
|
|
77
|
+
*/
|
|
78
|
+
async function loadLocalApiDescription() {
|
|
79
|
+
return {
|
|
80
|
+
endpoints: [
|
|
81
|
+
{ path: '/invoices', method: 'POST', description: 'Create a new invoice' },
|
|
82
|
+
{ path: '/invoices/:id', method: 'GET', description: 'Retrieve invoice by ID' }
|
|
83
|
+
]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Using ServiceRegistryClient with event consumption
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates opt-in subscription to registry change events
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { ServiceRegistryClient } = require('../src/index');
|
|
8
|
+
const Redis = require('ioredis');
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
// Optional Redis for index persistence
|
|
12
|
+
const redis = new Redis({
|
|
13
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
14
|
+
port: process.env.REDIS_PORT || 6379
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Initialize client with storage configuration
|
|
18
|
+
const client = new ServiceRegistryClient({
|
|
19
|
+
amqpUrl: process.env.AMQP_URL || 'amqp://localhost',
|
|
20
|
+
serviceName: 'example-service',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
redis: redis,
|
|
23
|
+
storageConfig: {
|
|
24
|
+
endPoint: process.env.MINIO_ENDPOINT || 'localhost',
|
|
25
|
+
port: parseInt(process.env.MINIO_PORT || 9000),
|
|
26
|
+
useSSL: process.env.MINIO_USE_SSL === 'true',
|
|
27
|
+
accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
|
|
28
|
+
secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin'
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Initialize connection
|
|
33
|
+
await client.init();
|
|
34
|
+
|
|
35
|
+
// Register ourselves with registry
|
|
36
|
+
await client.sendApiDescription({
|
|
37
|
+
openapi: '3.0.0',
|
|
38
|
+
info: {
|
|
39
|
+
title: 'Example Service',
|
|
40
|
+
version: '1.0.0'
|
|
41
|
+
},
|
|
42
|
+
paths: {
|
|
43
|
+
'/hello': {
|
|
44
|
+
get: {
|
|
45
|
+
summary: 'Say hello',
|
|
46
|
+
responses: {
|
|
47
|
+
'200': {
|
|
48
|
+
description: 'Success'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Start heartbeat
|
|
57
|
+
client.startHeartbeat();
|
|
58
|
+
|
|
59
|
+
// OPT-IN: Subscribe to registry changes
|
|
60
|
+
console.log('Subscribing to registry changes...');
|
|
61
|
+
await client.subscribeToChanges();
|
|
62
|
+
|
|
63
|
+
// Listen for events
|
|
64
|
+
client.on('serviceUpdated', ({ service, fingerprint, version }) => {
|
|
65
|
+
console.log(`Service updated: ${service} v${version} (${fingerprint})`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
client.on('statusChanged', ({ service, status }) => {
|
|
69
|
+
console.log(`Service status changed: ${service} -> ${status}`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
client.on('snapshotReceived', ({ count }) => {
|
|
73
|
+
console.log(`Received snapshot with ${count} services`);
|
|
74
|
+
|
|
75
|
+
// Show active services
|
|
76
|
+
const activeServices = client.getActiveServices();
|
|
77
|
+
console.log('Active services:', activeServices);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Example: Check if another service is active
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
if (client.isServiceActive('invoicing')) {
|
|
83
|
+
console.log('Invoicing service is active');
|
|
84
|
+
|
|
85
|
+
// Load its spec
|
|
86
|
+
client.getServiceSpec('invoicing')
|
|
87
|
+
.then(spec => {
|
|
88
|
+
console.log('Invoicing API:', spec.info?.title);
|
|
89
|
+
})
|
|
90
|
+
.catch(err => {
|
|
91
|
+
console.error('Failed to load invoicing spec:', err.message);
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
console.log('Invoicing service is not active');
|
|
95
|
+
}
|
|
96
|
+
}, 5000);
|
|
97
|
+
|
|
98
|
+
// Graceful shutdown
|
|
99
|
+
process.on('SIGINT', async () => {
|
|
100
|
+
console.log('\nShutting down...');
|
|
101
|
+
await client.close();
|
|
102
|
+
await redis.quit();
|
|
103
|
+
process.exit(0);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Run example
|
|
108
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onlineapps/conn-orch-registry",
|
|
3
|
+
"version": "1.1.4",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Connector-registry-client provides the core communication mechanism for microservices in this environment. It enables them to interact with a services_registry to receive and fulfill tasks by submitting heartbeats or their API descriptions.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"microservice",
|
|
8
|
+
"connector",
|
|
9
|
+
"registry",
|
|
10
|
+
"rabbitmq",
|
|
11
|
+
"heartbeat",
|
|
12
|
+
"api",
|
|
13
|
+
"environment",
|
|
14
|
+
"nodejs"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"src/",
|
|
18
|
+
"docs/",
|
|
19
|
+
"examples/",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"CONTRIBUTING.md"
|
|
23
|
+
],
|
|
24
|
+
"main": "src/index.js",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "jest --coverage",
|
|
27
|
+
"test:unit": "jest --testPathPattern=unit --coverage",
|
|
28
|
+
"test:component": "jest --testPathPattern=component --coverage",
|
|
29
|
+
"test:integration": "jest --config=jest.integration.config.js",
|
|
30
|
+
"test:integration:required": "REQUIRE_SERVICES=true jest --config=jest.integration.config.js",
|
|
31
|
+
"test:watch": "jest --watch",
|
|
32
|
+
"test:coverage": "jest --coverage --coverageReporters=text-lcov html",
|
|
33
|
+
"semantic-release": "semantic-release",
|
|
34
|
+
"lint": "eslint src test examples --ext .js --cache --cache-location .eslintcache",
|
|
35
|
+
"docs": "jsdoc2md --files src/**/*.js > API.md"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@onlineapps/conn-base-storage": "file:../conn-base-storage",
|
|
42
|
+
"amqplib": "^0.10.3",
|
|
43
|
+
"axios": "^1.5.0",
|
|
44
|
+
"dotenv": "^16.1.4",
|
|
45
|
+
"ioredis": "^5.3.2",
|
|
46
|
+
"joi": "^17.9.2",
|
|
47
|
+
"uuid": "^9.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
51
|
+
"@semantic-release/commit-analyzer": "^11.1.0",
|
|
52
|
+
"@semantic-release/git": "^10.0.1",
|
|
53
|
+
"@semantic-release/npm": "^11.0.3",
|
|
54
|
+
"@semantic-release/release-notes-generator": "^12.1.0",
|
|
55
|
+
"eslint": "^8.44.0",
|
|
56
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
57
|
+
"eslint-config-prettier": "^10.1.5",
|
|
58
|
+
"eslint-plugin-import": "^2.30.1",
|
|
59
|
+
"eslint-plugin-prettier": "^5.4.0",
|
|
60
|
+
"jest": "^29.6.1",
|
|
61
|
+
"semantic-release": "^22.0.12"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=18"
|
|
65
|
+
}
|
|
66
|
+
}
|