Skip to content

Commit

Permalink
Create type functions and filter options for dynamic config (#587)
Browse files Browse the repository at this point in the history
  • Loading branch information
madhuravi authored Mar 5, 2018
1 parent 4f0eb40 commit 3f14209
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 160 deletions.
97 changes: 97 additions & 0 deletions common/service/dynamicconfig/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package dynamicconfig

import (
"errors"
"time"

"github.com/sirupsen/logrus"
"github.com/uber-common/bark"
)

// Client allows fetching values from a dynamic configuration system NOTE: This does not have async
// options right now. In the interest of keeping it minimal, we can add when requirement arises.
type Client interface {
GetValue(name Key, defaultValue interface{}) (interface{}, error)
GetValueWithFilters(name Key, filters map[Filter]interface{}, defaultValue interface{}) (interface{}, error)

GetIntValue(name Key, filters map[Filter]interface{}, defaultValue int) (int, error)
GetFloatValue(name Key, filters map[Filter]interface{}, defaultValue float64) (float64, error)
GetBoolValue(name Key, filters map[Filter]interface{}, defaultValue bool) (bool, error)
GetStringValue(name Key, filters map[Filter]interface{}, defaultValue string) (string, error)
GetMapValue(
name Key, filters map[Filter]interface{}, defaultValue map[string]interface{},
) (map[string]interface{}, error)
GetDurationValue(
name Key, filters map[Filter]interface{}, defaultValue time.Duration,
) (time.Duration, error)
}

type nopClient struct{}

func (mc *nopClient) GetValue(name Key, defaultValue interface{}) (interface{}, error) {
return nil, errors.New("unable to find key")
}

func (mc *nopClient) GetValueWithFilters(
name Key, filters map[Filter]interface{}, defaultValue interface{},
) (interface{}, error) {
return nil, errors.New("unable to find key")
}

func (mc *nopClient) GetIntValue(name Key, filters map[Filter]interface{}, defaultValue int) (int, error) {
return defaultValue, errors.New("unable to find key")
}

func (mc *nopClient) GetFloatValue(name Key, filters map[Filter]interface{}, defaultValue float64) (float64, error) {
return defaultValue, errors.New("unable to find key")
}

func (mc *nopClient) GetBoolValue(name Key, filters map[Filter]interface{}, defaultValue bool) (bool, error) {
return defaultValue, errors.New("unable to find key")
}

func (mc *nopClient) GetStringValue(name Key, filters map[Filter]interface{}, defaultValue string) (string, error) {
return defaultValue, errors.New("unable to find key")
}

func (mc *nopClient) GetMapValue(
name Key, filters map[Filter]interface{}, defaultValue map[string]interface{},
) (map[string]interface{}, error) {
return defaultValue, errors.New("unable to find key")
}

func (mc *nopClient) GetDurationValue(
name Key, filters map[Filter]interface{}, defaultValue time.Duration,
) (time.Duration, error) {
return defaultValue, errors.New("unable to find key")
}

// NewNopClient creates a nop client
func NewNopClient() Client {
return &nopClient{}
}

// NewNopCollection creates a new nop collection
func NewNopCollection() *Collection {
return NewCollection(&nopClient{}, bark.NewLoggerFromLogrus(logrus.New()))
}
128 changes: 60 additions & 68 deletions common/service/dynamicconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,111 +21,103 @@
package dynamicconfig

import (
"errors"
"time"
)

// Client allows fetching values from a dynamic configuration system NOTE: This does not have async
// options right now. In the interest of keeping it minimal, we can add when requirement arises.
type Client interface {
GetValue(name Key) (interface{}, error)
GetValueWithFilters(name Key, filters map[Filter]interface{}) (interface{}, error)
}

type nopClient struct{}

func (mc *nopClient) GetValue(name Key) (interface{}, error) {
return nil, errors.New("unable to find key")
}

func (mc *nopClient) GetValueWithFilters(
name Key, filters map[Filter]interface{},
) (interface{}, error) {
return nil, errors.New("unable to find key")
}

// NewNopCollection creates a new nop collection
func NewNopCollection() *Collection {
return NewCollection(&nopClient{})
}
"github.com/uber-common/bark"
)

// NewCollection creates a new collection
func NewCollection(client Client) *Collection {
return &Collection{client}
func NewCollection(client Client, logger bark.Logger) *Collection {
return &Collection{client, logger}
}

// Collection wraps dynamic config client with a closure so that across the code, the config values
// can be directly accessed by calling the function without propagating the client everywhere in
// code
type Collection struct {
client Client
logger bark.Logger
}

// GetIntPropertyWithTaskList gets property with taskList filter and asserts that it's an integer
func (c *Collection) GetIntPropertyWithTaskList(key Key, defaultVal int) func(string) int {
return func(taskList string) int {
return c.getPropertyWithStringFilter(key, defaultVal, TaskListName)(taskList).(int)
}
func (c *Collection) logNoValue(key Key, err error) {
c.logger.Debugf("Failed to fetch key: %s from dynamic config with err: %s", key.String(), err.Error())
}

// GetDurationPropertyWithTaskList gets property with taskList filter and asserts that it's time.Duration
func (c *Collection) GetDurationPropertyWithTaskList(
key Key, defaultVal time.Duration,
) func(string) time.Duration {
return func(taskList string) time.Duration {
return c.getPropertyWithStringFilter(key, defaultVal, TaskListName)(taskList).(time.Duration)
}
}
// PropertyFn is a wrapper to get property from dynamic config
type PropertyFn func() interface{}

// IntPropertyFn is a wrapper to get int property from dynamic config
type IntPropertyFn func(opts ...FilterOption) int

// FloatPropertyFn is a wrapper to get float property from dynamic config
type FloatPropertyFn func(opts ...FilterOption) float64

// DurationPropertyFn is a wrapper to get duration property from dynamic config
type DurationPropertyFn func(opts ...FilterOption) time.Duration

// BoolPropertyFn is a wrapper to get bool property from dynamic config
type BoolPropertyFn func(opts ...FilterOption) bool

func (c *Collection) getPropertyWithStringFilter(
key Key, defaultVal interface{}, filter Filter,
) func(string) interface{} {
return func(filterVal string) interface{} {
filters := make(map[Filter]interface{})
filters[filter] = filterVal
val, err := c.client.GetValueWithFilters(key, filters)
// GetProperty gets a eface property and returns defaultValue if property is not found
func (c *Collection) GetProperty(key Key, defaultValue interface{}) PropertyFn {
return func() interface{} {
val, err := c.client.GetValue(key, defaultValue)
if err != nil {
return defaultVal
c.logNoValue(key, err)
}
return val
}
}

// GetProperty gets a eface property and returns defaultVal if property is not found
func (c *Collection) GetProperty(key Key, defaultVal interface{}) func() interface{} {
return func() interface{} {
val, err := c.client.GetValue(key)
if err != nil {
return defaultVal
}
return val
func getFilterMap(opts ...FilterOption) map[Filter]interface{} {
l := len(opts)
m := make(map[Filter]interface{}, l)
for _, opt := range opts {
opt(m)
}
return m
}

// GetIntProperty gets property and asserts that it's an integer
func (c *Collection) GetIntProperty(key Key, defaultVal int) func() int {
return func() int {
return c.GetProperty(key, defaultVal)().(int)
func (c *Collection) GetIntProperty(key Key, defaultValue int) IntPropertyFn {
return func(opts ...FilterOption) int {
val, err := c.client.GetIntValue(key, getFilterMap(opts...), defaultValue)
if err != nil {
c.logNoValue(key, err)
}
return val
}
}

// GetFloat64Property gets property and asserts that it's a float64
func (c *Collection) GetFloat64Property(key Key, defaultVal float64) func() float64 {
return func() float64 {
return c.GetProperty(key, defaultVal)().(float64)
func (c *Collection) GetFloat64Property(key Key, defaultValue float64) FloatPropertyFn {
return func(opts ...FilterOption) float64 {
val, err := c.client.GetFloatValue(key, getFilterMap(opts...), defaultValue)
if err != nil {
c.logNoValue(key, err)
}
return val
}
}

// GetDurationProperty gets property and asserts that it's a duration
func (c *Collection) GetDurationProperty(key Key, defaultVal time.Duration) func() time.Duration {
return func() time.Duration {
return c.GetProperty(key, defaultVal)().(time.Duration)
func (c *Collection) GetDurationProperty(key Key, defaultValue time.Duration) DurationPropertyFn {
return func(opts ...FilterOption) time.Duration {
val, err := c.client.GetDurationValue(key, getFilterMap(opts...), defaultValue)
if err != nil {
c.logNoValue(key, err)
}
return val
}
}

// GetBoolProperty gets property and asserts that it's an bool
func (c *Collection) GetBoolProperty(key Key, defaultVal bool) func() bool {
return func() bool {
return c.GetProperty(key, defaultVal)().(bool)
func (c *Collection) GetBoolProperty(key Key, defaultValue bool) BoolPropertyFn {
return func(opts ...FilterOption) bool {
val, err := c.client.GetBoolValue(key, getFilterMap(opts...), defaultValue)
if err != nil {
c.logNoValue(key, err)
}
return val
}
}
6 changes: 4 additions & 2 deletions common/service/dynamicconfig/config_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ package dynamicconfig
import (
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/uber-common/bark"
)

func BenchmarkGetIntProperty(b *testing.B) {
client := newInMemoryClient()
cln := NewCollection(client)
key := MaxTaskBatchSize
cln := NewCollection(client, bark.NewLoggerFromLogrus(logrus.New()))
key := MatchingMaxTaskBatchSize
for i := 0; i < b.N; i++ {
size := cln.GetIntProperty(key, 10)
assert.Equal(b, 10, size())
Expand Down
Loading

0 comments on commit 3f14209

Please sign in to comment.