evaluation

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2022 License: Apache-2.0 Imports: 10 Imported by: 0

README

SimpleFlags Evaluation Engine


Simple flags which introduce variables, expressions and rule engine

Overview


This is experimental project, do not use it in production!!!

  1. No Variations
  2. No Clauses
  3. No configuration kind (bool, number ...)
  4. No states (on, off)
  5. No segments (target groups)
  6. No private attributes or anonymous target
1. No variations

More than 80% of flags are bool flags and there is no reason to have just two values as variation. Many companies use variation "true" and "false" but on my engine true and false are reserved keywords. Following JSON and swagger spec engine use data types from specification. If we talk about multivariate flags like strings, number etc. than probably there is a reason to have variation but if we look into flags there is no variations sharing between flags, so I don't see a reason to have variations at all.

2. No custom rules and clauses

Clauses are hard to read and to maintain so IMO I think there are better ways like rule engine evaluations. instead of maintaining schema for complex clauses better use expression language.

dev:
    value: true
    expression: target.identifier in beta
3. No configuration kind (bool, number ...)

No reason to have flag/configuration kind and engine supports dynamic nature of flags, so if flag serves bool than the flag is boolean. One thing why engine is different from others it can serve any values as you wish:

  • Bool
  • Number
  • String
  • Map
  • Slice
evaluate("some_flag", target).Bool(defaultValue)

if some_flag serves string values 'true' or 'false' you can call bool or string methods it will give same results.

when Generics are fully implemented into Golang then signature will be:

type types interface {
	~bool | ~string | ~float64 | ~map[string]any | []any
}
evaluate[T types]("some_flag", target, true) T
4. No states (on, off)

Instead of "on" or "off" there is simple bool field on: bool, if flag is on then 'on' will be true.

5. No segments (target groups)

This is the most interesting part there are no target groups or segments. So we are introducing concept of variables why variables? Because feature flags are remote "if" or online "if" and if is part of the language so variables are more suitable. Variable can have any data type like bool, string, number, slice, map. Variables can be used as serving values and values used in rule engine. Variables can be global and local. Local variables are used in the project and globals outside the project. Imagine if you have paid customers, and you want to share among different projects then you can specify global variable paidCustomers.

paidCustomers = [
    'enver', 'bisevac'
]

and in flag rule expr:

target.name in paidCustomers

Global variables and local variables can have the same name.

6. No private attributes or anonymous target

All targets are private, so they are never stored and doesn't require any required field, you can put any property.

Draft design

Configuration structure:

  • project is project identifier in your system
  • environment can be one of your environments like (dev, prod, stage, stage1)
  • identifier is unique configuration (flag) identifier
  • deprecated if true, than this configuration should be replaced with new configuration
  • on if true configuration is active otherwise it will serve always off_value
  • off_value when on is false it will serve always this value
  • prerequisites check first dependencies on flag
  • rules array of two fields expression and value. Value can be one of the types: string, number, object (JSON object), array, boolean, null. Expressions will be explained in next section.
  • version configuration version

Basic sample configuration:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "bool-flag",
  "deprecated": false,
  "on": true,
  "off_value": false,
  "prerequisites": [],
  "rules": [
    {
      "value": true, // default serve
      "expression": ""
    }
  ],
  "version": 1
}

As you can see this is very simple configuration which will server just one of the values true or false. Note: true and false are reserved keywords.

Basic number flag configuration:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "number-flag",
  "deprecated": false,
  "on": true,
  "off_value": 1,
  "prerequisites": [],
  "rules": [
    {
      "value": 5, // default serve
      "expression": ""
    }
  ],
  "version": 1
}

Basic number flag configuration using variables:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "number-flag",
  "deprecated": false,
  "on": true,
  "off_value": "${one}",
  "prerequisites": [],
  "rules": [
    {
      "value": "${five}", // default serve
      "expression": ""
    }
  ],
  "version": 1
}

Basic string flag configuration:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "string-flag",
  "deprecated": false,
  "on": true,
  "off_value": "item two",
  "prerequisites": [],
  "rules": [
    {
      "value": "item one", // default serve
      "expression": ""
    }
  ],
  "version": 1
}

Basic array flag configuration:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "slice-flag",
  "deprecated": false,
  "on": true,
  "off_value": [],
  "prerequisites": [],
  "rules": [
    {
      "value": ["item one", "item two", "item three","${success}"], // default serve
      "expression": ""
    }
  ],
  "version": 1
}

Basic object flag configuration:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "number-flag",
  "deprecated": false,
  "on": true,
  "off_value": {},
  "prerequisites": [],
  "rules": [
    {
      "value": { // default serve
        "os": "linux",
        "distro": "arch"
      },
      "expression": ""
    }
  ],
  "version": 1
}

Prerequisites flag configuration:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "number-flag",
  "deprecated": false,
  "on": true,
  "off_value": {},
  "prerequisites": [
    {
      "identifier": "bool-flag",
      "value": true
    }
  ],
  "rules": [
    {
      "value": {  // default serve
        "os": "linux",
        "distro": "arch"
      },
      "expression": ""
    }
  ],
  "version": 1
}

Rule based flag configuration:

it will serve true only if target identifier is equal to 'enver' otherwise it will be false

{
  "project": "demo",
  "environment": "dev",
  "identifier": "bool-flag",
  "deprecated": false,
  "on": true,
  "off_value": false,
  "prerequisites": [],
  "rules": [
    {
      "value": true,
      "expression": "target.identifier == 'enver'"
    }
  ],
  "version": 1
}

multivariate flag with some custom rule

