Golang sharing configurations between packages
Asked Answered
D

3

18

So I just started learning the Go programming language and have spent hours on end looking at examples, references and so forth. As most of you would agree there is no better way to learn a language than to dive in and make something, which is what I am attempting to do at the moment. I am building a Restful web service. I have managed to get the basics running as well as inserting into db, registering routes etc. However for the past two days I have been struggling to implement application configurations/properties. It could just be that since I'm newbie my Go project architecture is all wrong hence why I am having such difficulty with this. Without further a due here is my project structure

src
   server
      database
         dbaccess.go
         dbcomm.go
      handling
         handler.go
         handlercomm.go
      models
         config.go
         response.go
         user.go
      routing
         routes.go
      main.go

Here is my config.go

package models

import (
   "io/ioutil"
   "encoding/json"
)

type Config struct  {
   Db map[string]string `json:"db"`
   Server map[string]string `json:"server"`
}


func NewConfig(fname string) *Config{
   data,err := ioutil.ReadFile(fname)
   if err != nil{
      panic(err)
   }
   config := Config{}
   err = json.Unmarshal(data,&config)
   if err != nil {
   panic(err)
}
return config

This is my main

func main(){
    args := os.Args[1:]
    if len(args) == 0{
       fmt.Println("********************\nMust specify a config file   in args\n********************")
    os.Exit(1)
   }

   config := models.NewConfig(args[0])
   port := config.Server["PORT"]

   router := routing.NewRouter()
   fmt.Printf(  "-------------------------------------------------\n"+
        "Listening and Serving on Port %s\n"+
        "-------------------------------------------------",port)

   log.Fatal(http.ListenAndServe(":"+port,router))
 }

And finally this is where my routes get mapped

type Route struct {
   Name string
   Method string
   Pattern string
   HandlerFunc http.HandlerFunc
}

var routes = []Route{
   Route{
    "signup",
    "POST",
    "/signup",
    handling.PostSignUpUser,
   },

   Route{
    "authenticate",
    "POST",
    "/login",
    handling.PostLogin,
   },
}

func NewRouter() *mux.Router{
 router :=  mux.NewRouter().StrictSlash(true)
 for _,route := range routes{       
    router.Methods(route.Method)
          .Path(route.Pattern)
          .Name(route.Name)
          .Handler(route.HandlerFunc)
}

return router
}

So as you can see in my Main I initialise the relevant configurations from a file which is fine. But the issue is how would I go about using that same config object from main in the database package,since I will need to set Host,Ports etc ? I could parse the file again but I would prefer if I could share that one object from the start. Please point me in the right direction

Dardar answered 10/4, 2016 at 9:33 Comment(0)
S
5

What I would suggest is declaring a global variable in config.go and initialize it using the init() function. That way, you know that the variable will always be initialized when any package imports it. Here's some code:

package models

import (
   "io/ioutil"
   "encoding/json"
)


var (

    Configuration Config 
)


init() {

    args := os.Args[1:]
    if len(args) == 0{
       fmt.Println("********************\nMust specify a config file   in args\n********************")
       os.Exit(1)
   }

   Configuration = NewConfig(args[0]) // configuration initialized here
}

type Config struct  {
   Db map[string]string `json:"db"`
   Server map[string]string `json:"server"`
}


func NewConfig(fname string) *Config{
   data,err := ioutil.ReadFile(fname)
   if err != nil{
      panic(err)
   }
   config := Config{}
   err = json.Unmarshal(data,&config)
   if err != nil {
      panic(err)
   }
   return config
}

var() is going to run before init(), but init() will run before the code in the package importing it. So if main.go imports the models package, then init() in models will run before any code inside main.go and thus the variable Configuration will be initialized before it is used.

Effective Go explanation of init()

Sesquialtera answered 18/4, 2016 at 12:21 Comment(1)
@MarvinCollins Why? There are some occasions where global variables and init functions are useful. I'd argue that this is one of those occasions.Sesquialtera
V
2

Now what you want just is to provide a variable can be used in another package,the solution is easy,remember that if you declare a variable name begins with uppercase letter:[A-Z],this variable can be visible and used in another package in go.

So you just need to rename config in your main.go to Config and extract it as global variable:

var Config *models.Config
func main(){
args := os.Args[1:]
if len(args) == 0{
   fmt.Println("********************\nMust specify a config file   in args\n********************")
os.Exit(1)
}

Config = models.NewConfig(args[0])
port := Config.Server["PORT"]

router := routing.NewRouter()
fmt.Printf(  "-------------------------------------------------\n"+
    "Listening and Serving on Port %s\n"+
    "-------------------------------------------------",port)

log.Fatal(http.ListenAndServe(":"+port,router))
}

when you want to use it in another package,just call <package name>.Config,the package name is the package name which your main.go belongs to,maybe main in your case.

Vespiary answered 10/4, 2016 at 12:53 Comment(6)
Thanks for the answer and yes I am aware of the uppercase scope rule however when I tried your solution the config was not picked up by other packages. So my main.go is in package main I renamed the variable to Config as suggested then tried referencing it in database/dbaccess.go . However I get unresolved reference seems like it did not workDardar
@MrSSS16,sorry,you should also declare it as a global variable,My answer has been updated.Vespiary
I don't see your change in the answer and still have the same issue where the other packages can't detect my Config from main.go. So I declared in my main var Config *models.Config but I can't access this from other packages, specifically database.Dardar
@MrSSS16,maybe network issue,I add the example code,according your newest question,have you import the main package by add code import "xx/main"?Vespiary
Right but from what I read it is discouraged to do that but rather define the 'var Config *models.config' in a separate package then reference it from there rather then import the main package into sub packages. So I defined the global config inside 'Config.go'. The issue is that the variable is empty when I access it from 'database'Dardar
Is there a better way to prevent modification of the global configuration?Pilothouse
D
0

I've built a library just for that, a toolbox factory (singletons) & an agnostic config parser (yaml, json and toml), give it a look, watch the example: SpareBox

Dicot answered 31/7, 2018 at 12:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.