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

config and log rearchitect #177

Merged
merged 6 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 27 additions & 23 deletions c.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,22 @@ type ConfProvider interface {
}

// ConfigProvider provides contract.ConfigAccessor to the core.
type ConfigProvider func(configStack []config.ProviderSet, configWatcher contract.ConfigWatcher) contract.ConfigAccessor
type ConfigProvider func(configStack []config.ProviderSet, configWatcher contract.ConfigWatcher) contract.ConfigUnmarshaler

// EventDispatcherProvider provides contract.Dispatcher to the core.
type EventDispatcherProvider func(conf contract.ConfigAccessor) contract.Dispatcher
type EventDispatcherProvider func(conf contract.ConfigUnmarshaler) contract.Dispatcher

// DiProvider provides the DiContainer to the core.
type DiProvider func(conf contract.ConfigAccessor) DiContainer
type DiProvider func(conf contract.ConfigUnmarshaler) DiContainer

// AppNameProvider provides the contract.AppName to the core.
type AppNameProvider func(conf contract.ConfigAccessor) contract.AppName
type AppNameProvider func(conf contract.ConfigUnmarshaler) contract.AppName

// EnvProvider provides the contract.Env to the core.
type EnvProvider func(conf contract.ConfigAccessor) contract.Env
type EnvProvider func(conf contract.ConfigUnmarshaler) contract.Env

// LoggerProvider provides the log.Logger to the core.
type LoggerProvider func(conf contract.ConfigAccessor, appName contract.AppName, env contract.Env) log.Logger
type LoggerProvider func(conf contract.ConfigUnmarshaler, appName contract.AppName, env contract.Env) log.Logger

