如何设计 类 X 个需要在内存中单独读取的配置文件?

How to design classes for X number of config files which needs to be read individually in memory?

我正在处理大量配置文件。我需要在自己的 struct 中读取所有这些单独的配置文件,然后制作一个巨大的 Config 结构,其中包含所有其他单独的配置结构。

假设我正在使用 3 个配置文件。

我为每个单独的配置文件创建了单独的 class,并且在其中有单独的 Readxxxxx 方法来读取他们自己的单独配置和 return 结构。下面是我的 config.go 文件,它在传递 pathlogger.

后通过 main 函数的 Init 方法调用

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
}

客户端config.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

我对 datamapconfig 也有类似的风格。 clientconfig.go 文件的精确副本,但在不同的配置文件名上运行,并将 return DataMapConfig 结构返回。

processdataConfig.go

clientconfig.go 文件相同。唯一的区别是它将对不同的配置文件和 return ProcessDataConfig 结构返回操作。

问题陈述

我正在寻找可以改进上述设计的想法?在 golang 中有更好的方法吗?我们可以使用界面或其他任何可以改进上述设计的东西吗?

如果我有 10 个不同的文件而不是 3 个,那么我是否需要对剩余的 7 个文件继续执行上述相同的操作?如果是,那么代码将很难看。任何建议或想法都会对我有很大帮助。

更新

一切看起来都不错,但我有几个问题,因为我对如何根据您当前的建议实现这些目标感到困惑。在我的大部分配置中,你的建议是完美的,但在两种不同的配置中有两种情况我不知道如何去做。

以上场景示例:

示例案例 1

package config

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

type CustomerManifest struct {
  CustomerManifest map[int64]Site
}

type CustomerConfigs struct {
  CustomerConfigurations []Site `json:"customerConfigurations"`
}

type Site struct {
  ....
  ....
}

const (
  CustomerConfigFile = "abc.json"
)

func ReadCustomerConfig(path string, logger log.Logger) (*CustomerManifest, error) {
  // I try to find all the files with my below utility method.
  // Work with single file name and also with regex name
  files, err := utilities.FindFiles(path, CustomerConfigFile)
  if err != nil {
    return nil, err
  }
  var customerConfig CustomerConfigs
  // there is only file for this config so loop will run once
  for _, file := range files {
    body, err := ioutil.ReadFile(file)
    if err != nil {
      return nil, err
    }

    err = json.Unmarshal(body, &customerConfig)
    if err != nil {
      return nil, err
    }
  }

  customerConfigIndex := BuildIndex(customerConfig, logger)
  return &CustomerManifest{CustomerManifest: customerConfigIndex}, nil
}

func BuildIndex(customerConfig CustomerConfigs, logger log.Logger) map[int64]Site {
  ...
  ...
}

正如您在上面的 示例案例 1 中看到的,我正在从 CustomerConfigs 结构创建 CustomerManifest 结构,然后 return 它改为直接 returning CustomerConfigs

示例案例 2

package config

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

type StateManifest struct {
    NotionTemplates       NotionTemplates
    NotionIndex           map[int64]NotionTemplates
}

type NotionMapConfigs struct {
    NotionTemplates      []NotionTemplates      `json:"notionTemplates"`
  ...
}

const (
  // there are many files starting with "state-", it's not fixed number
    StateConfigFile = "state-*.json"
)

func ReadStateConfig(path string, logger log.Logger) (*StateManifest, error) {
  // I try to find all the files with my below utility method.
  // Work with single file name and also with regex name
    files, err := utilities.FindFiles(path, StateConfigFile)
    if err != nil {
        return nil, err
    }
    var defaultTemp NotionTemplates
    var idx = map[int64]NotionTemplates{}

  // there are lot of config files for this config so loop will run multiple times
    for _, file := range files {
        var notionMapConfig NotionMapConfigs
        body, err := ioutil.ReadFile(file)
        if err != nil {
            return nil, err
        }
        err = json.Unmarshal(body, &notionMapConfig)
        if err != nil {
            return nil, err
        }

        for _, tt := range notionMapConfig.NotionTemplates {
            if tt.IsProcess {
                defaultTemp = tt
            } else if tt.ClientId > 0 {
                idx[tt.ClientId] = tt
            }
        }
    }

    stateManifest := StateManifest{
        NotionTemplates:       defaultTemp,
        NotionIndex:           idx,
    }
    return &stateManifest, nil
}

正如您在上面的两种情况下看到的那样,我在反序列化完成后制作了另一个不同的结构,然后我 return 返回该结构,但截至目前,根据您当前的建议,我认为我不会'一般无法做到这一点,因为对于每个配置,我都会进行不同类型的按摩,然后 return 将这些结构返回。根据您目前的建议,有什么方法可以实现上述功能吗?基本上对于每个配置,如果我想做一些按摩,那么我应该能够做到并且 return 新的修改后的结构,但在某些情况下,如果我不想做任何按摩,那么我可以 return 直接反序列化 json 结构返回。这可以通用吗?

因为配置中有多个文件,所以这就是为什么我使用我的 utilities.FindFiles 方法根据文件名或正则表达式名称给我所有文件,然后我遍历所有这些文件以return原始结构返回或return按摩原始结构数据后返回新结构。

您可以使用一个通用函数来加载所有的配置文件。

假设您有配置结构:

type Config1 struct {...}
type Config2 struct {...}
type Config3 struct {...}

您为需要的人定义配置验证器:

func (c Config1) Validate() error {...}
func (c Config2) Validate() error {...}

请注意,这些实现了一个 Validatable 接口:

type Validatable interface {
   Validate() error
}

有一种配置类型包含所有这些配置:

type Config struct {
   C1 Config1
   C2 Config2
   C3 Config3
   ...
}

然后,你可以定义一个简单的配置加载器函数:

func LoadConfig(fname string, out interface{}) error {
    data, err:=ioutil.ReadFile(fname)
    if err!=nil {
       return err
    }
    if err:=json.Unmarshal(data,out); err!=nil {
       return err
    }
    // Validate the config if necessary
    if validator, ok:=out.(Validatable); ok {
       if err:=validator.Validate(); err!=nil {
         return err
       }
    }
    return nil
}

然后,您可以加载文件:

var c Config
if err:=LoadConfig("file1",&c.C1); err!=nil {
   return err
}
if err:=LoadConfig("file2",&c.C2); err!=nil {
   return err
}
...

如果有多个文件加载同一个结构的不同部分,你可以这样做:

LoadConfig("file1",&c.C3)
LoadConfig("file2",&c.C3)
...

您可以通过定义一个切片来进一步简化它:

type cfgInfo struct {
   fileName string
   getCfg func(*Config) interface{}
}

var configs=[]cfgInfo {
   {
     fileName: "file1",
     getCfg: func(c *Config) interface{} {return &c.C1},
   },
   {
     fileName: "file2",
     getCfg: func(c *Config) interface{} {return &c.C2},
   },
   {
     fileName: "file3",
     getCfg: func(c *Config) interface{} {return &c.C3},
   },
   ...
}

func loadConfigs(cfg *Config) error {
   for _,f:=range configs {
     if err:=loadConfig(f.fileName,f.getCfg(cfg)); err!=nil {
         return err
     }
   }
  return nil
}

然后,loadConfigs 会将所有配置文件加载到 cfg

func main() {
   var cfg Config
   if err:=loadConfigs(&cfg); err!=nil {
      panic(err)
   }
   ...
}

任何与此模式不匹配的配置都可以使用 LoadConfig:

处理
var customConfig1 CustomConfigStruct1
if err:=LoadConfig("customConfigFile1",&customConfig1); err!=nil {
   panic(err)
}
cfg.CustomConfig1 = processCustomConfig1(customConfig1)

var customConfig2 CustomConfigStruct2
if err:=LoadConfig("customConfigFile2",&customConfig2); err!=nil {
   panic(err)
}
cfg.CustomConfig2 = processCustomConfig2(customConfig2)