github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/docs/v2/design/framework/config.md (about) 1 # Overview 2 3 Most config in applications are statically configured or include complex logic to load from multiple sources. 4 Go-config makes this easy, pluggable and mergeable. You'll never have to deal with config in the same way again. 5 6 ## Features 7 8 - **Dynamic Loading** - Load configuration from multiple source as and when needed. Go Config manages watching config sources 9 in the background and automatically merges and updates an in memory view. 10 11 - **Pluggable Sources** - Choose from any number of sources to load and merge config. The backend source is abstracted away into 12 a standard format consumed internally and decoded via encoders. Sources can be env vars, flags, file, etcd, k8s configmap, etc. 13 14 - **Mergeable Config** - If you specify multiple sources of config, regardless of format, they will be merged and presented in 15 a single view. This massively simplifies priority order loading and changes based on environment. 16 17 - **Observe Changes** - Optionally watch the config for changes to specific values. Hot reload your app using Go Config's watcher. 18 You don't have to handle ad-hoc hup reloading or whatever else, just keep reading the config and watch for changes if you need 19 to be notified. 20 21 - **Safe Recovery** - In case config loads badly or is completely wiped away for some unknown reason, you can specify fallback 22 values when accessing any config values directly. This ensures you'll always be reading something default in the event of a problem. 23 24 ## Getting Started 25 26 - [Source](#source) - A backend from which config is loaded 27 - [Encoder](#encoder) - Handles encoding/decoding source config 28 - [Reader](#reader) - Merges multiple encoded sources as a single format 29 - [Config](#config) - Config manager which manages multiple sources 30 - [Secrets](#secrets) - Support for loading encoding secrets in config 31 - [Usage](#usage) - Example usage of go-config 32 - [FAQ](#faq) - General questions and answers 33 - [TODO](#todo) - TODO tasks/features 34 35 ## Sources 36 37 A `Source` is a backend from which config is loaded. Multiple sources can be used at the same time. 38 39 The following sources are supported: 40 41 - [cli](https://github.com/micro/go-micro/tree/master/config/source/cli) - read from parsed CLI flags 42 - [consul](https://github.com/micro/go-micro/tree/master/config/source/consul) - read from consul 43 - [env](https://github.com/micro/go-micro/tree/master/config/source/env) - read from environment variables 44 - [etcd](https://github.com/micro/go-micro/tree/master/config/source/etcd) - read from etcd v3 45 - [file](https://github.com/micro/go-micro/tree/master/config/source/file) - read from file 46 - [flag](https://github.com/micro/go-micro/tree/master/config/source/flag) - read from flags 47 - [memory](https://github.com/micro/go-micro/tree/master/config/source/memory) - read from memory 48 49 There are also community-supported plugins, which support the following sources: 50 51 - [configmap](https://github.com/micro/go-plugins/tree/master/config/source/configmap) - read from k8s configmap 52 - [grpc](https://github.com/micro/go-plugins/tree/master/config/source/grpc) - read from grpc server 53 - [runtimevar](https://github.com/micro/go-plugins/tree/master/config/source/runtimevar) - read from Go Cloud Development Kit runtime variable 54 - [url](https://github.com/micro/go-plugins/tree/master/config/source/url) - read from URL 55 - [vault](https://github.com/micro/go-plugins/tree/master/config/source/vault) - read from Vault server 56 57 TODO: 58 59 - git url 60 61 ### ChangeSet 62 63 Sources return config as a ChangeSet. This is a single internal abstraction for multiple backends. 64 65 ```go 66 type ChangeSet struct { 67 // Raw encoded config data 68 Data []byte 69 // MD5 checksum of the data 70 Checksum string 71 // Encoding format e.g json, yaml, toml, xml 72 Format string 73 // Source of the config e.g file, consul, etcd 74 Source string 75 // Time of loading or update 76 Timestamp time.Time 77 } 78 ``` 79 80 ## Encoder 81 82 An `Encoder` handles source config encoding/decoding. Backend sources may store config in many different 83 formats. Encoders give us the ability to handle any format. If an Encoder is not specified it defaults to json. 84 85 The following encoding formats are supported: 86 87 - json 88 - yaml 89 - toml 90 - xml 91 - hcl 92 93 ## Reader 94 95 A `Reader` represents multiple changesets as a single merged and queryable set of values. 96 97 ```go 98 type Reader interface { 99 // Merge multiple changeset into a single format 100 Merge(...*source.ChangeSet) (*source.ChangeSet, error) 101 // Return return Go assertable values 102 Values(*source.ChangeSet) (Values, error) 103 // Name of the reader e.g a json reader 104 String() string 105 } 106 ``` 107 108 The reader makes use of Encoders to decode changesets into `map[string]interface{}` then merge them into 109 a single changeset. It looks at the Format field to determine the Encoder. The changeset is then represented 110 as a set of `Values` with the ability to retrive Go types and fallback where values cannot be loaded. 111 112 ```go 113 114 // Values is returned by the reader 115 type Values interface { 116 // Return raw data 117 Bytes() []byte 118 // Retrieve a value 119 Get(path ...string) Value 120 // Return values as a map 121 Map() map[string]interface{} 122 // Scan config into a Go type 123 Scan(v interface{}) error 124 } 125 ``` 126 127 The `Value` interface allows casting/type asserting to go types with fallback defaults. 128 129 ```go 130 type Value interface { 131 Bool(def bool) bool 132 Int(def int) int 133 String(def string) string 134 Float64(def float64) float64 135 Duration(def time.Duration) time.Duration 136 StringSlice(def []string) []string 137 StringMap(def map[string]string) map[string]string 138 Scan(val interface{}) error 139 Bytes() []byte 140 } 141 ``` 142 143 ## Config 144 145 `Config` manages all config, abstracting away sources, encoders and the reader. 146 147 It manages reading, syncing, watching from multiple backend sources and represents them as a single merged and queryable source. 148 149 ```go 150 151 // Config is an interface abstraction for dynamic configuration 152 type Config interface { 153 // provide the reader.Values interface 154 reader.Values 155 // Stop the config loader/watcher 156 Close() error 157 // Load config sources 158 Load(source ...source.Source) error 159 // Force a source changeset sync 160 Sync() error 161 // Watch a value for changes 162 Watch(path ...string) (Watcher, error) 163 } 164 ``` 165 166 ## Secrets 167 168 Config should have the support for loading secrets. This is encoded data that requires a key to decrypt. 169 Implementations exist like Vault and Kubernetes secrets but we need something native to Micro we can 170 personally use. 171 172 Ideally we specify the path to load secrets from in config and the secrets interface provides this to us 173 174 ``` 175 type Secrets interface { 176 Load(path ...string) (reader.Values, error) 177 } 178 179 180 secrets.Load("auth", "token").String("") 181 ``` 182 183 In an ideal world we automatically decode secrets when they are found but this may be a future thing. 184 185 Further to this design proposal we assume the config service would include a Secrets rpc endpoint 186 that would enable loading of secrets as and when needed. The config service would be started 187 with a public/private keypair for encoding/decoding secrets. 188 189 ## Usage 190 191 - [Sample Config](#sample-config) 192 - [New Config](#new-config) 193 - [Load File](#load-file) 194 - [Read Config](#read-config) 195 - [Read Values](#read-values) 196 - [Watch Path](#watch-path) 197 - [Multiple Sources](#merge-sources) 198 - [Set Source Encoder](#set-source-encoder) 199 - [Add Reader Encoder](#add-reader-encoder) 200 201 202 203 ### Sample Config 204 205 A config file can be of any format as long as we have an Encoder to support it. 206 207 Example json config: 208 209 ```json 210 { 211 "hosts": { 212 "database": { 213 "address": "10.0.0.1", 214 "port": 3306 215 }, 216 "cache": { 217 "address": "10.0.0.2", 218 "port": 6379 219 } 220 } 221 } 222 ``` 223 224 ### New Config 225 226 Create a new config (or just make use of the default instance) 227 228 ```go 229 import "github.com/micro/go-micro/v2/config" 230 231 conf := config.NewConfig() 232 ``` 233 234 ### Load File 235 236 Load config from a file source. It uses the file extension to determine config format. 237 238 ```go 239 import ( 240 "github.com/micro/go-micro/v2/config" 241 ) 242 243 // Load json config file 244 config.LoadFile("/tmp/config.json") 245 ``` 246 247 Load a yaml, toml or xml file by specifying a file with the appropriate file extension 248 249 ```go 250 // Load yaml config file 251 config.LoadFile("/tmp/config.yaml") 252 ``` 253 254 If an extension does not exist, specify the encoder 255 256 ```go 257 import ( 258 "github.com/micro/go-micro/v2/config" 259 "github.com/micro/go-micro/v2/config/source/file" 260 ) 261 262 enc := toml.NewEncoder() 263 264 // Load toml file with encoder 265 config.Load(file.NewSource( 266 file.WithPath("/tmp/config"), 267 source.WithEncoder(enc), 268 )) 269 ``` 270 271 ### Read Config 272 273 Read the entire config as a map 274 275 ```go 276 // retrieve map[string]interface{} 277 conf := config.Map() 278 279 // map[cache:map[address:10.0.0.2 port:6379] database:map[address:10.0.0.1 port:3306]] 280 fmt.Println(conf["hosts"]) 281 ``` 282 283 Scan the config into a struct 284 285 ```go 286 type Host struct { 287 Address string `json:"address"` 288 Port int `json:"port"` 289 } 290 291 type Config struct{ 292 Hosts map[string]Host `json:"hosts"` 293 } 294 295 var conf Config 296 297 config.Scan(&conf) 298 299 // 10.0.0.1 3306 300 fmt.Println(conf.Hosts["database"].Address, conf.Hosts["database"].Port) 301 ``` 302 303 ### Read Values 304 305 Scan a value from the config into a struct 306 307 ```go 308 type Host struct { 309 Address string `json:"address"` 310 Port int `json:"port"` 311 } 312 313 var host Host 314 315 config.Get("hosts", "database").Scan(&host) 316 317 // 10.0.0.1 3306 318 fmt.Println(host.Address, host.Port) 319 ``` 320 321 Read individual values as Go types 322 323 ```go 324 // Get address. Set default to localhost as fallback 325 address := config.Get("hosts", "database", "address").String("localhost") 326 327 // Get port. Set default to 3000 as fallback 328 port := config.Get("hosts", "database", "port").Int(3000) 329 ``` 330 331 ### Watch Path 332 333 Watch a path for changes. When the file changes the new value will be made available. 334 335 ```go 336 w, err := config.Watch("hosts", "database") 337 if err != nil { 338 // do something 339 } 340 341 // wait for next value 342 v, err := w.Next() 343 if err != nil { 344 // do something 345 } 346 347 var host Host 348 349 v.Scan(&host) 350 ``` 351 352 ### Multiple Sources 353 354 Multiple sources can be loaded and merged. Merging priority is in reverse order. 355 356 ```go 357 config.Load( 358 // base config from env 359 env.NewSource(), 360 // override env with flags 361 flag.NewSource(), 362 // override flags with file 363 file.NewSource( 364 file.WithPath("/tmp/config.json"), 365 ), 366 ) 367 ``` 368 369 ### Set Source Encoder 370 371 A source requires an encoder to encode/decode data and specify the changeset format. 372 373 The default encoder is json. To change the encoder to yaml, xml, toml specify as an option. 374 375 ```go 376 e := yaml.NewEncoder() 377 378 s := consul.NewSource( 379 source.WithEncoder(e), 380 ) 381 ``` 382 383 ### Add Reader Encoder 384 385 The reader uses encoders to decode data from sources with different formats. 386 387 The default reader supports json, yaml, xml, toml and hcl. It represents the merged config as json. 388 389 Add a new encoder by specifying it as an option. 390 391 ```go 392 e := yaml.NewEncoder() 393 394 r := json.NewReader( 395 reader.WithEncoder(e), 396 ) 397 ``` 398 399 ## FAQ 400 401 ### How is this different from Viper? 402 403 [Viper](https://github.com/spf13/viper) and go-config are solving the same problem. Go-config provides a different interface 404 and is part of the larger micro ecosystem of tooling. 405 406 ### What's the difference between Encoder and Reader? 407 408 The encoder is used by a backend source to encode/decode it's data. The reader uses encoders to decode data from multiple 409 sources with different formats, it then merges them into a single encoding format. 410 411 In the case of a file source , we use the file extension to determine the config format so the encoder is not used. 412 413 In the case of consul, etcd or similar key-value source we may load from a prefix containing multiple keys which means 414 the source needs to understand the encoding so it can return a single changeset. 415 416 In the case of environment variables and flags we also need a way to encode the values as bytes and specify the format so 417 it can later be merged by the reader. 418 419 ### Why is changeset data not represented as map[string]interface{}? 420 421 In some cases source data may not actually be key-value so it's easier to represent it as bytes and defer decoding to 422 the reader. 423