type coreValues struct {
// Base Values
Expand Down Expand Up @@ -179,7 +179,7 @@ func New(opts ...CoreOption) *C {
var c = C{
AppName: appName,
Env: env,
ConfigAccessor: conf,
ConfigAccessor: config.WithAccessor(conf),
LevelLogger: logging.WithLevel(logger),
Container: &container.Container{},
Dispatcher: dispatcher,
Expand Down Expand Up @@ -317,26 +317,30 @@ func (c *C) ProvideEssentials() {
type coreDependencies struct {
di.Out

Env contract.Env
AppName contract.AppName
Container contract.Container
ConfigAccessor contract.ConfigAccessor
ConfigRouter contract.ConfigRouter
ConfigWatcher contract.ConfigWatcher
Logger log.Logger
Dispatcher contract.Dispatcher
DefaultConfigs []config.ExportedConfig `group:"config,flatten"`
Env contract.Env
AppName contract.AppName
Container contract.Container
ConfigUnmarshaler contract.ConfigUnmarshaler
ConfigAccessor contract.ConfigAccessor
ConfigRouter contract.ConfigRouter
ConfigWatcher contract.ConfigWatcher
Logger log.Logger
LevelLogger logging.LevelLogger
Dispatcher contract.Dispatcher
DefaultConfigs []config.ExportedConfig `group:"config,flatten"`
}

c.provide(func() coreDependencies {
coreDependencies := coreDependencies{
Env: c.Env,
AppName: c.AppName,
Container: c.Container,
ConfigAccessor: c.ConfigAccessor,
Logger: c.LevelLogger,
Dispatcher: c.Dispatcher,
DefaultConfigs: provideDefaultConfig(),
Env: c.Env,
AppName: c.AppName,
Container: c.Container,
ConfigUnmarshaler: c.ConfigAccessor,
ConfigAccessor: c.ConfigAccessor,
Logger: c.LevelLogger,
LevelLogger: c.LevelLogger,
Dispatcher: c.Dispatcher,
DefaultConfigs: provideDefaultConfig(),
}
if cc, ok := c.ConfigAccessor.(contract.ConfigRouter); ok {
coreDependencies.ConfigRouter = cc
Expand Down
4 changes: 2 additions & 2 deletions config/app_name_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

func TestNewAppNameFromConf(t *testing.T) {
t.Parallel()
app := NewAppNameFromConf(MapAdapter(map[string]interface{}{
app := NewAppNameFromConf(WithAccessor(MapAdapter(map[string]interface{}{
"name": "app",
}))
})))
assert.Equal(t, "app", app.String())
}
95 changes: 70 additions & 25 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"sync"
"time"

"github.com/DoNewsCode/core/contract"
"github.com/DoNewsCode/core/events"
Expand Down Expand Up @@ -211,33 +212,17 @@ func (k *KoanfAdapter) Float64(s string) float64 {
return k.K.Float64(s)
}

// MapAdapter implements ConfigAccessor and ConfigRouter.
// It is primarily used for testing
type MapAdapter map[string]interface{}

func (m MapAdapter) String(s string) string {
return m[s].(string)
}

func (m MapAdapter) Int(s string) int {
return m[s].(int)
}

func (m MapAdapter) Strings(s string) []string {
return m[s].([]string)
}

func (m MapAdapter) Bool(s string) bool {
return m[s].(bool)
}
// Duration returns the time.Duration value of a given key path or its zero value if the path does not exist or if the value is not a valid float64.
func (k *KoanfAdapter) Duration(s string) time.Duration {
k.rwlock.RLock()
defer k.rwlock.RUnlock()

func (m MapAdapter) Get(s string) interface{} {
return m[s]
return k.K.Duration(s)
}

func (m MapAdapter) Float64(s string) float64 {
return m[s].(float64)
}
// MapAdapter implements ConfigUnmarshaler and ConfigRouter.
// It is primarily used for testing
type MapAdapter map[string]interface{}

func (m MapAdapter) Unmarshal(path string, o interface{}) (err error) {
k := koanf.New(".")
Expand All @@ -258,7 +243,8 @@ func (m MapAdapter) Unmarshal(path string, o interface{}) (err error) {
})
}

func (m MapAdapter) Route(s string) contract.ConfigAccessor {
// Route implements contract.ConfigRouter
func (m MapAdapter) Route(s string) contract.ConfigUnmarshaler {
var v interface{}
v = m
if s != "" {
Expand Down Expand Up @@ -300,3 +286,62 @@ func stringToConfigDurationHookFunc() mapstructure.DecodeHookFunc {
return d, nil
}
}

type wrappedConfigAccessor struct {
unmarshaler contract.ConfigUnmarshaler
}

func (w wrappedConfigAccessor) Unmarshal(path string, o interface{}) error {
return w.unmarshaler.Unmarshal(path, o)
}

func (w wrappedConfigAccessor) String(s string) string {
var o string
w.unmarshaler.Unmarshal(s, &o)
return o
}

func (w wrappedConfigAccessor) Int(s string) int {
var o int
w.unmarshaler.Unmarshal(s, &o)
return o
}

func (w wrappedConfigAccessor) Strings(s string) []string {
var o []string
w.unmarshaler.Unmarshal(s, &o)
return o
}

func (w wrappedConfigAccessor) Bool(s string) bool {
var o bool
w.unmarshaler.Unmarshal(s, &o)
return o
}

func (w wrappedConfigAccessor) Get(s string) interface{} {
var o interface{}
w.unmarshaler.Unmarshal(s, &o)
return o
}

func (w wrappedConfigAccessor) Float64(s string) float64 {
var o float64
w.unmarshaler.Unmarshal(s, &o)
return o
}

func (w wrappedConfigAccessor) Duration(s string) time.Duration {
var dur Duration
w.unmarshaler.Unmarshal(s, &dur)
return dur.Duration
}

// WithAccessor upgrade contract.ConfigUnmarshaler to contract.ConfigAccessor by
// wrapping the original unmarshaler.
func WithAccessor(unmarshaler contract.ConfigUnmarshaler) contract.ConfigAccessor {
if accessor, ok := unmarshaler.(contract.ConfigAccessor); ok {
return accessor
}
return wrappedConfigAccessor{unmarshaler: unmarshaler}
}
59 changes: 19 additions & 40 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ func TestKoanfAdapter_Get(t *gotesting.T) {
assert.Equal(t, 1.0, k.Get("float"))
}

func TestKoanfAdapter_Duration(t *gotesting.T) {
t.Parallel()
k := prepareJSONTestSubject(t)
assert.Equal(t, time.Second, k.Duration("duration_string"))
}

func TestKoanfAdapter_Unmarshal_Json(t *gotesting.T) {
t.Parallel()
ka := prepareJSONTestSubject(t)
Expand Down Expand Up @@ -146,46 +152,6 @@ func TestKoanfAdapter_Unmarshal_Yaml(t *gotesting.T) {
assert.Equal(t, r, Duration{1 * time.Nanosecond})
}

func TestMapAdapter_Bool(t *gotesting.T) {
t.Parallel()
k := MapAdapter(
map[string]interface{}{
"bool": true,
},
)
assert.True(t, k.Bool("bool"))
}

func TestMapAdapter_String(t *gotesting.T) {
t.Parallel()
k := MapAdapter(
map[string]interface{}{
"string": "string",
},
)
assert.Equal(t, "string", k.String("string"))
}

func TestMapAdapter_Float64(t *gotesting.T) {
t.Parallel()
k := MapAdapter(
map[string]interface{}{
"float": 1.0,
},
)
assert.Equal(t, 1.0, k.Float64("float"))
}

func TestMapAdapter_Get(t *gotesting.T) {
t.Parallel()
k := MapAdapter(
map[string]interface{}{
"float": 1.0,
},
)
assert.Equal(t, 1.0, k.Get("float"))
}

func TestMapAdapter_Route(t *gotesting.T) {
t.Parallel()
m := MapAdapter(
Expand Down Expand Up @@ -231,6 +197,19 @@ func TestKoanfAdapter_Reload(t *gotesting.T) {
assert.Nil(t, conf)
}

func TestUpgrade(t *gotesting.T) {
var m MapAdapter = map[string]interface{}{"foo": "bar"}
upgraded := WithAccessor(m)

assert.Equal(t, float64(0), upgraded.Float64("foo"))
assert.Equal(t, 0, upgraded.Int("foo"))
assert.Equal(t, "bar", upgraded.String("foo"))
assert.Equal(t, false, upgraded.Bool("foo"))
assert.Equal(t, "bar", upgraded.Get("foo"))
assert.Equal(t, []string{"bar"}, upgraded.Strings("foo"))
assert.Equal(t, time.Duration(0), upgraded.Duration("foo"))
}

func prepareJSONTestSubject(t *gotesting.T) *KoanfAdapter {
k := koanf.New(".")
if err := k.Load(file.Provider("testdata/mock.json"), json.Parser()); err != nil {
Expand Down
5 changes: 3 additions & 2 deletions config/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ func NewEnv(env string) Env {
}

// NewEnvFromConf reads the name of application from configuration's "env" entry.
func NewEnvFromConf(conf contract.ConfigAccessor) Env {
envStr := conf.String("env")
func NewEnvFromConf(conf contract.ConfigUnmarshaler) Env {
var envStr string
conf.Unmarshal("env", &envStr)
return NewEnv(envStr)
}
5 changes: 0 additions & 5 deletions contract/codec.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package contract

// Marshaller is an interface for the data type that knows how to marshal itself.
type Marshaller interface {
Marshal() ([]byte, error)
}

// Codec is an interface for serialization and deserialization.
type Codec interface {
Unmarshal(data []byte, value interface{}) error
Expand Down
25 changes: 20 additions & 5 deletions contract/config.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
package contract

import "context"
import (
"context"
"time"
)

// ConfigRouter enables modular configuration by giving every piece of configuration a path.
type ConfigRouter interface {
Route(path string) ConfigAccessor
Route(path string) ConfigUnmarshaler
}

// ConfigAccessor models a the basic configuration. If the configuration is hot reloaded,
// ConfigAccessor should fetch the latest info.
// ConfigUnmarshaler is a minimum config interface that can be used to retrieve
// configuration from external system. If the configuration is hot reloaded,
// ConfigUnmarshaler should fetch the latest info.
type ConfigUnmarshaler interface {
Unmarshal(path string, o interface{}) error
}

// ConfigAccessor builds upon the ConfigUnmarshaler and provides a richer set of
// API.
// Note: it is recommended to inject ConfigUnmarshaler as the dependency
// and call config.Upgrade to get the ConfigAccessor. The interface area of
// ConfigUnmarshaler is much smaller and thus much easier to customize.
type ConfigAccessor interface {
ConfigUnmarshaler

String(string) string
Int(string) int
Strings(string) []string
Bool(string) bool
Get(string) interface{}
Float64(string) float64
Unmarshal(path string, o interface{}) error
Duration(string) time.Duration
}

// ConfigWatcher is an interface for hot-reload provider.
Expand Down
23 changes: 23 additions & 0 deletions contract/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package contract

import "github.com/go-kit/kit/log"

// Logger is an alias of go kit logger
type Logger = log.Logger

// LevelLogger is plaintext logger with level.
type LevelLogger interface {
Logger
Debug(args ...interface{})
Info(args ...interface{})
Warn(args ...interface{})
Err(args ...interface{})
Debugf(template string, args ...interface{})
Infof(template string, args ...interface{})
Warnf(template string, args ...interface{})
Errf(template string, args ...interface{})
Debugw(msg string, fields ...interface{})
Infow(msg string, fields ...interface{})
Warnw(msg string, fields ...interface{})
Errw(msg string, fields ...interface{})
}
Loading