d-coder
d-coder

Reputation: 13943

Better way to handle the cases depending on types in Go lang

I'm new to Go lang and I could use some suggestions on how to refactor the code. All I got to do is depending on the success or error from Sarama (Apache Kafka thing in go) I need to log and forward it further. So far my code looks like this

go func() {
    for err := range producer.Errors() {
        batchID := err.Msg.Metadata.(ackMeta).batchID # notice the struct here
        statusChan := err.Msg.Metadata.(ackMeta).statusChan
        statusChan <- false
        close(statusChan)
        logs.Debug(appName, "Signalled failure on statusChan for batch ", batchID)
        logs.Error(appName, "Failed to publish data to analyzer for batchID: ", batchID, err)
    }
}()

go func() {
    for succ := range producer.Successes() {
        batchID := succ.Metadata.(ackMeta).batchID        # notice the struct here
        statusChan := succ.Metadata.(ackMeta).statusChan
        statusChan <- true
        close(statusChan)
        logs.Debug(appName, "Signalled success on statusChan for batch ", batchID)
        logs.Debug(appName, "Successfully  published data to analyzer:", succ.Topic, succ.Key, succ.Partition, succ.Offset, succ.Metadata)
    }

I think I can do a better job and wrap the whole thing in a single function but so far I can't think of any other apart from using the switch case as shown here

func checkSuccessOrFailAck(msg interface{}) {
    switch msgType := msg.(type) {
    case producer.Errors:
        batchID := msg.Msg.Metadata.(ackMeta).batchID
        statusChan := msg.Msg.Metadata.(ackMeta).statusChan
        statusChan <- false
        close(statusChan)
        logs.Debug(appName, "Signalled failure on statusChan for batch ", batchID)
        logs.Error(appName, "Failed to publish data to analyzer for batchID: ", batchID, msg)
    case producer.Successes:
        batchID := msg.Metadata.(ackMeta).batchID
        statusChan := msg.Metadata.(ackMeta).statusChan
        statusChan <- true
        close(statusChan)
        logs.Debug(appName, "Signalled success on statusChan for batch ", batchID)
        logs.Debug(appName, "Successfully  published data to analyzer:", succ.Topic, succ.Key, succ.Partition, succ.Offset, succ.Metadata)
    }

}

The types of messages are different and so is the way I extract the attributes from it. But I'm not happy with this approach as the statements are more than the previous one. Could there be a better way to think to write ?

Upvotes: 0

Views: 49

Answers (2)

Adrian
Adrian

Reputation: 46413

You could do them both in a single goroutine using a select:

go func() {
    errsOpen := true
    succsOpen := true
    for errsOpen && succsOpen {
        select {
        case err,errsOpen := <- producer.Errors()
            batchID := err.Msg.Metadata.(ackMeta).batchID # notice the struct here
            statusChan := err.Msg.Metadata.(ackMeta).statusChan
            statusChan <- false
            close(statusChan)
            logs.Debug(appName, "Signalled failure on statusChan for batch ", batchID)
            logs.Error(appName, "Failed to publish data to analyzer for batchID: ", batchID, err)

        case succ,succsOpen := <- producer.Successes()
            batchID := succ.Metadata.(ackMeta).batchID        # notice the struct here
            statusChan := succ.Metadata.(ackMeta).statusChan
            statusChan <- true
            close(statusChan)
            logs.Debug(appName, "Signalled success on statusChan for batch ", batchID)
            logs.Debug(appName, "Successfully  published data to analyzer:", succ.Topic, succ.Key, succ.Partition, succ.Offset, succ.Metadata)
        }
    }
}()

This will loop until both channels have been closed, and at each loop iteration, handle a value from whichever channel has a value waiting to be received (or if both have values waiting, choose one at random).

Upvotes: 0

Eli Bendersky
Eli Bendersky

Reputation: 273366

First, it's not clear if the second code works at all. Since Errors() and Successes() return channels, you'd need to select to read from both of them simultaneously without blocking either.

Since succ and err have different types in the code, I'm not sure you can do it much shorter than your original sample. Go doesn't have generics yet, so writing code that would unify the two is challenging. If you really need it I'd try to find the closest points where the type is the same - it should be easier to unify code from that point on. for example err.Msg and succ are the same type?

Moreover, even if Go had generics already, it's not clear unifying these would be a win. Go's philosophy is that a little code repetition is better than over-complicated abstractions. The code isn't the shortest it could be in theory, but it's explicit and clear.

Upvotes: 1

Related Questions