SoFunction
Updated on 2025-03-03

Go implements a configuration package for detailed explanation

Configuration files are an indispensable part of modern software development. When writing a Go project, whether it is a simple single-file script or a huge microservice project, the flexibility and scalability of the program need to depend on the loading of configuration files. The configuration file can contain various settings of the program, such as port number, database connection information, authentication keys, etc. These settings may need to be adjusted according to different environments and deployment conditions. Therefore, a reliable configuration file loading mechanism is crucial to the successful implementation of the project. This article will explore how to load and manage configurations more conveniently in Go projects.

In Go, there are many ways to load and manage configurations, such as using command line flags, environment variables, or reading files in a specific format. Which method to choose depends on the specific needs of the project and the developer's preferences.

need

According to my development experience, a configuration package usually needs to support the following functions:

  • It supports loading configuration files from environment variables and command line flags, and then mapping the configuration file content into the Go structure. This process is called deserialization.

  • It supports writing configuration information in the Go structure into the configuration file. This process is called serialization.

  • At least, it must support the two most common configuration file formats, YAML and JSON.

config package implementation

Deserialization

Deserialization operation is the most commonly used function of configuration packages. Through deserialization, we can map the configuration file contents into the Go structure. So let's first implement how to deserialize configuration files.

type FileType int

const (
	FileTypeYAML = iota
	FileTypeJSON
)

func LoadConfig(filename string, cfg interface{}, typ FileType) error {
	data, err := (filename)
	if err != nil {
		return ("ReadFile: %v", err)
	}
	switch typ {
	case FileTypeYAML:
		return (data, cfg)
	case FileTypeJSON:
		return (data, cfg)
	}
	return ("unsupported file type")
}

First define aFileTypeType, used to distinguish different configuration file formats, the log package we want to implement can support two formats: YAML and JSON.

Next, it's implementedLoadConfigFunction, used to load configuration, it receives three parameters: configuration file name, configuration structure, and file type.

Inside the function, first use(filename)Read the configuration file content, then deserialize it using different methods according to the file type, and finally map the configuration information tocfgin the structure.

Serialization

The configuration package also supports serialization operations, that is, write configuration information from memory to a file. Sometimes we want to save oneFor reference by project users, this function is very practical.

func DumpConfig(filename string, cfg interface{}, typ FileType) error {
	var (
		data []byte
		err  error
	)
	switch typ {
	case FileTypeYAML:
		data, err = (cfg)
	case FileTypeJSON:
		data, err = (cfg)
	}
	if err != nil {
		return err
	}
	f, err := (filename)
	if err != nil {
		return err
	}
	_, err = (data)
	_ = ()
	return err
}

We've achieved itDumpConfigThe function is used to perform serialization operations. The function also receives three parameters: configuration file name, configuration structure, and file type.

In the function, first configure the object according to the file typecfgPerform serialization operation to obtaindata, and then pass(filename)Create a configuration file and finally use it(data)Write the serialized content to the file.

Now the core functions of the configuration package have been implemented, and both functions are allowed to be exported. If other projects want to use them, they can actually be used directly. However, the current configuration package still has room for optimization in terms of ease of use.

Specify configuration files through environment variables/command line parameters

In the above implementation of serialization/deserialization functions, the configuration file name needs to be passed in through parameters. We can also specify the configuration file name through environment variables or command line parameters.

var (
	cfgPath = ("c", "", "path to config file")
	dump    = ("d", false, "dump config to file")
)

func init() {
	_ = ("c", ("CONFIG_PATH"))
	_ = ("d", ("DUMP_CONFIG"))
}

Command line parameter parsing can be implemented using Go's built-in flag package. We declare two flags here.-cUsed to pass the configuration file name,-dUsed to decide whether to perform serialization or deserialization operations.

In addition,initIn the function, we implement the loading of configuration file names and operations that need to be performed from environment variables. When the configuration package is imported, it will be executed firstinitfunction, so this information is read first from environment variables.

Package

Now we can get the configuration file name and the operation to be executed through environment variables or command line parameters, and assign the values ​​tocfgPathdumpFor two variables, we will encapsulate the serialization/deserialization function we implemented previously to make it more convenient to use.

Deserialization

First, we can deserialize the operation functionLoadConfigFurther encapsulate it, wrap it into two functions to load the YAML configuration and the JSON configuration respectively, so that the caller can pass one less parameter when using it. Implementation is as follows:

func LoadYAMLConfig(filename string, cfg interface{}) error {
	return LoadConfig(filename, cfg, FileTypeYAML)
}

func LoadJSONConfig(filename string, cfg interface{}) error {
	return LoadConfig(filename, cfg, FileTypeJSON)
}

Secondly, what we gotcfgPathParameters can also come in handy, it can be correctLoadYAMLConfigLoadJSONConfigFurther encapsulation:

func LoadYAMLConfigFromFlag(cfg interface{}) error {
	if !() {
		()
	}
	return LoadYAMLConfig(*cfgPath, cfg)
}

func LoadJSONConfigFromFlag(cfg interface{}) error {
	if !() {
		()
	}
	return LoadJSONConfig(*cfgPath, cfg)
}

becausecfgPathIt is passed in through environment variables or command line parameters, so there is no need to pass in manually when calling the deserialization function.

But in usecfgPathBefore the variable, we called()And judge the return value if()returnfalseThen call()

This step is because the caller of the configuration package may also use the flag package, so if the caller has already called it()The command line parameters have been parsed and the call()Will returntrue, there is no need to parse the command line parameters inside the configuration package, it can be used directly.

Serialization

Once the deserialization operation is encapsulated, the corresponding serialization operation must also be encapsulated. The code implementation is exactly the same as deserialization operation. I posted the code here and I will not explain it anymore.

func DumpYAMLConfig(filename string, cfg interface{}) error {
	return DumpConfig(filename, cfg, FileTypeYAML)
}

func DumpJSONConfig(filename string, cfg interface{}) error {
	return DumpConfig(filename, cfg, FileTypeJSON)
}

func DumpYAMLConfigFromFlag(cfg interface{}) error {
	if !() {
		()
	}
	return DumpYAMLConfig(*cfgPath, cfg)
}

func DumpJSONConfigFromFlag(cfg interface{}) error {
	if !() {
		()
	}
	return DumpJSONConfig(*cfgPath, cfg)
}

Unified export function

We get another user-specified variable through environment variables or command line parametersdumpNot used yet,dumpIt's oneboolValue, which means whether the user wants to serialize the configuration, iftrueThen perform serialization operation, otherwise perform deserialization operation.

Therefore, we can encapsulate the serialization/deserialization operation into a function, and internally passdumpThe value of the operation determines which operation to perform. Implementation is as follows:

func LoadOrDumpYAMLConfigFromFlag(cfg interface{}) error {
	if !() {
		()
	}
	if *dump {
		if err := DumpYAMLConfig(*cfgPath, cfg); err != nil {
			return err
		}
		(0)
	}
	return LoadYAMLConfig(*cfgPath, cfg)
}

func LoadOrDumpJSONConfigFromFlag(cfg interface{}) error {
	if *dump {
		if err := DumpJSONConfigFromFlag(cfg); err != nil {
			return err
		}
		(0)
	}
	return LoadJSONConfigFromFlag(cfg)
}

We implement the configuration of YAML and JSON formats separatelyLoadOrDumpYAMLConfigFromFlagandLoadOrDumpJSONConfigFromFlagfunction. As the name suggests, this function can be loaded (Load) configuration, and can also write configuration to (Dump)document.

The two functions have the same internal ideas, ifdumpfortrue, serialize the configuration information into the file and use(0)Exit the program. At this point, the console will not have any output, which follows the design philosophy of Linux/Unix system. Without messages is the best news;dumpforfalse, then deserialization operation is performed.

The above is to implement all the functions of the custom configuration package, and the complete code can beClick hereCheck.

Use the config package

I wrote a complete example of using the configuration package in the project GitHub repositoryIn the file, you can click in to view it.

Summarize

This article takes you to use Go language to implement a simple log package. Although there are not many functions, it is enough for small and medium-sized projects to use. If you have more requirements for loading configuration files, such as log file support in more formats, hot loading, etc., you can consider using a more powerful Go third-party library Viper, or using configuration centers such as Nacos and Disconf.

In this cloud-native era, in most cases, configuration packages do not need to implement very complex functions, and try to keep them simple. The configuration file can be used with K8sConfigMapTo implement it, the advanced features of the configuration package can also be considered throughSidecarto realize the form of