sentinel-kafka-manager 1.0.3 → 1.0.5
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 +555 -551
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,36 @@ In most services, Kafka setup gets repeated:
|
|
|
27
27
|
|
|
28
28
|
`sentinel-kafka-manager` wraps those common tasks in one small class: `KafkaManager`.
|
|
29
29
|
|
|
30
|
+
## Enterprise Features
|
|
31
|
+
|
|
32
|
+
This version now includes a stronger production baseline:
|
|
33
|
+
|
|
34
|
+
- config validation for client id, brokers, and broker discovery inputs
|
|
35
|
+
- shared producer, admin, and consumer lifecycle
|
|
36
|
+
- logger and metrics hooks
|
|
37
|
+
- standard message envelope publishing
|
|
38
|
+
- managed consumer runner with dead-letter topic support
|
|
39
|
+
- health snapshot reporting
|
|
40
|
+
- safer startup behavior for concurrent connections
|
|
41
|
+
- stable error codes and structured exception details
|
|
42
|
+
|
|
43
|
+
## Recommended Usage Level
|
|
44
|
+
|
|
45
|
+
This package is now suitable as a shared internal Kafka module for SaaS microservices.
|
|
46
|
+
|
|
47
|
+
Recommended use:
|
|
48
|
+
|
|
49
|
+
- internal platform package for Node.js services
|
|
50
|
+
- event publishing with standard envelopes
|
|
51
|
+
- managed consumers with DLQ handling
|
|
52
|
+
- consistent service-layer success and error responses
|
|
53
|
+
|
|
54
|
+
Still recommended outside the package:
|
|
55
|
+
|
|
56
|
+
- automated unit and integration tests in the consuming service or platform repo
|
|
57
|
+
- schema validation for business payloads when required by your organization
|
|
58
|
+
- organization-specific tracing, alerting, and compliance rules
|
|
59
|
+
|
|
30
60
|
## Installation
|
|
31
61
|
|
|
32
62
|
Install from npm:
|
|
@@ -106,281 +136,300 @@ process.on('SIGTERM', async () => {
|
|
|
106
136
|
})
|
|
107
137
|
```
|
|
108
138
|
|
|
109
|
-
##
|
|
110
|
-
|
|
111
|
-
This repository already includes a full local Kafka server setup in [docker-compose.yml](/var/www/html/matrix-project/sentinel-kafka-manager/docker-compose.yml) and sample environment values in [.env.local.example](/var/www/html/matrix-project/sentinel-kafka-manager/.env.local.example).
|
|
112
|
-
|
|
113
|
-
The stack includes:
|
|
114
|
-
|
|
115
|
-
- 3 KRaft controllers
|
|
116
|
-
- 3 Kafka brokers
|
|
117
|
-
- 1 Kafka UI instance
|
|
118
|
-
|
|
119
|
-
### 1. Create your `.env`
|
|
139
|
+
## Basic Usage
|
|
120
140
|
|
|
121
|
-
|
|
141
|
+
### 1. Import the package
|
|
122
142
|
|
|
123
|
-
```
|
|
124
|
-
|
|
143
|
+
```ts
|
|
144
|
+
import { KafkaManager } from 'sentinel-kafka-manager'
|
|
125
145
|
```
|
|
126
146
|
|
|
127
|
-
If you want
|
|
128
|
-
|
|
129
|
-
### 2. Start the Kafka cluster
|
|
147
|
+
If you want to catch package-specific errors explicitly:
|
|
130
148
|
|
|
131
|
-
```
|
|
132
|
-
|
|
149
|
+
```ts
|
|
150
|
+
import {
|
|
151
|
+
KafkaErrorCode,
|
|
152
|
+
KafkaManager,
|
|
153
|
+
KafkaManagerError,
|
|
154
|
+
OperationResponse,
|
|
155
|
+
} from 'sentinel-kafka-manager'
|
|
133
156
|
```
|
|
134
157
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
- controller quorum on port `9093` inside Docker
|
|
138
|
-
- internal broker traffic on port `9092` inside Docker
|
|
139
|
-
- host-accessible broker ports `29092`, `39092`, and `49092`
|
|
140
|
-
- Kafka UI on `127.0.0.1:5001`
|
|
158
|
+
### 2. Create a manager
|
|
141
159
|
|
|
142
|
-
|
|
160
|
+
You can create it in two ways:
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```
|
|
162
|
+
1. pass brokers directly
|
|
163
|
+
2. build brokers from environment variables
|
|
147
164
|
|
|
148
|
-
|
|
165
|
+
### Direct broker example
|
|
149
166
|
|
|
150
|
-
```
|
|
151
|
-
|
|
167
|
+
```ts
|
|
168
|
+
const kafkaManager = new KafkaManager({
|
|
169
|
+
clientId: 'claims-service',
|
|
170
|
+
brokers: ['localhost:29092', 'localhost:39092', 'localhost:49092'],
|
|
171
|
+
})
|
|
152
172
|
```
|
|
153
173
|
|
|
154
|
-
###
|
|
174
|
+
### Environment-based example
|
|
155
175
|
|
|
156
|
-
```
|
|
157
|
-
|
|
176
|
+
```ts
|
|
177
|
+
const kafkaManager = KafkaManager.fromEnv({
|
|
178
|
+
clientId: 'claims-service',
|
|
179
|
+
mode: 'external',
|
|
180
|
+
})
|
|
158
181
|
```
|
|
159
182
|
|
|
160
|
-
|
|
183
|
+
## Recommended Project Pattern
|
|
161
184
|
|
|
162
|
-
|
|
185
|
+
In a real service, create one shared manager instance and reuse it.
|
|
163
186
|
|
|
164
|
-
|
|
165
|
-
localhost:29092
|
|
166
|
-
localhost:39092
|
|
167
|
-
localhost:49092
|
|
168
|
-
```
|
|
187
|
+
Example:
|
|
169
188
|
|
|
170
|
-
|
|
189
|
+
```ts
|
|
190
|
+
import { KafkaManager } from 'sentinel-kafka-manager'
|
|
171
191
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
192
|
+
export const kafkaManager = KafkaManager.fromEnv({
|
|
193
|
+
clientId: 'claims-service',
|
|
194
|
+
mode: 'external',
|
|
195
|
+
})
|
|
176
196
|
```
|
|
177
197
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- application clients must connect to brokers, not controllers
|
|
181
|
-
- controllers are internal cluster metadata nodes only
|
|
182
|
-
- `mode: 'external'` is for host-based apps
|
|
183
|
-
- `mode: 'internal'` is for Dockerized apps on the same network
|
|
184
|
-
|
|
185
|
-
## Kafka UI Usage
|
|
186
|
-
|
|
187
|
-
The Docker stack also starts Kafka UI for cluster inspection.
|
|
198
|
+
Then import that shared instance anywhere you need Kafka access.
|
|
188
199
|
|
|
189
|
-
|
|
200
|
+
You can also attach enterprise hooks:
|
|
190
201
|
|
|
191
|
-
|
|
202
|
+
```ts
|
|
203
|
+
import { KafkaManager } from 'sentinel-kafka-manager'
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
|
|
205
|
+
export const kafkaManager = KafkaManager.fromEnv({
|
|
206
|
+
clientId: 'claims-service',
|
|
207
|
+
mode: 'external',
|
|
208
|
+
logger: {
|
|
209
|
+
debug: (message, meta) => console.debug(message, meta),
|
|
210
|
+
info: (message, meta) => console.info(message, meta),
|
|
211
|
+
warn: (message, meta) => console.warn(message, meta),
|
|
212
|
+
error: (message, meta) => console.error(message, meta),
|
|
213
|
+
},
|
|
214
|
+
metrics: {
|
|
215
|
+
emit: (event) => {
|
|
216
|
+
console.log('METRIC', event)
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
})
|
|
195
220
|
```
|
|
196
221
|
|
|
197
|
-
|
|
222
|
+
## How To Use In Your Service
|
|
198
223
|
|
|
199
|
-
|
|
224
|
+
### Publish a message
|
|
200
225
|
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
```ts
|
|
227
|
+
await kafkaManager.publish({
|
|
228
|
+
topic: 'claims.created',
|
|
229
|
+
key: 'claim-1001',
|
|
230
|
+
payload: {
|
|
231
|
+
claimId: 'claim-1001',
|
|
232
|
+
status: 'created',
|
|
233
|
+
source: 'claims-service',
|
|
234
|
+
},
|
|
235
|
+
})
|
|
204
236
|
```
|
|
205
237
|
|
|
206
|
-
|
|
238
|
+
`publish()` automatically:
|
|
207
239
|
|
|
208
|
-
|
|
240
|
+
- gets a shared producer
|
|
241
|
+
- connects it once
|
|
242
|
+
- serializes `payload` with `JSON.stringify()`
|
|
209
243
|
|
|
210
|
-
|
|
211
|
-
broker-1:9092,broker-2:9092,broker-3:9092
|
|
212
|
-
```
|
|
244
|
+
### Publish an enterprise event envelope
|
|
213
245
|
|
|
214
|
-
|
|
246
|
+
Use this when you want traceable event metadata such as `eventType`, `version`, `traceId`, or `tenantId`.
|
|
215
247
|
|
|
216
|
-
```
|
|
217
|
-
|
|
248
|
+
```ts
|
|
249
|
+
await kafkaManager.publishEnvelope({
|
|
250
|
+
topic: 'claims.created',
|
|
251
|
+
key: 'claim-1001',
|
|
252
|
+
envelope: {
|
|
253
|
+
eventId: 'evt-claim-1001',
|
|
254
|
+
eventType: 'claims.created',
|
|
255
|
+
version: '1.0.0',
|
|
256
|
+
timestamp: new Date().toISOString(),
|
|
257
|
+
source: 'claims-service',
|
|
258
|
+
traceId: 'trace-123',
|
|
259
|
+
tenantId: 'tenant-abc',
|
|
260
|
+
correlationId: 'corr-001',
|
|
261
|
+
payload: {
|
|
262
|
+
claimId: 'claim-1001',
|
|
263
|
+
status: 'created',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
})
|
|
218
267
|
```
|
|
219
268
|
|
|
220
|
-
###
|
|
221
|
-
|
|
222
|
-
- view brokers and cluster health
|
|
223
|
-
- inspect topics and partitions
|
|
224
|
-
- browse messages
|
|
225
|
-
- inspect consumer groups
|
|
226
|
-
- check offsets and lag
|
|
269
|
+
### Create or reuse a producer manually
|
|
227
270
|
|
|
228
|
-
|
|
271
|
+
If you need direct producer access:
|
|
229
272
|
|
|
230
|
-
|
|
273
|
+
```ts
|
|
274
|
+
const producer = await kafkaManager.getProducer()
|
|
231
275
|
|
|
232
|
-
|
|
233
|
-
|
|
276
|
+
await producer.send({
|
|
277
|
+
topic: 'claims.created',
|
|
278
|
+
messages: [
|
|
279
|
+
{
|
|
280
|
+
key: 'claim-1001',
|
|
281
|
+
value: JSON.stringify({ claimId: 'claim-1001' }),
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
})
|
|
234
285
|
```
|
|
235
286
|
|
|
236
|
-
|
|
287
|
+
### Create or reuse a consumer
|
|
237
288
|
|
|
238
|
-
|
|
289
|
+
```ts
|
|
290
|
+
const consumer = await kafkaManager.getConsumer('claims-service-group')
|
|
239
291
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
292
|
+
await consumer.subscribe({
|
|
293
|
+
topic: 'claims.created',
|
|
294
|
+
fromBeginning: false,
|
|
295
|
+
})
|
|
243
296
|
|
|
244
|
-
|
|
297
|
+
await consumer.run({
|
|
298
|
+
eachMessage: async ({ topic, partition, message }) => {
|
|
299
|
+
const rawValue = message.value?.toString() ?? '{}'
|
|
300
|
+
const payload = JSON.parse(rawValue)
|
|
245
301
|
|
|
246
|
-
|
|
247
|
-
|
|
302
|
+
console.log({
|
|
303
|
+
topic,
|
|
304
|
+
partition,
|
|
305
|
+
key: message.key?.toString(),
|
|
306
|
+
payload,
|
|
307
|
+
})
|
|
308
|
+
},
|
|
309
|
+
})
|
|
248
310
|
```
|
|
249
311
|
|
|
250
|
-
|
|
312
|
+
`getConsumer(groupId)` returns one shared connected consumer per group id.
|
|
251
313
|
|
|
252
|
-
|
|
253
|
-
2. Open `http://127.0.0.1:5001`.
|
|
254
|
-
3. Log in with `KAFKA_UI_USERNAME` and `KAFKA_UI_PASSWORD`.
|
|
255
|
-
4. Open the configured cluster.
|
|
256
|
-
5. Go to Topics, Consumer Groups, or Brokers as needed.
|
|
314
|
+
### Run a managed consumer with DLQ support
|
|
257
315
|
|
|
258
|
-
|
|
316
|
+
For SaaS-style services, this is the recommended consumer pattern.
|
|
259
317
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
318
|
+
```ts
|
|
319
|
+
await kafkaManager.runConsumer({
|
|
320
|
+
groupId: 'claims-service-group',
|
|
321
|
+
topic: 'claims.created',
|
|
322
|
+
dlqTopic: 'claims.created.dlq',
|
|
323
|
+
onMessage: async ({ message, headers, key }) => {
|
|
324
|
+
console.log('Processing:', {
|
|
325
|
+
key,
|
|
326
|
+
traceId: headers['x-trace-id'],
|
|
327
|
+
payload: message,
|
|
328
|
+
})
|
|
329
|
+
},
|
|
330
|
+
onError: async (error, context) => {
|
|
331
|
+
console.error('Consumer error:', {
|
|
332
|
+
error: error.message,
|
|
333
|
+
topic: context.topic,
|
|
334
|
+
offset: context.offset,
|
|
335
|
+
})
|
|
336
|
+
},
|
|
337
|
+
})
|
|
270
338
|
```
|
|
271
339
|
|
|
272
|
-
|
|
273
|
-
- `KAFKA_CLUSTER_ID`: shared KRaft cluster id for all nodes
|
|
274
|
-
- `KAFKA_NETWORK_NAME`: Docker network name used by the full stack
|
|
275
|
-
|
|
276
|
-
### Controller quorum settings
|
|
340
|
+
If processing fails:
|
|
277
341
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
KAFKA_CONTROLLER_QUORUM_VOTERS=1@controller-1:9093,2@controller-2:9093,3@controller-3:9093
|
|
282
|
-
```
|
|
342
|
+
- the error is logged
|
|
343
|
+
- a metric hook can receive the failure event
|
|
344
|
+
- the message can be published to a dead-letter topic when `dlqTopic` is set
|
|
283
345
|
|
|
284
|
-
|
|
285
|
-
- apps should never use these controller addresses as Kafka client brokers
|
|
346
|
+
### Catch structured errors
|
|
286
347
|
|
|
287
|
-
|
|
348
|
+
All package-thrown operational errors now use `KafkaManagerError`.
|
|
288
349
|
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
KAFKA_BROKER_1_EXTERNAL_PORT=29092
|
|
295
|
-
KAFKA_BROKER_2_EXTERNAL_PORT=39092
|
|
296
|
-
KAFKA_BROKER_3_EXTERNAL_PORT=49092
|
|
297
|
-
KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL
|
|
298
|
-
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
|
|
299
|
-
```
|
|
350
|
+
```ts
|
|
351
|
+
import {
|
|
352
|
+
KafkaErrorCode,
|
|
353
|
+
KafkaManagerError,
|
|
354
|
+
} from 'sentinel-kafka-manager'
|
|
300
355
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
356
|
+
try {
|
|
357
|
+
await kafkaManager.publish({
|
|
358
|
+
topic: 'claims.created',
|
|
359
|
+
payload: { claimId: 'claim-1001' },
|
|
360
|
+
})
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (error instanceof KafkaManagerError) {
|
|
363
|
+
console.error('Kafka error', {
|
|
364
|
+
code: error.code,
|
|
365
|
+
message: error.message,
|
|
366
|
+
details: error.details,
|
|
367
|
+
})
|
|
304
368
|
|
|
305
|
-
|
|
369
|
+
if (error.code === KafkaErrorCode.MESSAGE_PUBLISH_FAILED) {
|
|
370
|
+
// retry, alert, or degrade gracefully
|
|
371
|
+
}
|
|
372
|
+
}
|
|
306
373
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
|
|
310
|
-
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=2
|
|
311
|
-
KAFKA_DEFAULT_REPLICATION_FACTOR=3
|
|
312
|
-
KAFKA_MIN_INSYNC_REPLICAS=2
|
|
313
|
-
KAFKA_NUM_PARTITIONS=6
|
|
374
|
+
throw error
|
|
375
|
+
}
|
|
314
376
|
```
|
|
315
377
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
### Broker behavior
|
|
319
|
-
|
|
320
|
-
```env
|
|
321
|
-
KAFKA_AUTO_CREATE_TOPICS_ENABLE=false
|
|
322
|
-
KAFKA_DELETE_TOPIC_ENABLE=true
|
|
323
|
-
KAFKA_LOG_RETENTION_HOURS=168
|
|
324
|
-
KAFKA_LOG_SEGMENT_BYTES=1073741824
|
|
325
|
-
KAFKA_MESSAGE_MAX_BYTES=10485880
|
|
326
|
-
KAFKA_REPLICA_FETCH_MAX_BYTES=10485880
|
|
327
|
-
KAFKA_SOCKET_REQUEST_MAX_BYTES=104857600
|
|
328
|
-
```
|
|
378
|
+
### Use common success and error response types
|
|
329
379
|
|
|
330
|
-
|
|
331
|
-
- deleting topics is allowed
|
|
332
|
-
- retention is 7 days by default
|
|
380
|
+
If your service wraps Kafka operations in API or service-layer responses, you can use the shared response contracts from this package.
|
|
333
381
|
|
|
334
|
-
|
|
382
|
+
```ts
|
|
383
|
+
import {
|
|
384
|
+
ErrorResponse,
|
|
385
|
+
OperationResponse,
|
|
386
|
+
SuccessResponse,
|
|
387
|
+
} from 'sentinel-kafka-manager'
|
|
335
388
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
KAFKA_LOG_MAX_SIZE=100m
|
|
341
|
-
KAFKA_LOG_MAX_FILE=5
|
|
389
|
+
type PublishClaimCreatedResponse = OperationResponse<{
|
|
390
|
+
topic: string
|
|
391
|
+
key: string
|
|
392
|
+
}>
|
|
342
393
|
```
|
|
343
394
|
|
|
344
|
-
|
|
395
|
+
Success example:
|
|
345
396
|
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
397
|
+
```ts
|
|
398
|
+
const response: SuccessResponse<{ topic: string; key: string }> = {
|
|
399
|
+
success: true,
|
|
400
|
+
message: 'Kafka message published successfully.',
|
|
401
|
+
data: {
|
|
402
|
+
topic: 'claims.created',
|
|
403
|
+
key: 'claim-1001',
|
|
404
|
+
},
|
|
405
|
+
meta: {
|
|
406
|
+
timestamp: new Date().toISOString(),
|
|
407
|
+
traceId: 'trace-123',
|
|
408
|
+
},
|
|
409
|
+
}
|
|
359
410
|
```
|
|
360
411
|
|
|
361
|
-
|
|
362
|
-
- `KAFKA_UI_PORT=5001` publishes the UI on your machine
|
|
363
|
-
- `KAFKA_UI_READONLY=true` prevents write actions from the UI
|
|
364
|
-
|
|
365
|
-
### Healthcheck settings
|
|
412
|
+
Error example:
|
|
366
413
|
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
414
|
+
```ts
|
|
415
|
+
const response: ErrorResponse = {
|
|
416
|
+
success: false,
|
|
417
|
+
message: 'Failed to publish Kafka message.',
|
|
418
|
+
error: {
|
|
419
|
+
code: KafkaErrorCode.MESSAGE_PUBLISH_FAILED,
|
|
420
|
+
details: {
|
|
421
|
+
topic: 'claims.created',
|
|
422
|
+
key: 'claim-1001',
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
meta: {
|
|
426
|
+
timestamp: new Date().toISOString(),
|
|
427
|
+
retryable: true,
|
|
428
|
+
},
|
|
429
|
+
}
|
|
372
430
|
```
|
|
373
431
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
Because `KAFKA_AUTO_CREATE_TOPICS_ENABLE=false`, topics should be created deliberately.
|
|
377
|
-
|
|
378
|
-
You have two good options:
|
|
379
|
-
|
|
380
|
-
- create topics from your service with `kafkaManager.provisionTopics()`
|
|
381
|
-
- allow UI-based creation by setting `KAFKA_UI_READONLY=false`
|
|
382
|
-
|
|
383
|
-
Example with this package:
|
|
432
|
+
### Provision topics
|
|
384
433
|
|
|
385
434
|
```ts
|
|
386
435
|
await kafkaManager.provisionTopics([
|
|
@@ -389,505 +438,458 @@ await kafkaManager.provisionTopics([
|
|
|
389
438
|
numPartitions: 6,
|
|
390
439
|
replicationFactor: 3,
|
|
391
440
|
},
|
|
441
|
+
{
|
|
442
|
+
topic: 'claims.updated',
|
|
443
|
+
numPartitions: 6,
|
|
444
|
+
replicationFactor: 3,
|
|
445
|
+
},
|
|
392
446
|
])
|
|
393
447
|
```
|
|
394
448
|
|
|
395
|
-
|
|
449
|
+
This is useful during service startup when your topics should exist before producers or consumers begin work.
|
|
396
450
|
|
|
397
|
-
|
|
451
|
+
### Disconnect on shutdown
|
|
398
452
|
|
|
399
|
-
|
|
400
|
-
- shared producer, admin, and consumer lifecycle
|
|
401
|
-
- logger and metrics hooks
|
|
402
|
-
- standard message envelope publishing
|
|
403
|
-
- managed consumer runner with dead-letter topic support
|
|
404
|
-
- health snapshot reporting
|
|
405
|
-
- safer startup behavior for concurrent connections
|
|
406
|
-
- stable error codes and structured exception details
|
|
453
|
+
Always close Kafka clients during application shutdown.
|
|
407
454
|
|
|
408
|
-
|
|
455
|
+
```ts
|
|
456
|
+
process.on('SIGINT', async () => {
|
|
457
|
+
await kafkaManager.disconnect()
|
|
458
|
+
process.exit(0)
|
|
459
|
+
})
|
|
409
460
|
|
|
410
|
-
|
|
461
|
+
process.on('SIGTERM', async () => {
|
|
462
|
+
await kafkaManager.disconnect()
|
|
463
|
+
process.exit(0)
|
|
464
|
+
})
|
|
465
|
+
```
|
|
411
466
|
|
|
412
|
-
|
|
467
|
+
### Inspect health
|
|
413
468
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
- managed consumers with DLQ handling
|
|
417
|
-
- consistent service-layer success and error responses
|
|
469
|
+
```ts
|
|
470
|
+
const health = kafkaManager.getHealthSnapshot()
|
|
418
471
|
|
|
419
|
-
|
|
472
|
+
console.log(health)
|
|
473
|
+
```
|
|
420
474
|
|
|
421
|
-
|
|
422
|
-
- schema validation for business payloads when required by your organization
|
|
423
|
-
- organization-specific tracing, alerting, and compliance rules
|
|
475
|
+
## Environment Setup
|
|
424
476
|
|
|
425
|
-
|
|
477
|
+
This package supports two environment styles:
|
|
426
478
|
|
|
427
|
-
|
|
479
|
+
1. one explicit broker list with `KAFKA_BROKERS`
|
|
480
|
+
2. derived brokers from your existing Kafka runtime variables
|
|
428
481
|
|
|
429
|
-
|
|
430
|
-
|
|
482
|
+
### Option 1: Use `KAFKA_BROKERS`
|
|
483
|
+
|
|
484
|
+
This is the simplest option.
|
|
485
|
+
|
|
486
|
+
### `.env`
|
|
487
|
+
|
|
488
|
+
```env
|
|
489
|
+
KAFKA_BROKERS=localhost:29092,localhost:39092,localhost:49092
|
|
431
490
|
```
|
|
432
491
|
|
|
433
|
-
|
|
492
|
+
### Code
|
|
434
493
|
|
|
435
494
|
```ts
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
KafkaManagerError,
|
|
440
|
-
OperationResponse,
|
|
441
|
-
} from 'sentinel-kafka-manager'
|
|
495
|
+
const kafkaManager = KafkaManager.fromEnv({
|
|
496
|
+
clientId: 'claims-service',
|
|
497
|
+
})
|
|
442
498
|
```
|
|
443
499
|
|
|
444
|
-
### 2
|
|
500
|
+
### Option 2: Use your current Kafka cluster variables
|
|
445
501
|
|
|
446
|
-
|
|
502
|
+
This matches the Kafka server setup you shared.
|
|
447
503
|
|
|
448
|
-
|
|
449
|
-
2. build brokers from environment variables
|
|
504
|
+
### For host machine apps
|
|
450
505
|
|
|
451
|
-
|
|
506
|
+
Use this when your Node.js service runs on the host machine.
|
|
507
|
+
|
|
508
|
+
### `.env`
|
|
509
|
+
|
|
510
|
+
```env
|
|
511
|
+
KAFKA_EXTERNAL_HOST=localhost
|
|
512
|
+
KAFKA_BROKER_1_EXTERNAL_PORT=29092
|
|
513
|
+
KAFKA_BROKER_2_EXTERNAL_PORT=39092
|
|
514
|
+
KAFKA_BROKER_3_EXTERNAL_PORT=49092
|
|
515
|
+
KAFKA_BROKER_INTERNAL_PORT=9092
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Code
|
|
452
519
|
|
|
453
520
|
```ts
|
|
454
|
-
const kafkaManager =
|
|
521
|
+
const kafkaManager = KafkaManager.fromEnv({
|
|
455
522
|
clientId: 'claims-service',
|
|
456
|
-
|
|
523
|
+
mode: 'external',
|
|
457
524
|
})
|
|
458
525
|
```
|
|
459
526
|
|
|
460
|
-
|
|
527
|
+
This resolves to:
|
|
528
|
+
|
|
529
|
+
```text
|
|
530
|
+
localhost:29092
|
|
531
|
+
localhost:39092
|
|
532
|
+
localhost:49092
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### For Dockerized apps on the Kafka network
|
|
536
|
+
|
|
537
|
+
Use this when your Node.js service runs inside Docker on the same Kafka network.
|
|
538
|
+
|
|
539
|
+
### `.env`
|
|
540
|
+
|
|
541
|
+
```env
|
|
542
|
+
KAFKA_BROKER_INTERNAL_PORT=9092
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Code
|
|
461
546
|
|
|
462
547
|
```ts
|
|
463
548
|
const kafkaManager = KafkaManager.fromEnv({
|
|
464
549
|
clientId: 'claims-service',
|
|
465
|
-
mode: '
|
|
550
|
+
mode: 'internal',
|
|
466
551
|
})
|
|
467
552
|
```
|
|
468
553
|
|
|
469
|
-
|
|
554
|
+
This resolves to:
|
|
555
|
+
|
|
556
|
+
```text
|
|
557
|
+
broker-1:9092
|
|
558
|
+
broker-2:9092
|
|
559
|
+
broker-3:9092
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Which Mode Should You Use?
|
|
470
563
|
|
|
471
|
-
|
|
564
|
+
- Use `mode: 'external'` when your service uses `localhost` broker ports like `29092`, `39092`, and `49092`
|
|
565
|
+
- Use `mode: 'internal'` when your service is inside Docker and should reach brokers like `broker-1:9092`
|
|
566
|
+
- Use `KAFKA_BROKERS` when you want the simplest and most explicit configuration
|
|
472
567
|
|
|
473
|
-
|
|
568
|
+
### Your Current Running Kafka Cluster
|
|
474
569
|
|
|
475
|
-
|
|
476
|
-
import { KafkaManager } from 'sentinel-kafka-manager'
|
|
570
|
+
Based on your running Docker containers, your Kafka cluster is exposed like this:
|
|
477
571
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
```
|
|
572
|
+
- `broker-1` -> host port `29092`
|
|
573
|
+
- `broker-2` -> host port `39092`
|
|
574
|
+
- `broker-3` -> host port `49092`
|
|
575
|
+
- `controller-1`, `controller-2`, and `controller-3` are controller-only nodes and should not be used by application clients
|
|
483
576
|
|
|
484
|
-
|
|
577
|
+
If your Node.js service runs on the host machine, use:
|
|
485
578
|
|
|
486
|
-
|
|
579
|
+
```env
|
|
580
|
+
KAFKA_EXTERNAL_HOST=localhost
|
|
581
|
+
KAFKA_BROKER_1_EXTERNAL_PORT=29092
|
|
582
|
+
KAFKA_BROKER_2_EXTERNAL_PORT=39092
|
|
583
|
+
KAFKA_BROKER_3_EXTERNAL_PORT=49092
|
|
584
|
+
KAFKA_BROKER_INTERNAL_PORT=9092
|
|
585
|
+
```
|
|
487
586
|
|
|
488
587
|
```ts
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
export const kafkaManager = KafkaManager.fromEnv({
|
|
588
|
+
const kafkaManager = KafkaManager.fromEnv({
|
|
492
589
|
clientId: 'claims-service',
|
|
493
590
|
mode: 'external',
|
|
494
|
-
logger: {
|
|
495
|
-
debug: (message, meta) => console.debug(message, meta),
|
|
496
|
-
info: (message, meta) => console.info(message, meta),
|
|
497
|
-
warn: (message, meta) => console.warn(message, meta),
|
|
498
|
-
error: (message, meta) => console.error(message, meta),
|
|
499
|
-
},
|
|
500
|
-
metrics: {
|
|
501
|
-
emit: (event) => {
|
|
502
|
-
console.log('METRIC', event)
|
|
503
|
-
},
|
|
504
|
-
},
|
|
505
591
|
})
|
|
506
592
|
```
|
|
507
593
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
### Publish a message
|
|
594
|
+
If your Node.js service runs inside Docker on the same Kafka network, use:
|
|
511
595
|
|
|
512
596
|
```ts
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
payload: {
|
|
517
|
-
claimId: 'claim-1001',
|
|
518
|
-
status: 'created',
|
|
519
|
-
source: 'claims-service',
|
|
520
|
-
},
|
|
597
|
+
const kafkaManager = KafkaManager.fromEnv({
|
|
598
|
+
clientId: 'claims-service',
|
|
599
|
+
mode: 'internal',
|
|
521
600
|
})
|
|
522
601
|
```
|
|
523
602
|
|
|
524
|
-
|
|
603
|
+
This package is designed to work with that exact broker layout.
|
|
525
604
|
|
|
526
|
-
|
|
527
|
-
- connects it once
|
|
528
|
-
- serializes `payload` with `JSON.stringify()`
|
|
605
|
+
## Local Infrastructure And Operations
|
|
529
606
|
|
|
530
|
-
###
|
|
607
|
+
### Create A Kafka Server With This Repo
|
|
531
608
|
|
|
532
|
-
|
|
609
|
+
This repository already includes a full local Kafka server setup in [docker-compose.yml](/var/www/html/matrix-project/sentinel-kafka-manager/docker-compose.yml) and sample environment values in [.env.local.example](/var/www/html/matrix-project/sentinel-kafka-manager/.env.local.example).
|
|
533
610
|
|
|
534
|
-
|
|
535
|
-
await kafkaManager.publishEnvelope({
|
|
536
|
-
topic: 'claims.created',
|
|
537
|
-
key: 'claim-1001',
|
|
538
|
-
envelope: {
|
|
539
|
-
eventId: 'evt-claim-1001',
|
|
540
|
-
eventType: 'claims.created',
|
|
541
|
-
version: '1.0.0',
|
|
542
|
-
timestamp: new Date().toISOString(),
|
|
543
|
-
source: 'claims-service',
|
|
544
|
-
traceId: 'trace-123',
|
|
545
|
-
tenantId: 'tenant-abc',
|
|
546
|
-
correlationId: 'corr-001',
|
|
547
|
-
payload: {
|
|
548
|
-
claimId: 'claim-1001',
|
|
549
|
-
status: 'created',
|
|
550
|
-
},
|
|
551
|
-
},
|
|
552
|
-
})
|
|
553
|
-
```
|
|
611
|
+
The stack includes:
|
|
554
612
|
|
|
555
|
-
|
|
613
|
+
- 3 KRaft controllers
|
|
614
|
+
- 3 Kafka brokers
|
|
615
|
+
- 1 Kafka UI instance
|
|
556
616
|
|
|
557
|
-
|
|
617
|
+
### 1. Create your `.env`
|
|
558
618
|
|
|
559
|
-
|
|
560
|
-
const producer = await kafkaManager.getProducer()
|
|
619
|
+
Copy the example file:
|
|
561
620
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
messages: [
|
|
565
|
-
{
|
|
566
|
-
key: 'claim-1001',
|
|
567
|
-
value: JSON.stringify({ claimId: 'claim-1001' }),
|
|
568
|
-
},
|
|
569
|
-
],
|
|
570
|
-
})
|
|
621
|
+
```bash
|
|
622
|
+
cp .env.local.example .env
|
|
571
623
|
```
|
|
572
624
|
|
|
573
|
-
|
|
625
|
+
If you want a standard localhost setup, the example values are already suitable.
|
|
574
626
|
|
|
575
|
-
|
|
576
|
-
const consumer = await kafkaManager.getConsumer('claims-service-group')
|
|
627
|
+
### 2. Start the Kafka cluster
|
|
577
628
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
})
|
|
629
|
+
```bash
|
|
630
|
+
docker compose --env-file .env up -d
|
|
631
|
+
```
|
|
582
632
|
|
|
583
|
-
|
|
584
|
-
eachMessage: async ({ topic, partition, message }) => {
|
|
585
|
-
const rawValue = message.value?.toString() ?? '{}'
|
|
586
|
-
const payload = JSON.parse(rawValue)
|
|
633
|
+
This creates:
|
|
587
634
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
635
|
+
- controller quorum on port `9093` inside Docker
|
|
636
|
+
- internal broker traffic on port `9092` inside Docker
|
|
637
|
+
- host-accessible broker ports `29092`, `39092`, and `49092`
|
|
638
|
+
- Kafka UI on `127.0.0.1:5001`
|
|
639
|
+
|
|
640
|
+
### 3. Stop the Kafka cluster
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
docker compose --env-file .env down
|
|
596
644
|
```
|
|
597
645
|
|
|
598
|
-
|
|
646
|
+
To also remove persisted Kafka data volumes:
|
|
599
647
|
|
|
600
|
-
|
|
648
|
+
```bash
|
|
649
|
+
docker compose --env-file .env down -v
|
|
650
|
+
```
|
|
601
651
|
|
|
602
|
-
|
|
652
|
+
### 4. Check container status
|
|
603
653
|
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
groupId: 'claims-service-group',
|
|
607
|
-
topic: 'claims.created',
|
|
608
|
-
dlqTopic: 'claims.created.dlq',
|
|
609
|
-
onMessage: async ({ message, headers, key }) => {
|
|
610
|
-
console.log('Processing:', {
|
|
611
|
-
key,
|
|
612
|
-
traceId: headers['x-trace-id'],
|
|
613
|
-
payload: message,
|
|
614
|
-
})
|
|
615
|
-
},
|
|
616
|
-
onError: async (error, context) => {
|
|
617
|
-
console.error('Consumer error:', {
|
|
618
|
-
error: error.message,
|
|
619
|
-
topic: context.topic,
|
|
620
|
-
offset: context.offset,
|
|
621
|
-
})
|
|
622
|
-
},
|
|
623
|
-
})
|
|
654
|
+
```bash
|
|
655
|
+
docker compose --env-file .env ps
|
|
624
656
|
```
|
|
625
657
|
|
|
626
|
-
|
|
658
|
+
### 5. Current broker endpoints
|
|
627
659
|
|
|
628
|
-
|
|
629
|
-
- a metric hook can receive the failure event
|
|
630
|
-
- the message can be published to a dead-letter topic when `dlqTopic` is set
|
|
660
|
+
For applications running on your host machine:
|
|
631
661
|
|
|
632
|
-
|
|
662
|
+
```text
|
|
663
|
+
localhost:29092
|
|
664
|
+
localhost:39092
|
|
665
|
+
localhost:49092
|
|
666
|
+
```
|
|
633
667
|
|
|
634
|
-
|
|
668
|
+
For applications running inside Docker on the same Compose network:
|
|
635
669
|
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
670
|
+
```text
|
|
671
|
+
broker-1:9092
|
|
672
|
+
broker-2:9092
|
|
673
|
+
broker-3:9092
|
|
674
|
+
```
|
|
641
675
|
|
|
642
|
-
|
|
643
|
-
await kafkaManager.publish({
|
|
644
|
-
topic: 'claims.created',
|
|
645
|
-
payload: { claimId: 'claim-1001' },
|
|
646
|
-
})
|
|
647
|
-
} catch (error) {
|
|
648
|
-
if (error instanceof KafkaManagerError) {
|
|
649
|
-
console.error('Kafka error', {
|
|
650
|
-
code: error.code,
|
|
651
|
-
message: error.message,
|
|
652
|
-
details: error.details,
|
|
653
|
-
})
|
|
676
|
+
Important:
|
|
654
677
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
678
|
+
- application clients must connect to brokers, not controllers
|
|
679
|
+
- controllers are internal cluster metadata nodes only
|
|
680
|
+
- `mode: 'external'` is for host-based apps
|
|
681
|
+
- `mode: 'internal'` is for Dockerized apps on the same network
|
|
659
682
|
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
```
|
|
683
|
+
### Kafka UI Usage
|
|
663
684
|
|
|
664
|
-
|
|
685
|
+
The Docker stack also starts Kafka UI for cluster inspection.
|
|
665
686
|
|
|
666
|
-
|
|
687
|
+
### Open the UI
|
|
667
688
|
|
|
668
|
-
|
|
669
|
-
import {
|
|
670
|
-
ErrorResponse,
|
|
671
|
-
OperationResponse,
|
|
672
|
-
SuccessResponse,
|
|
673
|
-
} from 'sentinel-kafka-manager'
|
|
689
|
+
Visit:
|
|
674
690
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
key: string
|
|
678
|
-
}>
|
|
691
|
+
```text
|
|
692
|
+
http://127.0.0.1:5001
|
|
679
693
|
```
|
|
680
694
|
|
|
681
|
-
|
|
695
|
+
### Login credentials
|
|
682
696
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
topic: 'claims.created',
|
|
689
|
-
key: 'claim-1001',
|
|
690
|
-
},
|
|
691
|
-
meta: {
|
|
692
|
-
timestamp: new Date().toISOString(),
|
|
693
|
-
traceId: 'trace-123',
|
|
694
|
-
},
|
|
695
|
-
}
|
|
697
|
+
By default, the UI uses the credentials from `.env`:
|
|
698
|
+
|
|
699
|
+
```env
|
|
700
|
+
KAFKA_UI_USERNAME=admin
|
|
701
|
+
KAFKA_UI_PASSWORD=Admin@007
|
|
696
702
|
```
|
|
697
703
|
|
|
698
|
-
|
|
704
|
+
### What the UI connects to
|
|
699
705
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
error: {
|
|
705
|
-
code: KafkaErrorCode.MESSAGE_PUBLISH_FAILED,
|
|
706
|
-
details: {
|
|
707
|
-
topic: 'claims.created',
|
|
708
|
-
key: 'claim-1001',
|
|
709
|
-
},
|
|
710
|
-
},
|
|
711
|
-
meta: {
|
|
712
|
-
timestamp: new Date().toISOString(),
|
|
713
|
-
retryable: true,
|
|
714
|
-
},
|
|
715
|
-
}
|
|
706
|
+
Kafka UI is preconfigured in `docker-compose.yml` to use all three brokers:
|
|
707
|
+
|
|
708
|
+
```text
|
|
709
|
+
broker-1:9092,broker-2:9092,broker-3:9092
|
|
716
710
|
```
|
|
717
711
|
|
|
718
|
-
|
|
712
|
+
The cluster name shown in the UI comes from:
|
|
719
713
|
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
{
|
|
723
|
-
topic: 'claims.created',
|
|
724
|
-
numPartitions: 6,
|
|
725
|
-
replicationFactor: 3,
|
|
726
|
-
},
|
|
727
|
-
{
|
|
728
|
-
topic: 'claims.updated',
|
|
729
|
-
numPartitions: 6,
|
|
730
|
-
replicationFactor: 3,
|
|
731
|
-
},
|
|
732
|
-
])
|
|
714
|
+
```env
|
|
715
|
+
KAFKA_UI_CLUSTER_NAME=Project Omni Enterprise Kafka
|
|
733
716
|
```
|
|
734
717
|
|
|
735
|
-
|
|
718
|
+
### What you can do in the UI
|
|
736
719
|
|
|
737
|
-
|
|
720
|
+
- view brokers and cluster health
|
|
721
|
+
- inspect topics and partitions
|
|
722
|
+
- browse messages
|
|
723
|
+
- inspect consumer groups
|
|
724
|
+
- check offsets and lag
|
|
738
725
|
|
|
739
|
-
|
|
726
|
+
### Read-only mode
|
|
740
727
|
|
|
741
|
-
|
|
742
|
-
process.on('SIGINT', async () => {
|
|
743
|
-
await kafkaManager.disconnect()
|
|
744
|
-
process.exit(0)
|
|
745
|
-
})
|
|
728
|
+
The example `.env` sets:
|
|
746
729
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
process.exit(0)
|
|
750
|
-
})
|
|
730
|
+
```env
|
|
731
|
+
KAFKA_UI_READONLY=true
|
|
751
732
|
```
|
|
752
733
|
|
|
753
|
-
|
|
734
|
+
That means the UI is intended for safe inspection only.
|
|
754
735
|
|
|
755
|
-
|
|
756
|
-
const health = kafkaManager.getHealthSnapshot()
|
|
736
|
+
If you want to create topics or make changes from the UI, change it to:
|
|
757
737
|
|
|
758
|
-
|
|
738
|
+
```env
|
|
739
|
+
KAFKA_UI_READONLY=false
|
|
759
740
|
```
|
|
760
741
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
This package supports two environment styles:
|
|
742
|
+
Then restart the stack:
|
|
764
743
|
|
|
765
|
-
|
|
766
|
-
|
|
744
|
+
```bash
|
|
745
|
+
docker compose --env-file .env up -d
|
|
746
|
+
```
|
|
767
747
|
|
|
768
|
-
|
|
748
|
+
### Typical UI flow
|
|
769
749
|
|
|
770
|
-
|
|
750
|
+
1. Start the Docker stack.
|
|
751
|
+
2. Open `http://127.0.0.1:5001`.
|
|
752
|
+
3. Log in with `KAFKA_UI_USERNAME` and `KAFKA_UI_PASSWORD`.
|
|
753
|
+
4. Open the configured cluster.
|
|
754
|
+
5. Go to Topics, Consumer Groups, or Brokers as needed.
|
|
771
755
|
|
|
772
|
-
### `.env`
|
|
756
|
+
### Docker Compose And `.env` Reference
|
|
773
757
|
|
|
774
|
-
|
|
775
|
-
KAFKA_BROKERS=localhost:29092,localhost:39092,localhost:49092
|
|
776
|
-
```
|
|
758
|
+
This project does not use a separate `Dockerfile` for Kafka. The Kafka server is created from `docker-compose.yml` and environment values from `.env`.
|
|
777
759
|
|
|
778
|
-
###
|
|
760
|
+
### Core Kafka image and cluster identity
|
|
779
761
|
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
762
|
+
```env
|
|
763
|
+
KAFKA_IMAGE=confluentinc/cp-kafka:7.6.6
|
|
764
|
+
KAFKA_CLUSTER_ID=MkU3OEVBNTcwNTJENDM2Qk
|
|
765
|
+
KAFKA_RESTART_POLICY=unless-stopped
|
|
766
|
+
KAFKA_STOP_GRACE_PERIOD=60s
|
|
767
|
+
KAFKA_NETWORK_NAME=kafka-network
|
|
784
768
|
```
|
|
785
769
|
|
|
786
|
-
|
|
770
|
+
- `KAFKA_IMAGE`: Kafka container image used by controllers and brokers
|
|
771
|
+
- `KAFKA_CLUSTER_ID`: shared KRaft cluster id for all nodes
|
|
772
|
+
- `KAFKA_NETWORK_NAME`: Docker network name used by the full stack
|
|
787
773
|
|
|
788
|
-
|
|
774
|
+
### Controller quorum settings
|
|
789
775
|
|
|
790
|
-
|
|
776
|
+
```env
|
|
777
|
+
KAFKA_CONTROLLER_PORT=9093
|
|
778
|
+
KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER
|
|
779
|
+
KAFKA_CONTROLLER_QUORUM_VOTERS=1@controller-1:9093,2@controller-2:9093,3@controller-3:9093
|
|
780
|
+
```
|
|
791
781
|
|
|
792
|
-
|
|
782
|
+
- controllers run only inside Docker
|
|
783
|
+
- apps should never use these controller addresses as Kafka client brokers
|
|
793
784
|
|
|
794
|
-
###
|
|
785
|
+
### Broker listener settings
|
|
795
786
|
|
|
796
787
|
```env
|
|
788
|
+
KAFKA_BROKER_BIND_ADDRESS=0.0.0.0
|
|
797
789
|
KAFKA_EXTERNAL_HOST=localhost
|
|
790
|
+
KAFKA_BROKER_INTERNAL_PORT=9092
|
|
791
|
+
KAFKA_BROKER_EXTERNAL_CONTAINER_PORT=19092
|
|
798
792
|
KAFKA_BROKER_1_EXTERNAL_PORT=29092
|
|
799
793
|
KAFKA_BROKER_2_EXTERNAL_PORT=39092
|
|
800
794
|
KAFKA_BROKER_3_EXTERNAL_PORT=49092
|
|
801
|
-
|
|
795
|
+
KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL
|
|
796
|
+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
|
|
802
797
|
```
|
|
803
798
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
const kafkaManager = KafkaManager.fromEnv({
|
|
808
|
-
clientId: 'claims-service',
|
|
809
|
-
mode: 'external',
|
|
810
|
-
})
|
|
811
|
-
```
|
|
799
|
+
- `KAFKA_EXTERNAL_HOST=localhost` exposes brokers to your host machine
|
|
800
|
+
- `KAFKA_BROKER_1_EXTERNAL_PORT`, `KAFKA_BROKER_2_EXTERNAL_PORT`, and `KAFKA_BROKER_3_EXTERNAL_PORT` are the ports your local apps should use
|
|
801
|
+
- `KAFKA_BROKER_INTERNAL_PORT=9092` is used by containers on the Docker network
|
|
812
802
|
|
|
813
|
-
|
|
803
|
+
### Replication and durability defaults
|
|
814
804
|
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
805
|
+
```env
|
|
806
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=3
|
|
807
|
+
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=3
|
|
808
|
+
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=2
|
|
809
|
+
KAFKA_DEFAULT_REPLICATION_FACTOR=3
|
|
810
|
+
KAFKA_MIN_INSYNC_REPLICAS=2
|
|
811
|
+
KAFKA_NUM_PARTITIONS=6
|
|
819
812
|
```
|
|
820
813
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
Use this when your Node.js service runs inside Docker on the same Kafka network.
|
|
814
|
+
These values are designed for the included three-broker cluster.
|
|
824
815
|
|
|
825
|
-
###
|
|
816
|
+
### Broker behavior
|
|
826
817
|
|
|
827
818
|
```env
|
|
828
|
-
|
|
819
|
+
KAFKA_AUTO_CREATE_TOPICS_ENABLE=false
|
|
820
|
+
KAFKA_DELETE_TOPIC_ENABLE=true
|
|
821
|
+
KAFKA_LOG_RETENTION_HOURS=168
|
|
822
|
+
KAFKA_LOG_SEGMENT_BYTES=1073741824
|
|
823
|
+
KAFKA_MESSAGE_MAX_BYTES=10485880
|
|
824
|
+
KAFKA_REPLICA_FETCH_MAX_BYTES=10485880
|
|
825
|
+
KAFKA_SOCKET_REQUEST_MAX_BYTES=104857600
|
|
829
826
|
```
|
|
830
827
|
|
|
831
|
-
|
|
828
|
+
- topics are not auto-created by default
|
|
829
|
+
- deleting topics is allowed
|
|
830
|
+
- retention is 7 days by default
|
|
832
831
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
832
|
+
### Storage and safety limits
|
|
833
|
+
|
|
834
|
+
```env
|
|
835
|
+
KAFKA_LOG_DIRS=/var/lib/kafka/data
|
|
836
|
+
KAFKA_ULIMIT_NOFILE_SOFT=65536
|
|
837
|
+
KAFKA_ULIMIT_NOFILE_HARD=65536
|
|
838
|
+
KAFKA_LOG_MAX_SIZE=100m
|
|
839
|
+
KAFKA_LOG_MAX_FILE=5
|
|
838
840
|
```
|
|
839
841
|
|
|
840
|
-
|
|
842
|
+
### Kafka UI settings
|
|
841
843
|
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
844
|
+
```env
|
|
845
|
+
KAFKA_UI_IMAGE=provectuslabs/kafka-ui:latest
|
|
846
|
+
KAFKA_UI_CONTAINER_NAME=kafka-ui
|
|
847
|
+
KAFKA_UI_HOSTNAME=kafka-ui
|
|
848
|
+
KAFKA_UI_RESTART_POLICY=unless-stopped
|
|
849
|
+
KAFKA_UI_BIND_ADDRESS=127.0.0.1
|
|
850
|
+
KAFKA_UI_PORT=5001
|
|
851
|
+
KAFKA_UI_DYNAMIC_CONFIG_ENABLED=false
|
|
852
|
+
KAFKA_UI_CLUSTER_NAME=Project Omni Enterprise Kafka
|
|
853
|
+
KAFKA_UI_READONLY=true
|
|
854
|
+
KAFKA_UI_AUTH_TYPE=LOGIN_FORM
|
|
855
|
+
KAFKA_UI_USERNAME=admin
|
|
856
|
+
KAFKA_UI_PASSWORD=Admin@007
|
|
846
857
|
```
|
|
847
858
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
-
|
|
851
|
-
- Use `mode: 'internal'` when your service is inside Docker and should reach brokers like `broker-1:9092`
|
|
852
|
-
- Use `KAFKA_BROKERS` when you want the simplest and most explicit configuration
|
|
859
|
+
- `KAFKA_UI_BIND_ADDRESS=127.0.0.1` keeps the UI local-only
|
|
860
|
+
- `KAFKA_UI_PORT=5001` publishes the UI on your machine
|
|
861
|
+
- `KAFKA_UI_READONLY=true` prevents write actions from the UI
|
|
853
862
|
|
|
854
|
-
|
|
863
|
+
### Healthcheck settings
|
|
855
864
|
|
|
856
|
-
|
|
865
|
+
```env
|
|
866
|
+
KAFKA_HEALTHCHECK_INTERVAL=10s
|
|
867
|
+
KAFKA_HEALTHCHECK_TIMEOUT=5s
|
|
868
|
+
KAFKA_HEALTHCHECK_RETRIES=15
|
|
869
|
+
KAFKA_HEALTHCHECK_START_PERIOD=30s
|
|
870
|
+
```
|
|
857
871
|
|
|
858
|
-
|
|
859
|
-
- `broker-2` -> host port `39092`
|
|
860
|
-
- `broker-3` -> host port `49092`
|
|
861
|
-
- `controller-1`, `controller-2`, and `controller-3` are controller-only nodes and should not be used by application clients
|
|
872
|
+
### Creating Topics
|
|
862
873
|
|
|
863
|
-
|
|
874
|
+
Because `KAFKA_AUTO_CREATE_TOPICS_ENABLE=false`, topics should be created deliberately.
|
|
864
875
|
|
|
865
|
-
|
|
866
|
-
KAFKA_EXTERNAL_HOST=localhost
|
|
867
|
-
KAFKA_BROKER_1_EXTERNAL_PORT=29092
|
|
868
|
-
KAFKA_BROKER_2_EXTERNAL_PORT=39092
|
|
869
|
-
KAFKA_BROKER_3_EXTERNAL_PORT=49092
|
|
870
|
-
KAFKA_BROKER_INTERNAL_PORT=9092
|
|
871
|
-
```
|
|
876
|
+
You have two good options:
|
|
872
877
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
clientId: 'claims-service',
|
|
876
|
-
mode: 'external',
|
|
877
|
-
})
|
|
878
|
-
```
|
|
878
|
+
- create topics from your service with `kafkaManager.provisionTopics()`
|
|
879
|
+
- allow UI-based creation by setting `KAFKA_UI_READONLY=false`
|
|
879
880
|
|
|
880
|
-
|
|
881
|
+
Example with this package:
|
|
881
882
|
|
|
882
883
|
```ts
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
884
|
+
await kafkaManager.provisionTopics([
|
|
885
|
+
{
|
|
886
|
+
topic: 'claims.created',
|
|
887
|
+
numPartitions: 6,
|
|
888
|
+
replicationFactor: 3,
|
|
889
|
+
},
|
|
890
|
+
])
|
|
887
891
|
```
|
|
888
892
|
|
|
889
|
-
This package is designed to work with that exact broker layout.
|
|
890
|
-
|
|
891
893
|
## API Summary
|
|
892
894
|
|
|
893
895
|
### `new KafkaManager(options)`
|
|
@@ -1138,7 +1140,9 @@ const kafkaManager = new KafkaManager({
|
|
|
1138
1140
|
})
|
|
1139
1141
|
```
|
|
1140
1142
|
|
|
1141
|
-
##
|
|
1143
|
+
## Operational Guidance
|
|
1144
|
+
|
|
1145
|
+
### Common Mistakes
|
|
1142
1146
|
|
|
1143
1147
|
- Do not connect application clients to KRaft controller nodes. Connect only to brokers.
|
|
1144
1148
|
- Do not use `mode: 'external'` inside Docker unless you truly want host-published ports.
|
|
@@ -1149,7 +1153,7 @@ const kafkaManager = new KafkaManager({
|
|
|
1149
1153
|
- Do not publish business-critical events without metadata. Prefer `publishEnvelope()` for traceability.
|
|
1150
1154
|
- Do not depend on raw driver error strings in application logic. Use `KafkaManagerError.code`.
|
|
1151
1155
|
|
|
1152
|
-
|
|
1156
|
+
### Recommended SaaS Pattern
|
|
1153
1157
|
|
|
1154
1158
|
For most SaaS services, the cleanest pattern is:
|
|
1155
1159
|
|
|
@@ -1160,13 +1164,13 @@ For most SaaS services, the cleanest pattern is:
|
|
|
1160
1164
|
5. Catch `KafkaManagerError` and branch on `error.code`.
|
|
1161
1165
|
6. Wrap service outcomes in `OperationResponse<T>` where useful.
|
|
1162
1166
|
|
|
1163
|
-
|
|
1167
|
+
### Notes
|
|
1164
1168
|
|
|
1165
1169
|
- Your current Kafka cluster works fine with PLAINTEXT, so SSL and SASL are optional unless your environment changes
|
|
1166
1170
|
- This package is designed for both host-based services and Dockerized services
|
|
1167
1171
|
- If you want the least surprise, use `KAFKA_BROKERS`
|
|
1168
1172
|
|
|
1169
|
-
##
|
|
1173
|
+
## Additional Documentation
|
|
1170
1174
|
|
|
1171
1175
|
- npm deployment guide: [NPM_DEPLOYMENT.md](/var/www/html/matrix-project/sentinel-kafka-manager/NPM_DEPLOYMENT.md)
|
|
1172
1176
|
- Example folder: [examples/service-module-usage/README.md](/var/www/html/matrix-project/sentinel-kafka-manager/examples/service-module-usage/README.md)
|