11import * as util from 'util' ;
2- import { isUndefined } from '@nestjs/common/utils/shared.utils' ;
2+ import { isUndefined , isNil } from '@nestjs/common/utils/shared.utils' ;
33import { Logger } from '@nestjs/common/services/logger.service' ;
44import { loadPackage } from '@nestjs/common/utils/load-package.util' ;
55import { Observable } from 'rxjs' ;
@@ -20,7 +20,8 @@ import {
2020 EachMessagePayload ,
2121 Message ,
2222 KafkaMessage ,
23- logLevel
23+ logLevel ,
24+ ConsumerGroupJoinEvent
2425} from '../external/kafka.interface' ;
2526import { KafkaHeaders } from '../enums' ;
2627
@@ -40,6 +41,10 @@ export class ClientKafka extends ClientProxy {
4041 private readonly clientId : string ;
4142 private readonly groupId : string ;
4243
44+ private consumerAssignments : { [ key : string ] : number [ ] } = { } ;
45+
46+ private static REPLY_PATTERN_AFFIX : string = '.reply' ;
47+
4348 constructor ( protected readonly options : KafkaOptions [ 'options' ] ) {
4449 super ( ) ;
4550 this . brokers = this . getOptionsProp ( this . options . client , 'brokers' ) || [ KAFKA_DEFAULT_BROKER ] ;
@@ -66,22 +71,44 @@ export class ClientKafka extends ClientProxy {
6671 return this . producer ;
6772 }
6873 this . client = this . createClient ( ) ;
74+
6975 this . producer = this . client . producer ( this . options . producer || { } ) ;
7076 this . consumer = this . client . consumer ( Object . assign ( this . options . consumer || { } , {
7177 groupId : this . groupId
7278 } ) ) ;
7379
80+ // set member assignments on join and rebalance
81+ this . consumer . on ( this . consumer . events . GROUP_JOIN , ( data : ConsumerGroupJoinEvent ) => {
82+ this . consumerAssignments = data . payload . memberAssignment ;
83+ } ) ;
84+
85+ // connect the producer and consumer
7486 await this . producer . connect ( ) ;
7587 await this . consumer . connect ( ) ;
7688
77- // @TODO : Use descriptors to define the reply topics
78- // Run the consumer
79- await this . consumer . subscribe ( { topic : 'math.sum.reply' } ) ;
89+ // bind the topics
90+ await this . bindTopics ( ) ;
91+
92+ return this . producer ;
93+ }
94+
95+ public async bindTopics ( ) : Promise < void > {
96+ const requestPatterns = [ ...this . requestMap . keys ( ) ] ;
97+
98+ await Promise . all ( requestPatterns . map ( async requestPattern => {
99+ // get the reply pattern
100+ const replyPattern = this . getReplyPattern ( requestPattern , ClientKafka . REPLY_PATTERN_AFFIX ) ;
101+
102+ // subscribe to the pattern of the topic
103+ await this . consumer . subscribe ( {
104+ topic : replyPattern
105+ } ) ;
106+ } ) ) ;
107+
108+ // run the consumer to start listening on the reply topics
80109 await this . consumer . run ( Object . assign ( this . options . run || { } , {
81110 eachMessage : this . createResponseCallback ( )
82111 } ) ) ;
83-
84- return this . producer ;
85112 }
86113
87114 public createClient < T = any > ( ) : T {
@@ -118,19 +145,10 @@ export class ClientKafka extends ClientProxy {
118145
119146 public createResponseCallback ( ) : ( payload : EachMessagePayload ) => any {
120147 return ( payload : EachMessagePayload ) => {
121- // const { err, response, isDisposed, id } = JSON.parse(
122- // buffer.toString(),
123- // ) as WritePacket & PacketId;
124-
125148 const packet = this . deserialize ( payload ) ;
126149
127- this . logger . error ( util . format ( 'createResponseCallback() fn() packet: %o' , packet ) ) ;
128-
129150 const callback = this . routingMap . get ( packet . id ) ;
130151
131- this . logger . error ( util . format ( 'createResponseCallback() fn() this.routingMap: %o' , this . routingMap ) ) ;
132- this . logger . error ( util . format ( 'createResponseCallback() fn() callback: %o' , callback ) ) ;
133-
134152 if ( ! callback ) {
135153 return undefined ;
136154 }
@@ -188,19 +206,37 @@ export class ClientKafka extends ClientProxy {
188206 } , this . options . send || { } ) ) ;
189207 }
190208
191- private getReplyTopic ( pattern : string ) : string {
192- return `${ pattern } .reply` ;
193- }
194-
195- private getReplyPartition ( topic : string ) : string {
209+ private getReplyPartition ( topic : string ) : number {
196210 // this.consumer.describeGroup().then((description) => {
197- // this.logger.error(util.format('getReplyTopicPartition(): topic: %s groupDescription: %o', topic, description));
211+ // // this.logger.error(util.format('getReplyTopicPartition(): groupDescription: %o', description));
212+
213+ // description.members.forEach((member) => {
214+ // // const memberMetadata = kafkaPackage.AssignerProtocol.MemberMetadata.decode(member.memberMetadata);
215+ // const memberAssignment = kafkaPackage.AssignerProtocol.MemberAssignment.decode(member.memberAssignment);
216+
217+ // this.logger.error(util.format('getReplyTopicPartition(): groupDescription.member[i]: %o', member));
218+ // // this.logger.error(util.format('getReplyTopicPartition(): groupDescription.member[i] metadata: %o', memberMetadata));
219+ // this.logger.error(util.format('getReplyTopicPartition(): groupDescription.member[i] assignment: %o', memberAssignment));
220+ // });
198221 // });
199222
200- return '0' ;
201- }
223+ // return 0;
202224
203- // private getReplyTopic
225+ // get topic assignment
226+ const topicAssignments = this . consumerAssignments [ topic ] ;
227+
228+ // throw error
229+ if ( isUndefined ( topicAssignments ) ) {
230+ throw new Error ( `Unable to send the message request because the client consumer is not subscribed to the topic (${ topic } ).` ) ;
231+ }
232+
233+ // if the current member isn't listening to any partitions on the topic then throw an error.
234+ if ( isUndefined ( topicAssignments [ 0 ] ) ) {
235+ throw new Error ( `Unable to send the message request because the client consumer subscribed to the topic (${ topic } ) is not assigned any partitions.` ) ;
236+ }
237+
238+ return topicAssignments [ 0 ] ;
239+ }
204240
205241 protected publish (
206242 partialPacket : ReadPacket ,
@@ -209,18 +245,9 @@ export class ClientKafka extends ClientProxy {
209245 try {
210246 const packet = this . assignPacketId ( partialPacket ) ;
211247 const pattern = this . normalizePattern ( partialPacket . pattern ) ;
212- const replyTopic = this . getReplyTopic ( pattern ) ;
248+ const replyTopic = this . getReplyPattern ( pattern , ClientKafka . REPLY_PATTERN_AFFIX ) ;
213249 const replyPartition = this . getReplyPartition ( replyTopic ) ;
214250
215- // subscribe
216- // this.consumer.subscribe({
217- // topic: replyTopic
218- // }).then(() => {
219- // return this.consumer.run(Object.assign(this.options.run || {}, {
220- // eachMessage: this.createResponseCallback()
221- // }));
222- // });
223-
224251 // set the route
225252 this . routingMap . set ( packet . id , callback ) ;
226253
@@ -234,8 +261,6 @@ export class ClientKafka extends ClientProxy {
234261 packet . data . headers [ KafkaHeaders . REPLY_TOPIC ] = replyTopic ;
235262 packet . data . headers [ KafkaHeaders . REPLY_PARTITION ] = replyPartition ;
236263
237- this . logger . error ( util . format ( 'publish() packet %o' , packet ) ) ;
238-
239264 // send through unhandled promise
240265 this . producer . send ( Object . assign ( {
241266 topic : pattern ,
0 commit comments