如何设计 类 X 个需要在内存中单独读取的配置文件?
How to design classes for X number of config files which needs to be read individually in memory?
我正在处理大量配置文件。我需要在自己的 struct
中读取所有这些单独的配置文件,然后制作一个巨大的 Config
结构,其中包含所有其他单独的配置结构。
假设我正在使用 3 个配置文件。
ClientConfig
处理一个配置文件。
DataMapConfig
处理第二个配置文件。
ProcessDataConfig
处理第三个配置文件。
我为每个单独的配置文件创建了单独的 class,并且在其中有单独的 Readxxxxx
方法来读取他们自己的单独配置和 return 结构。下面是我的 config.go
文件,它在传递 path
和 logger
.
后通过 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 将 json 反序列化为与 json 格式匹配的原始结构后,我在处理该数据后创建了另一个不同的结构,然后我 return 返回结构。
- 案例2我所有的配置文件都是一个文件,但很少有配置文件里面有多个文件,而且数量不固定。所以我传递正则表达式文件名,然后我找到所有以该正则表达式开头的文件,然后一个一个地遍历所有这些文件。在反序列化每个 json 文件后,我开始填充另一个对象并继续填充它,直到所有文件都被反序列化,然后用这些对象创建一个新结构,然后 return 它。
以上场景示例:
示例案例 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, ¬ionMapConfig)
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)
我正在处理大量配置文件。我需要在自己的 struct
中读取所有这些单独的配置文件,然后制作一个巨大的 Config
结构,其中包含所有其他单独的配置结构。
假设我正在使用 3 个配置文件。
ClientConfig
处理一个配置文件。DataMapConfig
处理第二个配置文件。ProcessDataConfig
处理第三个配置文件。
我为每个单独的配置文件创建了单独的 class,并且在其中有单独的 Readxxxxx
方法来读取他们自己的单独配置和 return 结构。下面是我的 config.go
文件,它在传递 path
和 logger
.
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 将 json 反序列化为与 json 格式匹配的原始结构后,我在处理该数据后创建了另一个不同的结构,然后我 return 返回结构。
- 案例2我所有的配置文件都是一个文件,但很少有配置文件里面有多个文件,而且数量不固定。所以我传递正则表达式文件名,然后我找到所有以该正则表达式开头的文件,然后一个一个地遍历所有这些文件。在反序列化每个 json 文件后,我开始填充另一个对象并继续填充它,直到所有文件都被反序列化,然后用这些对象创建一个新结构,然后 return 它。
以上场景示例:
示例案例 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, ¬ionMapConfig)
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)