Hsn
Hsn

Reputation: 1238

How can i store in struct and use the value in different functions throughout the application?

I would like to store a mqtt client in a struct and use this client throughout the application.

My project structure looks like this:

-src
  -payloads
     -payload.go
  -repositories
     -repository.go 
-main.go

payload.go looks like this:

package payload

import MQTT "github.com/eclipse/paho.mqtt.golang"

type MQTTClient struct {
    Client MQTT.Client
}

in my repository.go I have a Connect() function like this:

func Connect() MQTT.Client {

    deviceID := flag.String("device", "", "GCP Device-Id")
    bridge := struct {
        host *string
        port *string
    }{
        flag.String("mqtt_host", "", "MQTT Bridge Host"),
        flag.String("mqtt_port", "", "MQTT Bridge Port"),
    }
    projectID := flag.String("project", "", "GCP Project ID")
    registryID := flag.String("registry", "", "Cloud IoT Registry ID (short form)")
    region := flag.String("region", "", "GCP Region")
    certsCA := flag.String("ca_certs", "", "Download https://pki.google.com/roots.pem")
    privateKey := flag.String("private_key", "", "Path to private key file")

    server := fmt.Sprintf("ssl://%v:%v", *bridge.host, *bridge.port)
    topic := struct {
        config    string
        telemetry string
    }{
        config:    fmt.Sprintf("/devices/%v/config", *deviceID),
        telemetry: fmt.Sprintf("/devices/%v/events/", *deviceID),
    }
    qos := flag.Int("qos", 0, "The QoS to subscribe to messages at")
    clientid := fmt.Sprintf("projects/%v/locations/%v/registries/%v/devices/%v",
        *projectID,
        *region,
        *registryID,
        *deviceID,
    )
    log.Println("[main] Loading Google's roots")
    certpool := x509.NewCertPool()
    pemCerts, err := ioutil.ReadFile(*certsCA)
    if err == nil {
        certpool.AppendCertsFromPEM(pemCerts)
    }

    log.Println("[main] Creating TLS Config")
    config := &tls.Config{
        RootCAs:      certpool,
        ClientAuth:   tls.NoClientCert,
        ClientCAs:    nil,
        Certificates: []tls.Certificate{},
        MinVersion:   tls.VersionTLS12,
    }

    flag.Parse()

    connOpts := MQTT.NewClientOptions().
        AddBroker(server).
        SetClientID(clientid).
        SetAutoReconnect(true).
        SetConnectRetry(true).
        SetDefaultPublishHandler(onMessageReceived).
        SetConnectionLostHandler(connLostHandler).
        SetReconnectingHandler(reconnHandler).
        SetTLSConfig(config)
    connOpts.SetUsername("unused")
    ///JWT Generation Starts from Here
    token := jwt.New(jwt.SigningMethodES256)
    token.Claims = jwt.StandardClaims{
        Audience:  *projectID,
        IssuedAt:  time.Now().Unix(),
        ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
    }
    //Reading key file
    log.Println("[main] Load Private Key")
    keyBytes, err := ioutil.ReadFile(*privateKey)
    if err != nil {
        log.Fatal(err)
    }
    //Parsing key from file
    log.Println("[main] Parse Private Key")
    key, err := jwt.ParseECPrivateKeyFromPEM(keyBytes)
    if err != nil {
        log.Fatal(err)
    }
    //Signing JWT with private key
    log.Println("[main] Sign String")
    tokenString, err := token.SignedString(key)
    if err != nil {
        log.Fatal(err)
    }
    //JWT Generation Ends here

    connOpts.SetPassword(tokenString)
    connOpts.OnConnect = func(c MQTT.Client) {
        if token := c.Subscribe(topic.config, byte(*qos), nil); token.Wait() && token.Error() != nil {
            log.Fatal(token.Error())
        }
    }

    client := MQTT.NewClient(connOpts)
    if token := client.Connect(); token.Wait() && token.Error() != nil {
        fmt.Printf("Not Connected..Retrying...  %s\n", server)
    } else {
        fmt.Printf("Connected to %s\n", server)
    }
    return client

}

No i am using this Connect() function in a go routine along with a grpc server like this..

func main() {
    fmt.Println("Server started at port 5005")
    lis, err := net.Listen("tcp", "0.0.0.0:5005")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }
    //Creating keepAlive channel for mqttt subscribe
    keepAlive := make(chan os.Signal)
    defer close(keepAlive)
    go func() {
        //Connecting to mqtt client
        client := repositories.Connect()
        //passing the client to struct
        res := &payload.MQTTClient{
            Client: client,
        }
        fmt.Println(res)
        //looking for interupt(Ctrl+C)
        value := <-keepAlive
        //If Ctrl+C is pressed then exit the application
        if value == os.Interrupt {
            fmt.Printf("Exiting the application")
            os.Exit(3)
        }
    }()
    s := grpc.NewServer()
    MqttRepository := repositories.NewMqttRepository()
    // It creates a new gRPC server instance
    rpc.NewMqttServer(s, MqttRepository)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)

    }
}

Now I am storing the client from the Connect() function into the struct but I am not able to use it. How can I store client somewhere in main.go and then use it throughout the aplication?

Upvotes: 1

Views: 223

Answers (1)

Nebril
Nebril

Reputation: 3273

If you want to have a struct available throughout your code, you might want to use a Singleton Pattern [1].

In Go you basically define an exported global variable in a package which will be available by all code that imports the package.

You can have client live in payload package (or whichever works for you, this is just an example):

package payload

import MQTT "github.com/eclipse/paho.mqtt.golang"

type MQTTClient struct {
    Client MQTT.Client
}

var SingletonClient *MQTTClient

Then in main.go instead of

res := &payload.MQTTClient{
    Client: client,
}

do

payload.SingletonClient = &payload.MQTTClient{
    Client: client,
}

Voila, now you can use payload.SingletonClient everywhere you import payload package.

Be aware, that Singleton pattern is not considered a good practice, it would be much better if you could structure your code to pass the client around where it's needed. Sometimes it may be useful though.

Be aware, that any code that tries to use payload.SingletonClient before it's set in main.go will fail because the client will not be initialized yet, so you might want to use some kind of a lock to make sure that the global variable is set before using it.

[1] https://en.wikipedia.org/wiki/Singleton_pattern

Upvotes: 2

Related Questions