Use of config in the Core


#1

In the core we are using the package config in 2 different ways:

As a dependency injected in a package

Pro: We could mock it easily
Con: We need to pass it pretty much everywhere and that become a de-facto dependency

As a singleton

Pro: Ease of use
Con: Creates a string dependency


Both solutions are valid but we need to make sure we choose only one to not have any confusion in the code with sometime dependencies and sometime singleton.

Both solution needs some modifications in the code. If singleton, we need to have a real singleton the New function should not be exposed. If dependency we need to remove the Global function and do everything in the New method.

Would like to have your feedbacks on that :slight_smile:

This subject came from this discussion on github https://github.com/mesg-foundation/core/pull/737#discussion_r251319159


#2

There are more solutions to this.

  1. A global variable (we don’t need singleton because of it just simulation of a global variable)
package config

var Cfg *Config

type Config struct {
  Bar string
}

func Init() error {
 Cfg = &Config{Bar: "bar"}
 return nil
}

package main

import "config"
import "a"

func main() {
  config.Init()
  a.New().foo()
}

package a

import "config"

type A struct {
}

func New() *A {
  return &A{}
}

func (a *A) foo() {
  fmt.Println(config.Cfg.Bar)
}
  1. Pass config
package config

type Config struct {
  Bar string
}

func New() (*Config) {
 return &Config{Bar: "bar"}
}

package main

import "config"
import "a"

func main() {
  cfg := config.New()
  a.New(cfg).foo()
}

package a

import "config"

type A struct {
  cfg *config.Config
}

func New(cfg *config.Config) *A {
  return &A{cfg: cfg}
}

func (a *A) foo() {
  fmt.Println(a.cfg.Bar)
}
  1. Pass part of config
package config

type BarConfig struct {
  Bar string
}

type Config struct {
  Bar BarConfig
}

func New() (*Config) {
 return &Config{Bar: BarConfig{"bar"}}
}

package main

import "config"
import "a"

func main() {
  cfg := config.New()
  a.New(cfg.BarConfig).foo()
}

package a

import "config"

type A struct {
  cfg *config.BarConfig
}

func New(cfg *config.BarConfig) *A {
  return &A{cfg: cfg}
}

func (a *A) foo() {
  fmt.Println(a.cfg.Bar)
}
  1. Each package create it’s own options to configure
package config

type BarConfig struct {
  Bar string
}

type Config struct {
  Bar BarConfig
}

func New() (*Config) {
 return &Config{Bar: BarConfig{"bar"}}
}

package main

import "config"
import "a"

func main() {
  cfg := config.New()
  a.New(&a.Config{Bar: cfg.BarConfig}).foo()
}

package a

import "config"

type Config struct {
  Bar string
}

type A struct {
  cfg *Config
}

func New(cfg *Config) *A {
  return &A{cfg: cfg}
}

func (a *A) foo() {
  fmt.Println(a.cfg.Bar)
}
  • 1 A global variable
  • 2 Pass config
  • 3 Pass part of config
  • 4 Each package create it’s own options to configure

0 voters


#3

I vote for singleton.

Having each package with different config will result in a mess. Some package needs config for other package because of the container architecture we have.


#4

Singleton is consider by many as antipattern. Also it leads to situation like in service package (described here https://github.com/mesg-foundation/core/pull/737#discussion_r251319159)