{
  "project": "demo",
  "environment": "dev",
  "identifier": "bool-flag",
  "deprecated": false,
  "on": true,
  "off_value": "item1",
  "prerequisites": [],
  "rules": [
    {
      "value": "item3",
      "expression": "target.identifier == 'enver'"
    },
    {
      "value": "item2", // default serve
      "expression": ""
    }
  ],
  "version": 1
}

Percentage rollout flag configuration:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "bool-flag",
  "deprecated": false,
  "on": true,
  "off_value": false,
  "prerequisites": [],
  "rules": [
    {
      "value": [
        {
          "__value__": true,
          "__weight__": 50
        },
        {
          "__value__": false,
          "__weight__": 50
        }
      ],
      "expression": "target.identifier in beta_users"
    },
    {
      "value": false, // default serve
      "expression": ""
    }
  ],
  "version": 1
}

Scheduled configurations

{
  "project": "demo",
  "environment": "dev",
  "identifier": "bool-flag",
  "deprecated": false,
  "on": true,
  "off_value": false,
  "prerequisites": [],
  "rules": [
    {
      "value": true,
      "expression": "target.identifier in paid_customers and now() >= date('2022-10-01')"
    }
  ],
  "version": 1
}

another example of scheduled flags:

{
  "project": "demo",
  "environment": "dev",
  "identifier": "bool-flag",
  "deprecated": false,
  "on": true,
  "off_value": false,
  "prerequisites": [],
  "rules": [
    {
      "value": [
        {
          "__value__": true,
          "__weight__": 50
        },
        {
          "__value__": false,
          "__weight__": 50
        }
      ],
      "expression": "target.identifier in beta_users and now() >= date('2022-10-01')"
    },
    {
      "value": false, // default serve
      "expression": ""
    }
  ],
  "version": 1
}

Documentation

Index

Constants

View Source
const (
	CreateFlagEvent = "create_flag"
	PatchFlagEvent  = "patch_flag"
	DeleteFlagEvent = "delete_flag"
	CreateVariable  = "create_var"
	PatchVariable   = "patch_var"
	DeleteVariable  = "delete_var"
)

Variables

View Source
var (
	// ErrQueryProviderMissing ...
	ErrQueryProviderMissing = errors.New("query field is missing in evaluator")
)

Functions

func SetLogger

func SetLogger(logger Logger)

func Variables

func Variables(expression string) ([]string, error)

Types

type Configuration

type Configuration struct {
	Project       string         `json:"project"`
	Environment   string         `json:"environment"`
	Identifier    string         `json:"identifier"`
	Deprecated    bool           `json:"deprecated"`
	On            bool           `json:"on"`
	OnValue       interface{}    `json:"on_value"`
	OffValue      interface{}    `json:"off_value"`
	Rules         []Rule         `json:"rules"`
	Prerequisites []Prerequisite `json:"prerequisites"`
	Version       uint           `json:"version"`

} // @name Configuration

type Configurations

type Configurations []Configuration // @name Configurations

type DataProvider

type DataProvider interface {
	GetVariable(key string) (Variable, error)
	GetConfiguration(key string) (Configuration, error)
}

DataProvider provides methods for segment and flag retrieval

type Evaluation

type Evaluation struct {
	Project     string      `json:"project"`
	Environment string      `json:"environment"`
	Identifier  string      `json:"identifier"`
	Value       interface{} `json:"value"`
	// contains filtered or unexported fields

} // @name Evaluation

func (Evaluation) Bool

func (v Evaluation) Bool(defaultValue bool) bool

func (Evaluation) Int

func (v Evaluation) Int(defaultValue int) int

func (Evaluation) IsNone

func (v Evaluation) IsNone() bool

func (Evaluation) Map

func (v Evaluation) Map(defaultValue map[string]interface{}) map[string]interface{}

func (Evaluation) Number

func (v Evaluation) Number(defaultValue float64) float64

func (Evaluation) String

func (v Evaluation) String(defaultValue string) string

type Evaluations

type Evaluations []Evaluation // @name Evaluations

type Evaluator

type Evaluator struct {
	// contains filtered or unexported fields
}

Evaluator engine evaluates flag from provided query

func NewEvaluator

func NewEvaluator(provider DataProvider) (*Evaluator, error)

NewEvaluator constructs evaluator with query instance

func (*Evaluator) Evaluate

func (e *Evaluator) Evaluate(key string, target Target) Evaluation

type Logger

type Logger interface {
	Debug(args ...interface{})
	Debugf(template string, args ...interface{})
	Info(args ...interface{})
	Infof(template string, args ...interface{})
	Warn(args ...interface{})
	Warnf(template string, args ...interface{})
	Error(args ...interface{})
	Errorf(template string, args ...interface{})
	Panic(args ...interface{})
	Panicf(template string, args ...interface{})
	Fatal(args ...interface{})
	Fatalf(template string, args ...interface{})
}

type Prerequisite

type Prerequisite struct {
	Identifier string      `json:"identifier" validate:"required"`
	Value      interface{} `json:"value" validate:"required"`

} // @name Prerequisite

type RolloutItem added in v0.2.0

type RolloutItem struct {
	Value  interface{} `json:"__value__"`
	Weight int         `json:"__weight__"`

} // @name RolloutItem

type Rule

type Rule struct {
	Expression string      `json:"expression"`
	Value      interface{} `json:"value"`

} // @name Rule

type Target

type Target map[string]interface{} // @name Target

func (Target) GetAttrValue

func (t Target) GetAttrValue(attr string) reflect.Value

GetAttrValue returns value from target with specified attribute

type Variable

type Variable struct {
	Identifier string      `json:"identifier"`
	Value      interface{} `json:"value"`

} // @name Var

Jump to

Keyboard shortcuts

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