@onlineapps/service-wrapper 2.0.6 → 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 +11 -0
- package/docs/API_STRUCTURE_STANDARD.md +132 -0
- package/docs/CONFIGURATION_GUIDE.md +261 -0
- package/docs/OPERATIONS_SCHEMA.md +363 -0
- package/docs/SERVICE_TESTING_STANDARD.md +389 -0
- package/package.json +9 -7
- package/src/ServiceWrapper.js +39 -6
- package/test/e2e/full-flow.test.js +293 -0
- package/test/monitoring-integration.test.js +150 -0
|
@@ -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
|