Once you've loaded it and mutated it for testing purposes or for copying from ENV vars into the config, you can then freeze it before passing it down to all your app level code.
Having this wrapper object that can be frozen and has a `get()` method to read JSON like data make it effectively not mutable.
BTW kudos on zanzibar. Love the tech and the code).
type Server struct { val bool }
type Config struct { Val bool }
func NewServer(... config *Config ...) http.Handler { if config == nil { config = &Config{} } return &Server{ val: config.Val } }
It took me a long time to settle on this pattern and I admit it's tedious to copy configuration over to the server struct, but I've found that it ends up being the least verbose and maintainable long term while making sure callers can't mutate config after the fact.
I can pass nil to NewServer to say "just the usual, please", customize everything, or surgically change a single option.
It's also useful for maintaining backwards compatibility. I'm free to refactor config on my server struct and "upgrade" deprecated config arguments inside my NewServer function.