dvow

package
v1.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2023 License: MIT Imports: 4 Imported by: 0

README

Dynamic Value Overwriting

Why context?

Dynamic Value Overwriting is usually an API-level requirement in which we need to dynamically replace the values of specific variables in one execution of our code with some values that our clients provide.

In essence, it means these overwritten values are bound to the request scope, making context a suitable storage for such values on paper.

Another good reason why we should use context is that dynamic value overwriting requires a cross-cutting implementation similar to OpenTracing spans. If you take a step back and think about it, each service is a combination of many code modules, each is responsible for one single task (e.g. calculate NSNSFare, calculate PTPFare, fetch coefficients, etc.). The variables we need to replace can be inside any of these code modules.

We can create a simple map of overwritten variables and pass it down every single code path to overwrite wherever necessary. However, this design requires us to add a map parameter to all functions/methods in our code path which is messy. In fact, this map might not be provided at all as most API calls don't require dynamic overwriting of values.

On the other hand, passing context down to every lower-level code module is a recommended coding practice so that each module can take into account timeout, deadline and handle cross-cutting concerns such as span tracing. Similar to how we only create a tracing span if necessary in the code path that we need to measure performance, by using context, we can choose to retrieve overwritten variables only in those places where we want clients to overwrite dynamically.

How to use

First, you need to receive a map of overwritten variables from clients via your gRPC or HTTP endpoints. This requires an update to the Protobuf definition of your APIs. In addition, you must define a list of overwritable variables including their fixed & unique names and expected data types so that clients know what to send into your APIs.

Subsequently, when you receive an API call, you should have a map[string]interface{} on hand to execute the following function.

// WithOverwrittenVariables returns a new context.Context that holds a reference to
// the given overwritten variables.
func WithOverwrittenVariables(ctx context.Context, overwrittenVariables map[string]interface{}) context.Context

After getting back a context from this function, you can pass it down to lower-level code, which is probably what you've already done in existing code.

To get an overwritten value out of this context, you can execute the following function.

// GetOverwrittenValue returns the Value of the variable under this name if it was overwritten
func GetOverwrittenValue(ctx context.Context, name string) Value

Another way is to extract the overwriting storage by calling the function below.

// ExtractOverwritingStorage returns the Storage currently associated with ctx, or
// nil if no such Storage could be found.
func ExtractOverwritingStorage(ctx context.Context) Storage

Subsequently, you can call the Get() method of this Storage to get an overwritten value.

// Get returns the Value of the variable under this name if it was overwritten
Get(name string) Value

Once you got a Value, take advantage of the provided helper methods and functions to implement the overwriting behavior cleanly.

// Value wraps a raw interface{} value
type Value interface {
    // AsIs returns the wrapped value as-is.
    AsIs() interface{}
    // AsString typecast to string. Returns zero value if not possible to cast.
    AsString() string
    // AsBool typecast to bool. Returns zero value if not possible to cast.
    AsBool() bool
    // AsFloat typecast to float64. Returns zero value if not possible to cast.
    // Note: Try not to use a raw value of type float32 if possible.
    // https://stackoverflow.com/questions/67145364/golang-losing-precision-while-converting-float32-to-float64
    AsFloat() float64
    // AsInt typecast to int64. Returns zero value if not possible to cast.
    // NOTE: JSON by default unmarshal to numbers which are treated as float.
    // Using this method, your float will lose precision as an int64.
    AsInt() int64
}

func Unmarshal[T any](v Value) (*T, error)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrPointerArgumentRequired ...
	ErrPointerArgumentRequired = errors.New("value type should be a pointer to struct")
)

Functions

func Unmarshal

func Unmarshal[T any](v Value) (*T, error)

Unmarshal into the given type

func WithOverwrittenVariables

func WithOverwrittenVariables(ctx context.Context, overwrittenVariables map[string]interface{}) context.Context

WithOverwrittenVariables returns a new context.Context that holds a reference to the given overwritten variables.

Note: This implementation creates a shallow copy of the input map to hold a reference to all of its key-value pairs. The expectation is that clients must only use the lib to **get** overwritten values from the context. If the value clients receive is a pointer or a complex struct containing some pointers or a pointer-like object such as an array or a map, they should NOT update this value since the context is most likely passed into many go-routines running in parallel. As a consequence, clients may run into a race condition if things goes wrong.

Types

type IOverwritingOps

type IOverwritingOps interface {
	// ExtractOverwritingStorage returns the Storage currently associated with ctx, or
	// nil if no such Storage could be found.
	ExtractOverwritingStorage(ctx context.Context) Storage
	// GetOverwrittenValue returns the Value of the variable under this name if it was overwritten
	GetOverwrittenValue(ctx context.Context, name string) Value
}

IOverwritingOps ...

var Ops IOverwritingOps = overwritingOps{}

Ops provides a wrapper around all overwriting-related functions provided by the library. It can be mocked to help write tests more fluently.

type MockIOverwritingOps

type MockIOverwritingOps struct {
	mock.Mock
}

MockIOverwritingOps is an autogenerated mock type for the IOverwritingOps type

func MockOps

func MockOps() (*MockIOverwritingOps, func())

MockOps can be used in tests to perform monkey-patching on Ops

func (*MockIOverwritingOps) ExtractOverwritingStorage

func (_m *MockIOverwritingOps) ExtractOverwritingStorage(ctx context.Context) Storage

ExtractOverwritingStorage provides a mock function with given fields: ctx

func (*MockIOverwritingOps) GetOverwrittenValue

func (_m *MockIOverwritingOps) GetOverwrittenValue(ctx context.Context, name string) Value

GetOverwrittenValue provides a mock function with given fields: ctx, name

type MockStorage

type MockStorage struct {
	mock.Mock
}

MockStorage is an autogenerated mock type for the Storage type

func (*MockStorage) Get

func (_m *MockStorage) Get(name string) Value

Get provides a mock function with given fields: name

type MockValue

type MockValue struct {
	mock.Mock
}

MockValue is an autogenerated mock type for the Value type

func (*MockValue) AsBool

func (_m *MockValue) AsBool() bool

AsBool provides a mock function with given fields:

func (*MockValue) AsFloat

func (_m *MockValue) AsFloat() float64

AsFloat provides a mock function with given fields:

func (*MockValue) AsInt

func (_m *MockValue) AsInt() int64

AsInt provides a mock function with given fields:

func (*MockValue) AsIs

func (_m *MockValue) AsIs() interface{}

AsIs provides a mock function with given fields:

func (*MockValue) AsString

func (_m *MockValue) AsString() string

AsString provides a mock function with given fields:

func (*MockValue) Unmarshal

func (_m *MockValue) Unmarshal(t interface{}) error

Unmarshal provides a mock function with given fields: t

type Storage

type Storage interface {
	// Get returns the Value of the variable under this name if it was overwritten
	Get(name string) Value
}

Storage is the container of all overwritten variables

func ExtractOverwritingStorage

func ExtractOverwritingStorage(ctx context.Context) Storage

ExtractOverwritingStorage returns the Storage currently associated with ctx, or nil if no such Storage could be found.

type Value

type Value interface {
	// AsIs returns the wrapped value as-is.
	AsIs() interface{}
	// AsString typecast to string. Returns zero value if not possible to cast.
	AsString() string
	// AsBool typecast to bool. Returns zero value if not possible to cast.
	AsBool() bool
	// AsFloat typecast to float64. Returns zero value if not possible to cast.
	// Note: Try not to use a raw value of type float32 if possible.
	// https://stackoverflow.com/questions/67145364/golang-losing-precision-while-converting-float32-to-float64
	AsFloat() float64
	// AsInt typecast to int64. Returns zero value if not possible to cast.
	// NOTE: JSON by default unmarshal to numbers which are treated as float.
	// Using this method, your float will lose precision as an int64.
	AsInt() int64
}

Value wraps a raw interface{} value

func GetOverwrittenValue

func GetOverwrittenValue(ctx context.Context, name string) Value

GetOverwrittenValue returns the Value of the variable under this name if it was overwritten

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL