Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question]The concept of keys in Apollo config's Watch is somewhat confusing. #3510

Open
uucloud opened this issue Dec 30, 2024 · 1 comment
Labels
question Further information is requested

Comments

@uucloud
Copy link

uucloud commented Dec 30, 2024

使用apollo配置中心结合kratos时存在一个令人困惑的地方:

我的配置是这样的:
yaml的配置是这样的 bootstrap.yaml:

server:
  name: ms-app-1

json的配置是这样的 js.json:

{
    "server":{
        "name": "ms-app-1"
    }
}

当我不开启WithOriginalConfig时,yaml和json的Scan的方式有些不同,比如:

  1. yaml不需要写yaml后缀,而json需要用.content作为后缀才能读到相应配置
  2. json没有解析成多级key的形式,此时json的config.Watch只有一个js.content,无法像yaml格式一样使用Watch("server.name", func)的形式监听某一个key的变化。

而当我开启WithOriginalConfig后,json和yaml的Scan用法一样了,但是Watch没有最外层的key,也就是不可以config.Watch("bootstrap"),导致不能Watch整个文件的变化了

func TestConfig(t *testing.T) {
	type Server struct {
		Name string `json:"name"`
	}
	type Bootstrap struct {
		Server Server `json:"server"`
	}

	defaultOpts := func() []apollo.Option {
		return []apollo.Option{
			apollo.WithAppID("xx"),
			apollo.WithCluster("xx"),
			apollo.WithEndpoint("xx"),
			apollo.WithSecret("xx"),
		}
	}

	type testCase struct {
		name  string
		cfg   config.Config
		f     func(t *testing.T, cfg config.Config) Bootstrap
		watch func(t *testing.T, cfg config.Config)
	}

	cases := []testCase{
		{
			name: "yaml",
			cfg:  config.New(config.WithSource(apollo.NewSource(append(defaultOpts(), apollo.WithNamespace("bootstrap.yaml"))...))),
			f: func(t *testing.T, cfg config.Config) Bootstrap {
				var v Bootstrap
				//默认情况下,yaml的格式不需要指定类型,并且会被层层解析成多级key,比如可以使用server.name的方式获取
				if err := cfg.Value("bootstrap").Scan(&v); err != nil {
					t.Fatal(err)
				}
				return v
			},
			watch: func(t *testing.T, cfg config.Config) {
				if err := cfg.Watch("bootstrap", func(key string, val config.Value) {}); err != nil {
					t.Fatal(err)
				}
			},
		},
		{
			name: "json",
			cfg:  config.New(config.WithSource(apollo.NewSource(append(defaultOpts(), apollo.WithNamespace("js.json"))...))),
			f: func(t *testing.T, cfg config.Config) Bootstrap {
				var v Bootstrap
				var s string
				//但是json格式需要额外指定一个.content,并且取到的是json的字符串,需要手动处理,此时Watch的使用就不一致了,也不能js.content.server.name的方式获取
				if err := cfg.Value("js.content").Scan(&s); err != nil {
					t.Fatal(err)
				}
				if err := json.Unmarshal([]byte(s), &v); err != nil {
					t.Fatal(err)
				}
				return v
			},
			watch: func(t *testing.T, cfg config.Config) {
				if err := cfg.Watch("js.content", func(key string, val config.Value) {}); err != nil {
					t.Fatal(err)
				}
			},
		},
		{
			name: "yaml-origin",
			cfg:  config.New(config.WithSource(apollo.NewSource(append(defaultOpts(), apollo.WithNamespace("bootstrap.yaml"), apollo.WithOriginalConfig())...))),
			f: func(t *testing.T, cfg config.Config) Bootstrap {
				var v Bootstrap
				//使用origin方式,可以直接获取到原始的yaml配置,不需要指定key,并且json也是一样的Scan方式,此处符合预期
				if err := cfg.Scan(&v); err != nil {
					t.Fatal(err)
				}
				return v
			},
			watch: func(t *testing.T, cfg config.Config) {
				//但watch又有问题,会报错key not found,无法watch bootstrap,因为origin方式没有根key,只能watch某个子key,比如server.name
				if err := cfg.Watch("bootstrap", func(key string, val config.Value) {}); err != nil {
					t.Fatal(err)
				}
			},
		},
		{
			name: "json-origin",
			cfg:  config.New(config.WithSource(apollo.NewSource(append(defaultOpts(), apollo.WithNamespace("js.json"), apollo.WithOriginalConfig())...))),
			f: func(t *testing.T, cfg config.Config) Bootstrap {
				var v Bootstrap
				if err := cfg.Scan(&v); err != nil {
					t.Fatal(err)
				}
				return v
			},
			watch: func(t *testing.T, cfg config.Config) {
				//会报错key not found,无法watch js,因为origin方式没有根key
				if err := cfg.Watch("js", func(key string, val config.Value) {}); err != nil {
					t.Fatal(err)
				}
			},
		},
	}

	for _, tt := range cases {
		t.Run(tt.name, func(t *testing.T) {
			if err := tt.cfg.Load(); err != nil {
				t.Fatal(err)
			}
			v := tt.f(t, tt.cfg)
			assert.Equal(t, v.Server.Name, "ms-app-1")

			if tt.watch != nil {
				tt.watch(t, tt.cfg)
			}

		})
	}
}

是我的使用方式不对吗,我期望的是,json和yaml的Scan使用方式一致,Watch都可以Watch多级子key的同时,也可以Watch整个文件的变化

@uucloud uucloud added the question Further information is requested label Dec 30, 2024
Copy link

dosubot bot commented Dec 30, 2024

Hello, @uucloud! I'm here to help you with your Apollo configuration issue. Let's work together to solve this!

To achieve consistent behavior for both JSON and YAML configurations in Apollo when using Kratos, you can follow these steps:

  1. Without WithOriginalConfig:

    • For YAML, you can directly use multi-level keys like server.name for both Scan and Watch.
    • For JSON, you need to use the .content suffix to access the configuration, and it doesn't support multi-level keys directly. You can only watch the entire content using js.content.
  2. With WithOriginalConfig:

    • Both JSON and YAML configurations can be scanned directly without specifying keys, which makes the Scan usage consistent.
    • However, Watch becomes limited because there is no root key. You can only watch specific sub-keys like server.name, but not the entire file.

To achieve your goal of having consistent Scan and Watch usage for both JSON and YAML, you might need to implement a custom solution that handles the differences in how JSON and YAML are processed. This could involve:

  • Implementing a custom change listener that can handle both formats uniformly.
  • Modifying the way keys are generated and watched to support both entire file changes and specific key changes.

The customChangeListener in the watcher.go file can be a good starting point for handling changes and resolving keys consistently across different formats [1].

To continue talking to Dosu, mention @dosu.


Help Dosu learn! Give it feedback: Great Response | Irrelevant Answer | Incorrect Sources | Too Verbose | Hallucination | Other

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant