@onlineapps/service-wrapper 2.0.7 → 2.0.8

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 CHANGED
@@ -1,7 +1,18 @@
1
1
  # @onlineapps/service-wrapper
2
2
 
3
+ **Main orchestration package for microservices - NO prefix as it's the primary integration point**
4
+
3
5
  Infrastructure wrapper that handles all workflow processing, message queue operations, and service registration for microservices.
4
6
 
7
+ ## Naming Convention
8
+
9
+ This package uses `@onlineapps/service-wrapper` WITHOUT any connector prefix because:
10
+ - It's the main orchestration layer, not a specific connector
11
+ - All services depend on it directly
12
+ - It aggregates all other connectors (base, infra, orch)
13
+
14
+ Directory: `/shared/connector/conn-app-service-wrapper/` (historical, kept for backward compatibility)
15
+
5
16
  ## Purpose
6
17
 
7
18
  This package provides the infrastructure layer that sits between:
@@ -0,0 +1,132 @@
1
+ # API Structure Standard for Microservices
2
+
3
+ ## Endpoint Structure
4
+
5
+ All microservices follow a simple, unversioned API structure.
6
+
7
+ ### URL Pattern
8
+
9
+ ```
10
+ / # Service discovery
11
+ /health # Health check
12
+ /status # Service status
13
+ /metrics # Prometheus metrics
14
+ /specification # Operations specification
15
+ /api/{operation} # Business operations
16
+ ```
17
+
18
+ ### Endpoint Categories
19
+
20
+ - **System endpoints**: Health, status, metrics, specification
21
+ - **Business endpoints** (`/api/*`): Application-specific operations
22
+
23
+ ## Standard Endpoints
24
+
25
+ ### Discovery Endpoint
26
+
27
+ **GET /**
28
+ ```json
29
+ {
30
+ "service": "service-name",
31
+ "version": "1.0.0",
32
+ "description": "Service description",
33
+ "specification": "/specification",
34
+ "health": "/health",
35
+ "status": "/status"
36
+ }
37
+ ```
38
+
39
+ ### System Endpoints
40
+
41
+ | Endpoint | Purpose | Response |
42
+ |----------|---------|----------|
43
+ | `GET /health` | Basic health check | `{"status": "healthy", "timestamp": "..."}` |
44
+ | `GET /status` | Detailed service status | Service metrics and dependencies |
45
+ | `GET /specification` | Operations specification | Operations JSON schema |
46
+ | `GET /metrics` | Prometheus metrics | Metrics in Prometheus format |
47
+
48
+ ### Business Endpoints
49
+
50
+ Service-specific operations under `/api/*`:
51
+ - `POST /api/create-user`
52
+ - `POST /api/process-order`
53
+ - `GET /api/get-product`
54
+
55
+ ## Implementation Guide
56
+
57
+ ### Express.js Structure
58
+
59
+ ```javascript
60
+ const express = require('express');
61
+ const app = express();
62
+
63
+ // Root endpoint - service discovery
64
+ app.get('/', (req, res) => {
65
+ res.json({
66
+ service: process.env.SERVICE_NAME,
67
+ version: require('./package.json').version,
68
+ specification: '/specification',
69
+ health: '/health',
70
+ status: '/status'
71
+ });
72
+ });
73
+
74
+ // System endpoints
75
+ app.get('/health', healthHandler);
76
+ app.get('/status', statusHandler);
77
+ app.get('/specification', specificationHandler);
78
+ app.get('/metrics', metricsHandler);
79
+
80
+ // Business endpoints under /api
81
+ app.post('/api/create-user', createUserHandler);
82
+ app.post('/api/process-order', processOrderHandler);
83
+ app.get('/api/get-product', getProductHandler);
84
+ ```
85
+
86
+ ## Configuration Structure
87
+
88
+ All service configurations are stored in `conn-config/` directory:
89
+
90
+ ```
91
+ /your-service
92
+ /conn-config
93
+ /config.json # Main configuration
94
+ /operations.json # Operations specification
95
+ /middleware.json # Middleware configuration
96
+ ```
97
+
98
+ See [Configuration Guide](./CONFIGURATION_GUIDE.md) for details.
99
+
100
+ ## Migration Guide
101
+
102
+ ### From OpenAPI to Operations
103
+
104
+ | Old | New | Purpose |
105
+ |-----|-----|------|
106
+ | `/api/v1/specification` | `/specification` | Operations schema |
107
+ | `/v1/system/health` | `/health` | Health check |
108
+ | `/v1/system/status` | `/status` | Service status |
109
+ | `/v1/{operation}` | `/api/{operation}` | Business operations |
110
+
111
+ ### Simple Migration Steps
112
+
113
+ 1. Create `operations.json` from OpenAPI
114
+ 2. Remove version prefixes from endpoints
115
+ 3. Move business endpoints under `/api`
116
+ 4. Update service discovery response
117
+
118
+ ## Testing Requirements
119
+
120
+ Tests must verify:
121
+
122
+ 1. **Discovery endpoint** returns service info and specification link
123
+ 2. **System endpoints** work correctly
124
+ 3. **Business endpoints** under `/api` process operations
125
+ 4. **Operations schema** is valid and complete
126
+
127
+ ## Benefits
128
+
129
+ 1. **Simplicity** - No version complexity
130
+ 2. **Clear structure** - System vs business separation
131
+ 3. **Direct mapping** - Operations to Cookbook steps
132
+ 4. **Lightweight** - Minimal configuration needed
@@ -0,0 +1,261 @@
1
+ # Service Configuration Guide
2
+
3
+ ## Configuration Directory Structure
4
+
5
+ Every service must have a `conn-config/` directory with these configuration files:
6
+
7
+ ```
8
+ /service-root
9
+ /conn-config
10
+ config.json # Main service configuration
11
+ operations.json # Operations specification
12
+ middleware.json # Middleware stack configuration
13
+ ```
14
+
15
+ ## Configuration Files
16
+
17
+ ### 1. config.json - Main Configuration
18
+
19
+ Primary service configuration with environment overrides:
20
+
21
+ ```json
22
+ {
23
+ "service": {
24
+ "name": "hello-service",
25
+ "version": "1.0.0",
26
+ "description": "Greeting service",
27
+ "environment": "${NODE_ENV:development}"
28
+ },
29
+ "server": {
30
+ "port": "${PORT:3000}",
31
+ "host": "${HOST:0.0.0.0}",
32
+ "timeout": 30000
33
+ },
34
+ "api": {
35
+ "versions": {
36
+ "available": ["v1"],
37
+ "current": "v1",
38
+ "deprecated": []
39
+ },
40
+ "rateLimit": {
41
+ "enabled": true,
42
+ "windowMs": 60000,
43
+ "max": 100
44
+ }
45
+ },
46
+ "monitoring": {
47
+ "healthCheck": {
48
+ "interval": 30000,
49
+ "timeout": 5000
50
+ },
51
+ "metrics": {
52
+ "enabled": "${METRICS_ENABLED:true}",
53
+ "port": "${METRICS_PORT:9090}"
54
+ }
55
+ },
56
+ "dependencies": {
57
+ "rabbitmq": {
58
+ "url": "${RABBITMQ_URL:amqp://localhost:5672}",
59
+ "enabled": "${MQ_ENABLED:true}"
60
+ },
61
+ "redis": {
62
+ "url": "${REDIS_URL:redis://localhost:6379}",
63
+ "enabled": "${CACHE_ENABLED:false}"
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### 2. operations.json - Service Operations
70
+
71
+ Defines all operations the service provides:
72
+
73
+ ```json
74
+ {
75
+ "operations": {
76
+ "create-greeting": {
77
+ "description": "Generate a greeting message",
78
+ "handler": "handlers.greetings.create",
79
+ "endpoint": "/api/create-greeting",
80
+ "method": "POST",
81
+ "input": {
82
+ "name": {
83
+ "type": "string",
84
+ "required": true,
85
+ "description": "Name to greet"
86
+ }
87
+ },
88
+ "output": {
89
+ "message": {
90
+ "type": "string",
91
+ "description": "Generated greeting"
92
+ },
93
+ "timestamp": {
94
+ "type": "datetime"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### 3. middleware.json - Middleware Configuration
103
+
104
+ Middleware stack and configuration:
105
+
106
+ ```json
107
+ {
108
+ "global": [
109
+ {
110
+ "name": "cors",
111
+ "enabled": true,
112
+ "config": {
113
+ "origin": "${CORS_ORIGIN:*}",
114
+ "credentials": true
115
+ }
116
+ },
117
+ {
118
+ "name": "helmet",
119
+ "enabled": true,
120
+ "config": {}
121
+ },
122
+ {
123
+ "name": "requestLogger",
124
+ "enabled": true,
125
+ "config": {
126
+ "level": "info"
127
+ }
128
+ },
129
+ {
130
+ "name": "bodyParser",
131
+ "enabled": true,
132
+ "config": {
133
+ "limit": "10mb"
134
+ }
135
+ }
136
+ ],
137
+ "routes": {
138
+ "authenticate": {
139
+ "handler": "middleware/auth.authenticate",
140
+ "config": {
141
+ "required": true
142
+ }
143
+ },
144
+ "authorize": {
145
+ "handler": "middleware/auth.authorize",
146
+ "config": {
147
+ "roles": ["admin", "user"]
148
+ }
149
+ },
150
+ "validate": {
151
+ "handler": "middleware/validation.validate"
152
+ },
153
+ "rateLimit": {
154
+ "handler": "middleware/rateLimit",
155
+ "config": {
156
+ "max": 100,
157
+ "windowMs": 60000
158
+ }
159
+ }
160
+ },
161
+ "error": [
162
+ {
163
+ "name": "notFound",
164
+ "handler": "middleware/errors.notFound"
165
+ },
166
+ {
167
+ "name": "errorHandler",
168
+ "handler": "middleware/errors.handler"
169
+ }
170
+ ]
171
+ }
172
+ ```
173
+
174
+
175
+ ## Environment Variables
176
+
177
+ Configuration supports environment variable substitution:
178
+
179
+ - Format: `${VARIABLE_NAME:default_value}`
180
+ - Example: `${PORT:3000}` uses PORT env var or defaults to 3000
181
+
182
+ ### Standard Environment Variables
183
+
184
+ | Variable | Purpose | Default |
185
+ |----------|---------|---------|
186
+ | `NODE_ENV` | Environment (development/production) | development |
187
+ | `SERVICE_NAME` | Service name | From config.json |
188
+ | `PORT` | Server port | 3000 |
189
+ | `LOG_LEVEL` | Logging level | info |
190
+ | `RABBITMQ_URL` | RabbitMQ connection | amqp://localhost:5672 |
191
+ | `REDIS_URL` | Redis connection | redis://localhost:6379 |
192
+ | `METRICS_ENABLED` | Enable metrics | true |
193
+
194
+ ## Loading Configuration
195
+
196
+ Service wrapper automatically loads configuration:
197
+
198
+ ```javascript
199
+ const ServiceWrapper = require('@onlineapps/service-wrapper');
200
+
201
+ // Automatically loads from ./conn-config/
202
+ const wrapper = new ServiceWrapper();
203
+
204
+ // Or specify custom path
205
+ const wrapper = new ServiceWrapper({
206
+ configPath: './custom-config/'
207
+ });
208
+
209
+ // Access configuration
210
+ const config = wrapper.config;
211
+ console.log(config.service.name);
212
+ ```
213
+
214
+ ## Validation
215
+
216
+ Configuration is validated on startup:
217
+
218
+ 1. Required files exist
219
+ 2. JSON syntax is valid
220
+ 3. Required fields are present
221
+ 4. Environment variables are resolved
222
+ 5. Operations schema is valid
223
+
224
+ ## Best Practices
225
+
226
+ 1. **Keep sensitive data in environment variables** - Never commit secrets
227
+ 2. **Use defaults** - Provide sensible defaults for all configs
228
+ 3. **Document all options** - Include descriptions in configs
229
+ 4. **Validate early** - Fail fast on invalid configuration
230
+ 5. **Version your configs** - Track configuration changes
231
+
232
+ ## Service Configuration Example
233
+
234
+ ### config.json with Execution Mode
235
+ ```json
236
+ {
237
+ "service": {
238
+ "name": "hello-service",
239
+ "version": "1.0.0",
240
+ "execution": {
241
+ "mode": "${EXECUTION_MODE:direct}",
242
+ "direct": {
243
+ "handler": "./src/handlers"
244
+ },
245
+ "http": {
246
+ "baseUrl": "${SERVICE_URL:http://localhost:3000}"
247
+ }
248
+ }
249
+ },
250
+ "monitoring": {
251
+ "healthCheck": {
252
+ "interval": 30000
253
+ }
254
+ }
255
+ }
256
+ ```
257
+
258
+ ### Execution Modes
259
+
260
+ - **direct**: Service Wrapper calls handlers directly (50% memory savings)
261
+ - **http**: Service Wrapper makes HTTP calls (for external services)
@@ -0,0 +1,363 @@
1
+ # Operations Schema Specification
2
+
3
+ ## Overview
4
+
5
+ The Operations Schema replaces OpenAPI for internal microservices, providing a simpler, more focused specification for service operations that map directly to Cookbook workflow steps.
6
+
7
+ ## Why Operations Schema?
8
+
9
+ - **Simpler than OpenAPI** - Only what's needed for workflow orchestration
10
+ - **Direct Cookbook mapping** - Operations correspond 1:1 with workflow steps
11
+ - **Flexible execution** - Supports both direct function calls and HTTP
12
+ - **Extensible** - Easy to add support for external APIs (REST, GraphQL, gRPC)
13
+
14
+ ## File Location
15
+
16
+ Operations are defined in `/conn-config/operations.json` for each service.
17
+
18
+ ## Schema Structure
19
+
20
+ ### Basic Operation (Internal Service)
21
+
22
+ ```json
23
+ {
24
+ "operations": {
25
+ "operation-name": {
26
+ "description": "Human-readable description",
27
+ "handler": "path.to.handler.function",
28
+ "endpoint": "/api/operation-name",
29
+ "method": "POST",
30
+ "input": {
31
+ "field1": {
32
+ "type": "string",
33
+ "required": true,
34
+ "description": "Field description"
35
+ },
36
+ "field2": {
37
+ "type": "number",
38
+ "default": 0
39
+ }
40
+ },
41
+ "output": {
42
+ "result": {
43
+ "type": "string",
44
+ "description": "Result description"
45
+ },
46
+ "timestamp": {
47
+ "type": "datetime"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### External REST API Operation
56
+
57
+ ```json
58
+ {
59
+ "operations": {
60
+ "send-email": {
61
+ "description": "Send email via SendGrid",
62
+ "handler": "handlers.email.send",
63
+ "external": {
64
+ "type": "rest",
65
+ "endpoint": "https://api.sendgrid.com/v3/mail/send",
66
+ "method": "POST",
67
+ "headers": {
68
+ "Authorization": "Bearer ${SENDGRID_API_KEY}",
69
+ "Content-Type": "application/json"
70
+ },
71
+ "mapping": {
72
+ "input": {
73
+ "personalizations[0].to[0].email": "$.recipient",
74
+ "from.email": "${SENDER_EMAIL}",
75
+ "subject": "$.subject",
76
+ "content[0].value": "$.body"
77
+ },
78
+ "output": {
79
+ "messageId": "$.x-message-id",
80
+ "status": "$.status"
81
+ }
82
+ }
83
+ },
84
+ "input": {
85
+ "recipient": { "type": "string", "required": true },
86
+ "subject": { "type": "string", "required": true },
87
+ "body": { "type": "string", "required": true }
88
+ },
89
+ "output": {
90
+ "messageId": "string",
91
+ "status": "string"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### GraphQL Operation
99
+
100
+ ```json
101
+ {
102
+ "operations": {
103
+ "get-user-repos": {
104
+ "description": "Get user repositories from GitHub",
105
+ "external": {
106
+ "type": "graphql",
107
+ "endpoint": "https://api.github.com/graphql",
108
+ "headers": {
109
+ "Authorization": "Bearer ${GITHUB_TOKEN}"
110
+ },
111
+ "query": "query($login: String!) { user(login: $login) { repositories(first: 10) { nodes { name, description } } } }",
112
+ "variables": {
113
+ "login": "$.username"
114
+ },
115
+ "mapping": {
116
+ "output": {
117
+ "repositories": "$.data.user.repositories.nodes"
118
+ }
119
+ }
120
+ },
121
+ "input": {
122
+ "username": { "type": "string", "required": true }
123
+ },
124
+ "output": {
125
+ "repositories": { "type": "array" }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ## Field Types
133
+
134
+ Supported types for input/output fields:
135
+
136
+ - `string` - Text data
137
+ - `number` - Numeric value (integer or float)
138
+ - `boolean` - True/false
139
+ - `object` - Nested object structure
140
+ - `array` - Array of items
141
+ - `datetime` - ISO 8601 datetime string
142
+ - `date` - ISO 8601 date string
143
+ - `email` - Email address (validated)
144
+ - `url` - URL (validated)
145
+ - `uuid` - UUID v4 (validated)
146
+
147
+ ## Execution Modes
148
+
149
+ Services can run in different execution modes, configured in `config.json`:
150
+
151
+ ### Direct Mode (Internal Services)
152
+ ```json
153
+ {
154
+ "service": {
155
+ "execution": {
156
+ "mode": "direct",
157
+ "handler": "./src/handlers"
158
+ }
159
+ }
160
+ }
161
+ ```
162
+ - Service Wrapper calls handlers directly
163
+ - No HTTP overhead
164
+ - 50% memory savings
165
+ - Single process
166
+
167
+ ### HTTP Mode (Default/External)
168
+ ```json
169
+ {
170
+ "service": {
171
+ "execution": {
172
+ "mode": "http",
173
+ "baseUrl": "${SERVICE_URL:http://localhost:3000}"
174
+ }
175
+ }
176
+ }
177
+ ```
178
+ - Service Wrapper makes HTTP calls
179
+ - Service runs separately
180
+ - Better for testing
181
+ - Required for external APIs
182
+
183
+ ## Cookbook Integration
184
+
185
+ Operations map directly to Cookbook workflow steps:
186
+
187
+ ### Cookbook Step
188
+ ```json
189
+ {
190
+ "step_id": "greet_user",
191
+ "type": "service_call",
192
+ "service": "hello-service",
193
+ "operation": "good-day",
194
+ "input": {
195
+ "name": "$.user_name"
196
+ }
197
+ }
198
+ ```
199
+
200
+ ### Corresponding Operation
201
+ ```json
202
+ {
203
+ "operations": {
204
+ "good-day": {
205
+ "description": "Generate good day greeting",
206
+ "handler": "handlers.greetings.goodDay",
207
+ "endpoint": "/api/good-day",
208
+ "input": {
209
+ "name": { "type": "string", "required": true }
210
+ },
211
+ "output": {
212
+ "message": "string",
213
+ "timestamp": "datetime"
214
+ }
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ ## Handler Implementation
221
+
222
+ ### Direct Mode Handler
223
+ ```javascript
224
+ // handlers/greetings.js
225
+ exports.goodDay = async (input) => {
226
+ const { name } = input;
227
+ return {
228
+ message: `Good day, ${name}!`,
229
+ timestamp: new Date().toISOString()
230
+ };
231
+ };
232
+ ```
233
+
234
+ ### HTTP Mode Endpoint
235
+ ```javascript
236
+ // src/app.js
237
+ app.post('/api/good-day', async (req, res) => {
238
+ const { name } = req.body;
239
+ res.json({
240
+ message: `Good day, ${name}!`,
241
+ timestamp: new Date().toISOString()
242
+ });
243
+ });
244
+ ```
245
+
246
+ ## Validation
247
+
248
+ Input/output validation is automatic based on the schema:
249
+
250
+ ```javascript
251
+ // Service Wrapper validates automatically
252
+ const operation = operations['good-day'];
253
+ validateInput(input, operation.input); // Throws if invalid
254
+ const result = await executeOperation(operation, input);
255
+ validateOutput(result, operation.output); // Throws if invalid
256
+ ```
257
+
258
+ ## Migration from OpenAPI
259
+
260
+ ### Before (OpenAPI)
261
+ ```yaml
262
+ paths:
263
+ /v1/good-day:
264
+ post:
265
+ operationId: createGoodDay
266
+ requestBody:
267
+ content:
268
+ application/json:
269
+ schema:
270
+ $ref: '#/components/schemas/GreetingRequest'
271
+ responses:
272
+ '200':
273
+ content:
274
+ application/json:
275
+ schema:
276
+ $ref: '#/components/schemas/GreetingResponse'
277
+ ```
278
+
279
+ ### After (Operations Schema)
280
+ ```json
281
+ {
282
+ "operations": {
283
+ "good-day": {
284
+ "handler": "handlers.greetings.goodDay",
285
+ "endpoint": "/api/good-day",
286
+ "input": {
287
+ "name": { "type": "string", "required": true }
288
+ },
289
+ "output": {
290
+ "message": "string",
291
+ "timestamp": "datetime"
292
+ }
293
+ }
294
+ }
295
+ }
296
+ ```
297
+
298
+ ## Best Practices
299
+
300
+ 1. **Keep operations focused** - One operation = one business action
301
+ 2. **Use descriptive names** - Operation names should be self-explanatory
302
+ 3. **Document fields** - Add descriptions to complex fields
303
+ 4. **Validate strictly** - Mark required fields appropriately
304
+ 5. **Handle errors gracefully** - Define error responses in output
305
+ 6. **Use environment variables** - For URLs, keys, and configuration
306
+ 7. **Test both modes** - Ensure operations work in both direct and HTTP mode
307
+
308
+ ## Service Wrapper Integration
309
+
310
+ The Service Wrapper automatically:
311
+ - Loads operations.json on startup
312
+ - Routes Cookbook steps to operations
313
+ - Validates input/output
314
+ - Handles execution mode (direct/HTTP)
315
+ - Manages external API calls
316
+ - Provides metrics and logging
317
+
318
+ ## Example Service Structure
319
+
320
+ ```
321
+ hello-service/
322
+ ├── conn-config/
323
+ │ ├── config.json # Service configuration
324
+ │ ├── operations.json # Operations schema
325
+ │ └── middleware.json # Middleware config
326
+ ├── src/
327
+ │ ├── app.js # Express app (for HTTP mode)
328
+ │ └── handlers/ # Handler functions (for direct mode)
329
+ │ └── greetings.js
330
+ └── package.json
331
+ ```
332
+
333
+ ## Testing Operations
334
+
335
+ ```javascript
336
+ // Test operation directly
337
+ const operations = require('./conn-config/operations.json');
338
+ const handlers = require('./src/handlers');
339
+
340
+ test('good-day operation', async () => {
341
+ const input = { name: 'World' };
342
+ const result = await handlers.greetings.goodDay(input);
343
+
344
+ expect(result.message).toBe('Good day, World!');
345
+ expect(result.timestamp).toBeDefined();
346
+ });
347
+ ```
348
+
349
+ ## Future Extensions
350
+
351
+ The schema is designed to be extensible:
352
+
353
+ - **WebSocket operations** - Real-time communication
354
+ - **Message queue operations** - Direct MQ publishing
355
+ - **Database operations** - Direct DB queries
356
+ - **File operations** - S3, MinIO uploads
357
+ - **Batch operations** - Bulk processing
358
+
359
+ Each extension follows the same pattern:
360
+ 1. Define operation in operations.json
361
+ 2. Specify type in `external` block
362
+ 3. Map inputs/outputs
363
+ 4. Service Wrapper handles execution
@@ -0,0 +1,389 @@
1
+ # Service Testing Standard and OpenAPI Generation
2
+
3
+ ## 1. OpenAPI Schema Generation
4
+
5
+ ### Step 1: Analyze Your Service Endpoints
6
+
7
+ List all your service endpoints and their:
8
+ - HTTP methods (GET, POST, PUT, DELETE)
9
+ - Request parameters (query, path, body)
10
+ - Response structures
11
+ - Error responses
12
+
13
+ ### Step 2: Create openapi.yaml
14
+
15
+ Create `openapi.yaml` in your service root directory with this structure:
16
+
17
+ ```yaml
18
+ openapi: 3.0.0
19
+ info:
20
+ title: # Your service name from package.json
21
+ version: # Your service version from package.json
22
+ description: # Brief description of what your service does
23
+
24
+ servers:
25
+ - url: http://localhost:{your-port}
26
+ description: Local development
27
+ - url: http://{container-name}:3000
28
+ description: Docker container
29
+
30
+ paths:
31
+ # Document each endpoint
32
+ /your-endpoint:
33
+ get/post/put/delete:
34
+ summary: # One line description
35
+ operationId: # Unique identifier (e.g., getUsers, createOrder)
36
+ parameters: # If applicable
37
+ requestBody: # If applicable
38
+ responses:
39
+ '200':
40
+ description: # Success response
41
+ content:
42
+ application/json:
43
+ schema:
44
+ # Define or reference schema
45
+ '400':
46
+ # Bad request
47
+ '500':
48
+ # Server error
49
+
50
+ components:
51
+ schemas:
52
+ # Define reusable schemas here
53
+ ```
54
+
55
+ ### Step 3: Generate Schema from Code (Alternative)
56
+
57
+ If you have many endpoints, generate initial schema:
58
+
59
+ ```bash
60
+ # Install openapi generator
61
+ npm install --save-dev @apidevtools/swagger-cli
62
+
63
+ # For Express apps, use express-openapi-generator
64
+ npm install --save-dev express-openapi-generator
65
+
66
+ # Add generation script to package.json
67
+ "scripts": {
68
+ "generate:openapi": "node scripts/generate-openapi.js"
69
+ }
70
+ ```
71
+
72
+ Create `scripts/generate-openapi.js`:
73
+ ```javascript
74
+ const app = require('../src/app');
75
+ const generator = require('express-openapi-generator');
76
+
77
+ generator(app, {
78
+ outputFile: './openapi.yaml',
79
+ info: {
80
+ title: process.env.SERVICE_NAME,
81
+ version: require('../package.json').version
82
+ }
83
+ });
84
+ ```
85
+
86
+ ### Step 4: Validate Your Schema
87
+
88
+ ```bash
89
+ # Install validator
90
+ npm install --save-dev @apidevtools/swagger-parser
91
+
92
+ # Add validation script
93
+ "scripts": {
94
+ "validate:openapi": "swagger-cli validate openapi.yaml"
95
+ }
96
+ ```
97
+
98
+ ## 2. Test Structure
99
+
100
+ ### Directory Structure
101
+
102
+ ```
103
+ /your-service
104
+ /test
105
+ /unit # Unit tests for individual functions
106
+ /integration # Integration tests with mocked dependencies
107
+ /api # API endpoint tests
108
+ /compliance # OpenAPI compliance tests
109
+ test.config.js # Shared test configuration
110
+ ```
111
+
112
+ ### Test Configuration
113
+
114
+ Create `test/test.config.js`:
115
+ ```javascript
116
+ module.exports = {
117
+ // Service configuration for tests
118
+ service: {
119
+ name: process.env.SERVICE_NAME || 'your-service',
120
+ port: process.env.PORT || 3000,
121
+ baseUrl: `http://localhost:${process.env.PORT || 3000}`
122
+ },
123
+
124
+ // Timeouts
125
+ timeouts: {
126
+ unit: 5000,
127
+ integration: 10000,
128
+ api: 15000
129
+ },
130
+
131
+ // Mock data
132
+ mocks: {
133
+ validRequest: { /* valid request data */ },
134
+ invalidRequest: { /* invalid request data */ }
135
+ }
136
+ };
137
+ ```
138
+
139
+ ## 3. Test Implementation
140
+
141
+ ### Unit Tests (`test/unit/`)
142
+
143
+ Test individual functions without external dependencies:
144
+
145
+ ```javascript
146
+ describe('Business Logic', () => {
147
+ test('should process valid input', () => {
148
+ const result = processFunction(validInput);
149
+ expect(result).toBeDefined();
150
+ expect(result.status).toBe('success');
151
+ });
152
+
153
+ test('should handle invalid input', () => {
154
+ expect(() => processFunction(invalidInput)).toThrow();
155
+ });
156
+ });
157
+ ```
158
+
159
+ ### API Tests (`test/api/`)
160
+
161
+ Test actual HTTP endpoints:
162
+
163
+ ```javascript
164
+ const request = require('supertest');
165
+ const app = require('../../src/app');
166
+
167
+ describe('API Endpoints', () => {
168
+ describe('GET /your-endpoint', () => {
169
+ test('should return 200 with valid response', async () => {
170
+ const response = await request(app)
171
+ .get('/your-endpoint')
172
+ .expect(200);
173
+
174
+ expect(response.body).toHaveProperty('expectedField');
175
+ });
176
+
177
+ test('should return 400 for invalid request', async () => {
178
+ const response = await request(app)
179
+ .get('/your-endpoint?invalid=true')
180
+ .expect(400);
181
+
182
+ expect(response.body).toHaveProperty('error');
183
+ });
184
+ });
185
+ });
186
+ ```
187
+
188
+ ### Compliance Tests (`test/compliance/`)
189
+
190
+ Test OpenAPI compliance:
191
+
192
+ ```javascript
193
+ const SwaggerParser = require('@apidevtools/swagger-parser');
194
+ const request = require('supertest');
195
+ const app = require('../../src/app');
196
+ const openApiSchema = require('../../openapi.yaml');
197
+
198
+ describe('OpenAPI Compliance', () => {
199
+ let api;
200
+
201
+ beforeAll(async () => {
202
+ // Validate and parse schema
203
+ api = await SwaggerParser.validate(openApiSchema);
204
+ });
205
+
206
+ test('schema should be valid OpenAPI 3.0', () => {
207
+ expect(api.openapi).toBe('3.0.0');
208
+ });
209
+
210
+ test('all endpoints should match schema', async () => {
211
+ for (const [path, methods] of Object.entries(api.paths)) {
212
+ for (const [method, spec] of Object.entries(methods)) {
213
+ if (['get', 'post', 'put', 'delete'].includes(method)) {
214
+ const response = await request(app)[method](path);
215
+ // Validate response matches schema
216
+ expect(response.status).toBeDefined();
217
+ }
218
+ }
219
+ }
220
+ });
221
+ });
222
+ ```
223
+
224
+ ## 4. NPM Scripts
225
+
226
+ Add to `package.json`:
227
+
228
+ ```json
229
+ {
230
+ "scripts": {
231
+ "test": "npm run test:unit && npm run test:api && npm run test:compliance",
232
+ "test:unit": "jest test/unit",
233
+ "test:integration": "jest test/integration",
234
+ "test:api": "jest test/api",
235
+ "test:compliance": "jest test/compliance",
236
+ "test:coverage": "jest --coverage",
237
+ "validate:openapi": "swagger-cli validate openapi.yaml",
238
+ "generate:openapi": "node scripts/generate-openapi.js",
239
+ "serve:docs": "swagger-ui-express openapi.yaml"
240
+ }
241
+ }
242
+ ```
243
+
244
+ ### Coverage Requirements
245
+
246
+ **Minimum recommended coverage: 80%**
247
+
248
+ Configure Jest coverage thresholds in `package.json`:
249
+
250
+ ```json
251
+ {
252
+ "jest": {
253
+ "coverageThreshold": {
254
+ "global": {
255
+ "branches": 80,
256
+ "functions": 80,
257
+ "lines": 80,
258
+ "statements": 80
259
+ }
260
+ },
261
+ "collectCoverageFrom": [
262
+ "src/**/*.js",
263
+ "!src/**/*.test.js",
264
+ "!src/**/*.spec.js"
265
+ ]
266
+ }
267
+ }
268
+ ```
269
+
270
+ ## 5. Service Wrapper Integration
271
+
272
+ ### Endpoint for OpenAPI Schema
273
+
274
+ Your service must expose its schema:
275
+
276
+ ```javascript
277
+ // In your app.js or routes
278
+ const fs = require('fs');
279
+ const yaml = require('js-yaml');
280
+
281
+ app.get('/openapi', (req, res) => {
282
+ try {
283
+ const doc = yaml.load(fs.readFileSync('./openapi.yaml', 'utf8'));
284
+ res.json(doc);
285
+ } catch (e) {
286
+ res.status(500).json({ error: 'Schema not available' });
287
+ }
288
+ });
289
+ ```
290
+
291
+ ### Validation Middleware
292
+
293
+ Use Service Wrapper's validation:
294
+
295
+ ```javascript
296
+ const { validateRequest } = require('@onlineapps/service-wrapper/middleware');
297
+ const openApiSchema = require('./openapi.yaml');
298
+
299
+ // Apply validation to routes
300
+ app.post('/your-endpoint',
301
+ validateRequest(openApiSchema, '/your-endpoint', 'post'),
302
+ (req, res) => {
303
+ // Request is already validated
304
+ }
305
+ );
306
+ ```
307
+
308
+ ## 6. Continuous Validation
309
+
310
+ ### Pre-commit Hook
311
+
312
+ `.husky/pre-commit`:
313
+ ```bash
314
+ #!/bin/sh
315
+ npm run validate:openapi
316
+ npm run test:compliance
317
+ ```
318
+
319
+ ### CI/CD Pipeline
320
+
321
+ ```yaml
322
+ # .github/workflows/test.yml or gitlab-ci.yml
323
+ test:
324
+ script:
325
+ - npm install
326
+ - npm run validate:openapi
327
+ - npm run test
328
+ - npm run test:coverage
329
+ coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
330
+ ```
331
+
332
+ ## 7. Validation Checklist
333
+
334
+ Before deployment, ensure:
335
+
336
+ - [ ] OpenAPI schema exists at `/openapi.yaml`
337
+ - [ ] Schema validates with `swagger-cli validate`
338
+ - [ ] All endpoints documented in schema
339
+ - [ ] Request/response schemas defined
340
+ - [ ] Error responses documented
341
+ - [ ] Schema version matches package.json
342
+ - [ ] `/openapi` endpoint returns schema
343
+ - [ ] Unit tests pass (>80% coverage)
344
+ - [ ] API tests pass
345
+ - [ ] Compliance tests pass
346
+ - [ ] Service Wrapper can load schema
347
+ - [ ] Validation middleware works
348
+
349
+ ## 8. Common Issues and Solutions
350
+
351
+ ### Issue: Schema doesn't match implementation
352
+ **Solution**: Generate schema from code first, then refine manually
353
+
354
+ ### Issue: Validation too strict
355
+ **Solution**: Use `additionalProperties: true` in schemas during development
356
+
357
+ ### Issue: Complex nested objects
358
+ **Solution**: Break into components and use `$ref`
359
+
360
+ ### Issue: Different environments need different configs
361
+ **Solution**: Use environment variables in schema:
362
+ ```yaml
363
+ servers:
364
+ - url: ${API_BASE_URL}
365
+ description: Configured base URL
366
+ ```
367
+
368
+ ## 9. Testing with conn-e2e-testing
369
+
370
+ The connector testing framework will:
371
+ 1. Load your OpenAPI schema
372
+ 2. Start your service
373
+ 3. Test each endpoint against schema
374
+ 4. Validate requests and responses
375
+ 5. Generate compliance report
376
+
377
+ Ensure your service is ready by:
378
+ 1. Exposing `/openapi` endpoint
379
+ 2. Having all endpoints documented
380
+ 3. Returning proper HTTP status codes
381
+ 4. Using consistent error format
382
+
383
+ ## 10. Resources
384
+
385
+ - [OpenAPI 3.0 Specification](https://swagger.io/specification/)
386
+ - [JSON Schema Validation](https://json-schema.org/draft/2019-09/json-schema-validation.html)
387
+ - [swagger-cli Documentation](https://apitools.dev/swagger-cli/)
388
+ - Service Wrapper documentation: `/shared/connector/service-wrapper/README.md`
389
+ - Testing examples: `/shared/connector/conn-e2e-testing/examples/`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/service-wrapper",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -25,13 +25,14 @@
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
27
  "@onlineapps/conn-base-cache": "^1.0.0",
28
- "@onlineapps/conn-base-logger": "^1.0.0",
28
+ "@onlineapps/conn-base-monitoring": "file:../conn-base-monitoring/onlineapps-conn-base-monitoring-1.0.0.tgz",
29
29
  "@onlineapps/conn-infra-error-handler": "^1.0.0",
30
30
  "@onlineapps/conn-infra-mq": "^1.1.0",
31
31
  "@onlineapps/conn-orch-api-mapper": "^1.0.0",
32
32
  "@onlineapps/conn-orch-cookbook": "^2.0.0",
33
33
  "@onlineapps/conn-orch-orchestrator": "^1.0.1",
34
- "@onlineapps/conn-orch-registry": "^1.1.4"
34
+ "@onlineapps/conn-orch-registry": "^1.1.4",
35
+ "@onlineapps/monitoring-core": "file:../../../api/shared/monitoring-core/onlineapps-monitoring-core-1.0.0.tgz"
35
36
  },
36
37
  "devDependencies": {
37
38
  "express": "^5.1.0",
@@ -13,7 +13,7 @@
13
13
  // Import connectors
14
14
  const MQConnector = require('@onlineapps/conn-infra-mq');
15
15
  const RegistryConnector = require('@onlineapps/conn-orch-registry');
16
- const LoggerConnector = require('@onlineapps/conn-base-logger');
16
+ const MonitoringConnector = require('@onlineapps/conn-base-monitoring');
17
17
  const OrchestratorConnector = require('@onlineapps/conn-orch-orchestrator');
18
18
  const ApiMapperConnector = require('@onlineapps/conn-orch-api-mapper');
19
19
  const CookbookConnector = require('@onlineapps/conn-orch-cookbook');
@@ -69,7 +69,8 @@ class ServiceWrapper {
69
69
  this.config = options.config || {};
70
70
 
71
71
  // Initialize connectors
72
- this.logger = new LoggerConnector({ serviceName: this.serviceName });
72
+ this.logger = MonitoringConnector; // Singleton, will be initialized later
73
+ this.monitoring = MonitoringConnector; // Also expose as monitoring for clarity
73
74
  this.mqClient = null;
74
75
  this.registryClient = null;
75
76
  this.orchestrator = null;
@@ -109,11 +110,14 @@ class ServiceWrapper {
109
110
  */
110
111
  async start() {
111
112
  if (this.isRunning) {
112
- this.logger.warn('Service wrapper already running');
113
+ console.warn('Service wrapper already running');
113
114
  return;
114
115
  }
115
116
 
116
117
  try {
118
+ // Initialize monitoring first
119
+ await this.monitoring.init({ serviceName: this.serviceName });
120
+
117
121
  this.logger.info(`Starting service wrapper for ${this.serviceName}`);
118
122
 
119
123
  // 1. Initialize infrastructure connectors
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Test monitoring integration in service-wrapper
3
+ */
4
+
5
+ describe('ServiceWrapper Monitoring Integration', () => {
6
+ let ServiceWrapper;
7
+ let express;
8
+
9
+ beforeEach(() => {
10
+ // Reset modules
11
+ jest.resetModules();
12
+
13
+ // Mock dependencies
14
+ jest.mock('@onlineapps/conn-infra-mq', () => {
15
+ return jest.fn().mockImplementation(() => ({
16
+ connect: jest.fn().mockResolvedValue(true),
17
+ disconnect: jest.fn().mockResolvedValue(true),
18
+ consume: jest.fn().mockResolvedValue(true),
19
+ isConnected: jest.fn().mockReturnValue(true)
20
+ }));
21
+ });
22
+
23
+ jest.mock('@onlineapps/conn-orch-registry', () => ({
24
+ ServiceRegistryClient: jest.fn().mockImplementation(() => ({
25
+ register: jest.fn().mockResolvedValue(true),
26
+ unregister: jest.fn().mockResolvedValue(true),
27
+ sendHeartbeat: jest.fn().mockResolvedValue(true),
28
+ isConnected: jest.fn().mockReturnValue(true)
29
+ }))
30
+ }));
31
+
32
+ jest.mock('@onlineapps/conn-base-monitoring', () => ({
33
+ init: jest.fn().mockResolvedValue({
34
+ info: jest.fn(),
35
+ warn: jest.fn(),
36
+ error: jest.fn(),
37
+ debug: jest.fn()
38
+ }),
39
+ info: jest.fn(),
40
+ warn: jest.fn(),
41
+ error: jest.fn(),
42
+ debug: jest.fn()
43
+ }));
44
+
45
+ jest.mock('@onlineapps/conn-orch-orchestrator', () => ({
46
+ create: jest.fn().mockReturnValue({
47
+ processWorkflowMessage: jest.fn().mockResolvedValue({ success: true })
48
+ })
49
+ }));
50
+
51
+ jest.mock('@onlineapps/conn-orch-api-mapper', () => ({
52
+ create: jest.fn().mockReturnValue({})
53
+ }));
54
+
55
+ jest.mock('@onlineapps/conn-orch-cookbook', () => ({}));
56
+ jest.mock('@onlineapps/conn-base-cache', () => jest.fn());
57
+ jest.mock('@onlineapps/conn-infra-error-handler', () => jest.fn());
58
+
59
+ ServiceWrapper = require('../src/ServiceWrapper');
60
+ express = require('express');
61
+ });
62
+
63
+ test('should initialize monitoring on start', async () => {
64
+ const app = express();
65
+ const monitoring = require('@onlineapps/conn-base-monitoring');
66
+
67
+ const wrapper = new ServiceWrapper({
68
+ service: app,
69
+ serviceName: 'test-service',
70
+ openApiSpec: { info: { version: '1.0.0' } },
71
+ config: {}
72
+ });
73
+
74
+ await wrapper.start();
75
+
76
+ expect(monitoring.init).toHaveBeenCalledWith({
77
+ serviceName: 'test-service'
78
+ });
79
+
80
+ expect(wrapper.logger).toBeDefined();
81
+ expect(wrapper.monitoring).toBeDefined();
82
+ });
83
+
84
+ test('should use monitoring for logging', async () => {
85
+ const app = express();
86
+ const wrapper = new ServiceWrapper({
87
+ service: app,
88
+ serviceName: 'test-service',
89
+ openApiSpec: { info: { version: '1.0.0' } },
90
+ config: {}
91
+ });
92
+
93
+ await wrapper.start();
94
+
95
+ // Test logger methods are available
96
+ expect(wrapper.logger.info).toBeDefined();
97
+ expect(wrapper.logger.error).toBeDefined();
98
+ expect(wrapper.logger.warn).toBeDefined();
99
+ expect(wrapper.logger.debug).toBeDefined();
100
+
101
+ // Test logger is used
102
+ wrapper.logger.info('Test message', { data: 'test' });
103
+ wrapper.logger.error('Error message', { error: 'test' });
104
+ });
105
+
106
+ test('should pass monitoring to orchestrator', async () => {
107
+ const app = express();
108
+ const OrchestratorConnector = require('@onlineapps/conn-orch-orchestrator');
109
+
110
+ const wrapper = new ServiceWrapper({
111
+ service: app,
112
+ serviceName: 'test-service',
113
+ openApiSpec: { info: { version: '1.0.0' } },
114
+ config: {}
115
+ });
116
+
117
+ await wrapper.start();
118
+
119
+ expect(OrchestratorConnector.create).toHaveBeenCalledWith(
120
+ expect.objectContaining({
121
+ logger: expect.objectContaining({
122
+ info: expect.any(Function),
123
+ error: expect.any(Function),
124
+ warn: expect.any(Function),
125
+ debug: expect.any(Function)
126
+ })
127
+ })
128
+ );
129
+ });
130
+
131
+ test('should handle monitoring mode from config', async () => {
132
+ const app = express();
133
+ const monitoring = require('@onlineapps/conn-base-monitoring');
134
+
135
+ const wrapper = new ServiceWrapper({
136
+ service: app,
137
+ serviceName: 'test-service',
138
+ openApiSpec: { info: { version: '1.0.0' } },
139
+ config: {
140
+ monitoringMode: 'debug'
141
+ }
142
+ });
143
+
144
+ await wrapper.start();
145
+
146
+ // Monitoring should be initialized but mode is not passed
147
+ // because ServiceWrapper doesn't pass it through yet
148
+ expect(monitoring.init).toHaveBeenCalled();
149
+ });
150
+ });