@@ -42,7 +42,7 @@ const (
4242 broadcastStartupMaxWait = 15 * time .Minute // only checked on group allocation failure
4343 nonBroadcastReaderIdleTimeout = 5 * time .Minute
4444 broadcastReaderIdleTimeout = baseBroadcastHeartbeat * 3 // This cannot be < 1s: Kafka takes a while to deliver events after reader startup or generation change
45- maxConsumerGroupNameLength = 35
45+ maxConsumerGroupNameLength = 55 // actual limit is 249 characters, but we need to subtract this from the topic name lenght limit so we use less
4646 dialTimeout = time .Minute * 2
4747 deleteTimeout = time .Minute * 4
4848 describeTimeout = time .Minute * 4
@@ -80,7 +80,7 @@ const (
8080)
8181
8282var (
83- maxTopicNameLength = 255 - maxConsumerGroupNameLength - len (deadLetterTopicPostfix ) - 1
83+ maxTopicNameLength = 249 - maxConsumerGroupNameLength - len (deadLetterTopicPostfix ) - 1 // 249 is actual limit
8484 LegalTopicNames = regexp .MustCompile (fmt .Sprintf (`^[-._a-zA-Z0-9]{1,%d}$` , maxTopicNameLength ))
8585 legalConsumerGroupNames = regexp .MustCompile (fmt .Sprintf (`^[-._a-zA-Z0-9]{1,%d}$` , maxConsumerGroupNameLength ))
8686)
@@ -102,8 +102,8 @@ type LibraryNoDB struct {
102102 broadcast * group
103103 startTime time.Time
104104 ready atomic.Int32
105- topicConfig map [string ]kafka.TopicConfig
106- topicsWork pwork.Work [string , topicsWhy ]
105+ topicConfig map [string ]kafka.TopicConfig // un-prefixed
106+ topicsWork pwork.Work [string , topicsWhy ] // un-prefixed in APIs
107107 topicListingStarted sync.Once
108108 topicsHaveBeenListed chan struct {}
109109 mustRegisterTopics bool
@@ -124,6 +124,7 @@ type LibraryNoDB struct {
124124 instanceID int32
125125 lazyProduce bool
126126 skipNotifier bool
127+ prefix string // prefixes all topics and consumer groups
127128
128129 // lock must be held when....
129130 //
@@ -157,13 +158,13 @@ const (
157158)
158159
159160type group struct {
160- topics map [string ]* topicHandlers
161- maxIdle time.Duration // reset reader when idle for this long
161+ topics map [string ]* topicHandlers // unprefixed topic -> handlers
162+ maxIdle time.Duration // reset reader when idle for this long
162163}
163164
164165type topicHandlers struct {
165- handlerNames []string // so that iteration is deterministic
166- handlers map [string ]* registeredHandler
166+ handlerNames []string // so that iteration is deterministic
167+ handlers map [string ]* registeredHandler // handlerName -> handler
167168}
168169
169170type registeredHandler struct {
@@ -197,7 +198,7 @@ type handlerSuccess struct {
197198type HandlerOpt func (* registeredHandler , * LibraryNoDB )
198199
199200type canHandle interface {
200- GetTopic () string
201+ GetTopic () string // unprefixed
201202 Handle (ctx context.Context , handlerInfo eventmodels.HandlerInfo , message []* kafka.Message ) []error
202203 Batch () bool
203204}
@@ -262,6 +263,17 @@ func (lib *Library[ID, TX, DB]) SetBroadcastConsumerMaxLocks(max uint32) {
262263 lib .broadcastConsumerMaxLock = max
263264}
264265
266+ // SetPrefix sets a string prefix used for all topics and all consumer groups. Keep this
267+ // short since max topic length is :
268+ //
269+ // 249 - 55 (maxConsumerGroupLength) - len("-dead-letter") - len(prefix) - 1
270+ func (lib * Library [ID , TX , DB ]) SetPrefix (prefix string ) {
271+ lib .lock .Lock ()
272+ defer lib .lock .Unlock ()
273+ lib .mustNotBeRunning ("attempt configure event library that is already processing" )
274+ lib .prefix = prefix
275+ }
276+
265277// DoNotLockBroadcastConsumerNumbers must be used before starting the library. If called,
266278// no database locks will be taken to reserve broadcast consumer numbers. There is a
267279// tradeoff: this saves a database connection that would otherwise sit around holding
@@ -670,7 +682,7 @@ func (lib *Library[ID, TX, DB]) getConsumerGroupCoordinator(ctx context.Context,
670682 }
671683 resp , err := controller .FindCoordinator (ctx , & kafka.FindCoordinatorRequest {
672684 Addr : controller .Addr ,
673- Key : consumerGroup .String (),
685+ Key : lib . addPrefix ( consumerGroup .String () ),
674686 KeyType : kafka .CoordinatorKeyTypeConsumer ,
675687 })
676688 if err != nil {
@@ -743,3 +755,34 @@ func NewConsumerGroup(name string) ConsumerGroupName {
743755
744756func (n consumerGroupName ) String () string { return string (n ) }
745757func (n consumerGroupName ) name () consumerGroupName { return n }
758+
759+ func (lib * LibraryNoDB ) validateTopic (unprefixedTopic string ) error {
760+ if len (unprefixedTopic )+ len (lib .prefix ) > maxTopicNameLength {
761+ return errors .Errorf ("topic name (%s) is too long" , unprefixedTopic )
762+ }
763+ if ! LegalTopicNames .MatchString (unprefixedTopic ) {
764+ return errors .Errorf ("topic name (%s) is invalid" , unprefixedTopic )
765+ }
766+ return nil
767+ }
768+
769+ // RemovePrefix removes a prefix from a topic or consumer group that was added
770+ // because the library had SetPrefix called previously.
771+ func (lib * LibraryNoDB ) RemovePrefix (topicOrConsumerGroup string ) string {
772+ if lib .prefix == "" {
773+ return topicOrConsumerGroup
774+ }
775+ return strings .TrimPrefix (topicOrConsumerGroup , lib .prefix )
776+ }
777+
778+ func (lib * LibraryNoDB ) addPrefix (topicOrConsumerGroup string ) string {
779+ return lib .prefix + topicOrConsumerGroup
780+ }
781+
782+ func (lib * LibraryNoDB ) addPrefixes (topicsOrConsumerGroups []string ) []string {
783+ p := make ([]string , len (topicsOrConsumerGroups ))
784+ for i , unprefixed := range topicsOrConsumerGroups {
785+ p [i ] = lib .prefix + unprefixed
786+ }
787+ return p
788+ }
0 commit comments