1
\$\begingroup\$

I have a web application that I'm writing in Go, using the Iris package. This app needs to listen to Azure Service Bus for messages, and take action when they come in. This is my first time writing Go, and I want to make sure I am using the concurrency model properly.

The requirements for the app lifecycle are fairly straightforward:

  1. when the main Iris app starts, start the ASB service, without blocking the main Iris app
  2. ASB service listens for messages coming in forever
  3. when the main Iris app shuts down, close the ASB subscription and shut the singleton down

Is this idiomatic/correct/safe?

This is my code atm:

// main.go
func main() {
    err := godotenv.Load()

    if err != nil {
        log.Fatal("Error loading environment file")
    }

    apiPort := os.Getenv("API_PORT")

    if apiPort == "" {
        apiPort = "8080"
    }

    app := iris.New()

    broker := messagebroker.GetMessageBroker()

    go func() {
        broker.Init()
    }()

    app.Logger().SetLevel("debug")
    app.Get("/jobs", listJobs)

    // graceful shutdown
    idleConnsClosed := make(chan struct{})
    iris.RegisterOnInterrupt(func() {
        timeout := 10 * time.Second
        ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
        defer cancel()

        broker.Shutdown()

        println("\nShutting Down Iris")
        // close all hosts
        app.Shutdown(ctx)
        close(idleConnsClosed)
    })

    app.Listen(fmt.Sprintf(":%s", apiPort), iris.WithoutInterruptHandler, iris.WithoutServerError((iris.ErrServerClosed)))
    <-idleConnsClosed
}

func listJobs(ctx iris.Context) {
    ctx.Text("Ok")
}
// message-broker.go
type MessageBroker interface {
    Shutdown() bool
    Init()
}

type messageBroker struct{}

var broker *messageBroker
var once sync.Once
var stopListener context.CancelFunc

func (mb messageBroker) Init() {
    err := godotenv.Load()

    if err != nil {
        log.Fatal("Error loading environment file")
    }

    connectString := os.Getenv("ASB_CONNECT_STRING")

    if connectString == "" {
        log.Fatal("No Service Bus credentials")
    }

    println("\nConnecting to Service Bus")
    client, err := azservicebus.NewClientFromConnectionString(connectString, nil)

    if err != nil {
        panic(err)
    }

    receiver, err := client.NewReceiverForQueue("jobstestqueue", nil)

    if err != nil {
        panic(err)
    }

    println("\nConnected to Service Bus")
    ctx, cancel := context.WithCancel(context.Background())
    stopListener = cancel

    out := make(chan string)

    for {
        messages, err := receiver.ReceiveMessages(ctx, 1, nil)

        if err != nil {
            panic(err)
        }

        go func() {
            for _, message := range messages {
                var body []byte = message.Body
                out <- string(body)

                err = receiver.CompleteMessage(ctx, message, nil)

                if err != nil {
                    var sbErr *azservicebus.Error

                    if errors.As(err, &sbErr) && sbErr.Code == azservicebus.CodeLockLost {
                        // The message lock has expired. This isn't fatal for the client, but it does mean
                        // that this message can be received by another Receiver (or potentially this one!).
                        fmt.Printf("Message lock expired\n")

                        // You can extend the message lock by calling receiver.RenewMessageLock(msg) before the
                        // message lock has expired.
                        continue
                    }

                    panic(err)
                }
            }
        }()

        for {
            select {
            case msg := <-out:
                fmt.Printf("Message received with body: %s\n", msg)
            }
        }
    }
}

func (mb messageBroker) Shutdown() bool {
    println("\nClosing broker subscriptions")
    stopListener()
    return true
}

// Returns the message broker singleton
func GetMessageBroker() MessageBroker {
    once.Do(func() {
        broker = &messageBroker{}
    })

    return *broker
}

```
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.