1
\$\begingroup\$

I am working with lot of config files. I need to read all those individual config file in their own struct and then make one giant Config struct which holds all other individual config struct in it.

Let's suppose if I am working with 3 config files.

  • ClientConfig deals with one config file.
  • DataMapConfig deals with second config file.
  • ProcessDataConfig deals with third config file.

I created separate class for each of those individual config file and have separate Readxxxxx method in them to read their own individual config and return struct back. Below is my config.go file which is called via Init method from main function after passing path and logger.

config.go

package config

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "github.com/david/internal/utilities"
)

type Config struct {
    ClientMapConfigs   ClientConfig
    DataMapConfigs     DataMapConfig
    ProcessDataConfigs ProcessDataConfig
}

func Init(path string, logger log.Logger) (*Config, error) {
    var err error
    clientConfig, err := ReadClientMapConfig(path, logger)
    dataMapConfig, err := ReadDataMapConfig(path, logger)
    processDataConfig, err := ReadProcessDataConfig(path, logger)
    if err != nil {
        return nil, err
    }
    return &Config{
        ClientMapConfigs:       *clientConfig,
        DataMapConfigs:         *dataMapConfig,
        ProcessDataConfigs:     *processDataConfig,
    }, nil
}

clientconfig.go

package config

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "github.com/david/internal/utilities"
)

type ClientConfig struct {
  .....
  .....
}

const (
    ClientConfigFile = "clientConfigMap.json"
)

func ReadClientMapConfig(path string, logger log.Logger) (*ClientConfig, error) {
  files, err := utilities.FindFiles(path, ClientConfigFile)
  // read all the files
  // do some validation on all those files
  // deserialize them into ClientConfig struct
  // return clientconfig object back
}

datamapconfig.go

Similar style I have for datamapconfig too. Exactly replica of clientconfig.go file but operating on different config file name and will return DataMapConfig struct back.

processdataConfig.go

Same thing as clientconfig.go file. Only difference is it will operate on different config file and return ProcessDataConfig struct back.

Problem Statement

I am looking for ideas where this above design can be improved? Is there any better way to do this in golang? Can we use interface or anything else which can improve the above design?

If I have let's say 10 different files instead of 3, then do I need to keep doing above same thing for remaining 7 files? If yes, then the code will look ugly. Any suggestions or ideas will greatly help me.

Note: I can do validations after unmarshaling.

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

If I understand your problem statement correctly, you have a number of JSON files and you want to do lookup/parsing on each file.

Keep in mind that Go is not an "object-oriented" language, and does not have classes. It is a slight conceptual shift to think about structs as data, rather than a way to organize code.

I would write a helper function that takes as arguments the file name and a struct to serialize into. A sketch of the implementation is given below.

// ReadConfig reads the specified file and then parses the
// file as JSON into the given config structure.
func ReadConfig(filePath string, config any) error {
    files, err := utilities.FindFiles(filePath)
    if err != nil {
        return err
    }
    if len(files) == 0 {
        return errors.New("no files were found")
    }
    fileContent, err := os.ReadFile(files[0])
    if err != nil {
        return err
    }
    return json.Unmarshal(fileContent, config)
}

// Then you would call the helper function as follows

func Init(path string) (Config, error) {
    // Parse client map config.
    var clientConfig ClientConfig
    if err := ReadConfig(path+"/clientConfigMap.json", &clientConfig); err != nil {
        return Config{}, err
    }
    if err := ValidateClientMapConfig(clientConfig); err != nil {
        return Config{}, err
    }

    // Parse data map config.
    var dataMapConfig DataMapConfig
    if err := ReadConfig(path+"/dataConfigMap.json", &dataMapConfig); err != nil {
        return Config{}, err
    }
    if err := ValidateDataMapConfig(dataMapConfig); err != nil {
        return Config{}, err
    }

    // and so on for other config types
}

The key idea is that json.Unmarshal already takes a struct of any type, so we make our helper function also accept an any type, and just pass the value straight to json.Unmarshal.

I don't think interfaces are a good fit here, as each parsing function has a different type. Interfaces are useful when you have multiple methods with the same name and types, and you need to pick between them at runtime.

\$\endgroup\$
0
\$\begingroup\$

I think that you are on the right track when you think of using an interface. One way to look at this is to make ReadConfig an interface with methods ReadConfig(); etc...

\$\endgroup\$
1
  • 2
    \$\begingroup\$ yeah can you provide some examples on how can I make my current code better using interfaces and all? \$\endgroup\$
    – AndyP
    Commented Feb 8, 2022 at 1:07

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.