Skip to content

Commit

Permalink
feat!: consolidated configuration change events into one event (#241)
Browse files Browse the repository at this point in the history
## This PR

Consolidates configuration change events into a singular event (avoid
superfluous bulk emissions).
Existing `configuration_change` event listeners will need to update
their handling to consume the singular events.

### Related Issues
<!-- add here the GitHub issue that this PR resolves if applicable -->

Fixes #238 

- [x] [Update
go-sdk-contrib](open-feature/go-sdk-contrib#66)
- [ ] Update java-sdk-contrib

### Notes
<!-- any additional notes for this PR -->

### Follow-up Tasks
<!-- anything that is related to this PR but not done here should be
noted under this section -->
<!-- if there is a need for a new issue, please link it here -->

### How to test
<!-- if applicable, add testing instructions under this section -->

Signed-off-by: Skye Gill <gill.skye95@gmail.com>
  • Loading branch information
skyerus authored Jan 6, 2023
1 parent 3f406b5 commit f9684b8
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 65 deletions.
16 changes: 1 addition & 15 deletions pkg/eval/ievaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,13 @@ const (
NotificationUpdate StateChangeNotificationType = "update"
)

type StateChangeNotification struct {
Type StateChangeNotificationType `json:"type"`
Source string `json:"source"`
FlagKey string `json:"flagKey"`
}

/*
IEvaluator implementations store the state of the flags,
do parsing and validation of the flag state and evaluate flags in response to handlers.
*/
type IEvaluator interface {
GetState() (string, error)
SetState(source string, state string) ([]StateChangeNotification, error)
SetState(source string, state string) (map[string]interface{}, error)

ResolveBooleanValue(
reqID string,
Expand All @@ -47,11 +41,3 @@ type IEvaluator interface {
flagKey string,
context *structpb.Struct) (value map[string]any, variant string, reasons string, err error)
}

func (s *StateChangeNotification) ToMap() map[string]interface{} {
return map[string]interface{}{
"type": string(s.Type),
"source": s.Source,
"flagKey": s.FlagKey,
}
}
2 changes: 1 addition & 1 deletion pkg/eval/json_evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (je *JSONEvaluator) GetState() (string, error) {
return string(data), nil
}

func (je *JSONEvaluator) SetState(source string, state string) ([]StateChangeNotification, error) {
func (je *JSONEvaluator) SetState(source string, state string) (map[string]interface{}, error) {
schemaLoader := gojsonschema.NewStringLoader(schema.FlagdDefinitions)
flagStringLoader := gojsonschema.NewStringLoader(state)
result, err := gojsonschema.Validate(schemaLoader, flagStringLoader)
Expand Down
31 changes: 14 additions & 17 deletions pkg/eval/json_evaluator_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,17 @@ type Evaluators struct {
Evaluators map[string]json.RawMessage `json:"$evaluators"`
}

func (f Flags) Merge(logger *logger.Logger, source string, ff Flags) (Flags, []StateChangeNotification) {
notifications := []StateChangeNotification{}
func (f Flags) Merge(logger *logger.Logger, source string, ff Flags) (Flags, map[string]interface{}) {
notifications := map[string]interface{}{}
result := Flags{Flags: make(map[string]Flag)}
for k, v := range f.Flags {
if v.Source == source {
if _, ok := ff.Flags[k]; !ok {
// flag has been deleted
notifications = append(notifications, StateChangeNotification{
Type: NotificationDelete,
Source: source,
FlagKey: k,
})
notifications[k] = map[string]interface{}{
"type": string(NotificationDelete),
"source": source,
}
continue
}
}
Expand All @@ -37,11 +36,10 @@ func (f Flags) Merge(logger *logger.Logger, source string, ff Flags) (Flags, []S
v.Source = source
val, ok := result.Flags[k]
if !ok {
notifications = append(notifications, StateChangeNotification{
Type: NotificationCreate,
Source: source,
FlagKey: k,
})
notifications[k] = map[string]interface{}{
"type": string(NotificationCreate),
"source": source,
}
} else if !reflect.DeepEqual(val, v) {
if val.Source != source {
logger.Warn(
Expand All @@ -53,11 +51,10 @@ func (f Flags) Merge(logger *logger.Logger, source string, ff Flags) (Flags, []S
),
)
}
notifications = append(notifications, StateChangeNotification{
Type: NotificationUpdate,
Source: source,
FlagKey: k,
})
notifications[k] = map[string]interface{}{
"type": string(NotificationUpdate),
"source": source,
}
}
result.Flags[k] = v
}
Expand Down
58 changes: 36 additions & 22 deletions pkg/eval/json_evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -739,35 +739,40 @@ func BenchmarkResolveObjectValue(b *testing.B) {
func TestMergeFlags(t *testing.T) {
t.Parallel()
tests := []struct {
name string
current eval.Flags
new eval.Flags
newSource string
want eval.Flags
name string
current eval.Flags
new eval.Flags
newSource string
want eval.Flags
wantNotifs map[string]interface{}
}{
{
name: "both nil",
current: eval.Flags{Flags: nil},
new: eval.Flags{Flags: nil},
want: eval.Flags{Flags: map[string]eval.Flag{}},
name: "both nil",
current: eval.Flags{Flags: nil},
new: eval.Flags{Flags: nil},
want: eval.Flags{Flags: map[string]eval.Flag{}},
wantNotifs: map[string]interface{}{},
},
{
name: "both empty flags",
current: eval.Flags{Flags: map[string]eval.Flag{}},
new: eval.Flags{Flags: map[string]eval.Flag{}},
want: eval.Flags{Flags: map[string]eval.Flag{}},
name: "both empty flags",
current: eval.Flags{Flags: map[string]eval.Flag{}},
new: eval.Flags{Flags: map[string]eval.Flag{}},
want: eval.Flags{Flags: map[string]eval.Flag{}},
wantNotifs: map[string]interface{}{},
},
{
name: "empty current",
current: eval.Flags{Flags: nil},
new: eval.Flags{Flags: map[string]eval.Flag{}},
want: eval.Flags{Flags: map[string]eval.Flag{}},
name: "empty current",
current: eval.Flags{Flags: nil},
new: eval.Flags{Flags: map[string]eval.Flag{}},
want: eval.Flags{Flags: map[string]eval.Flag{}},
wantNotifs: map[string]interface{}{},
},
{
name: "empty new",
current: eval.Flags{Flags: map[string]eval.Flag{}},
new: eval.Flags{Flags: nil},
want: eval.Flags{Flags: map[string]eval.Flag{}},
name: "empty new",
current: eval.Flags{Flags: map[string]eval.Flag{}},
new: eval.Flags{Flags: nil},
want: eval.Flags{Flags: map[string]eval.Flag{}},
wantNotifs: map[string]interface{}{},
},
{
name: "extra fields on each",
Expand All @@ -793,6 +798,9 @@ func TestMergeFlags(t *testing.T) {
Source: "2",
},
}},
wantNotifs: map[string]interface{}{
"paka": map[string]interface{}{"type": "write", "source": "2"},
},
},
{
name: "override",
Expand All @@ -807,6 +815,10 @@ func TestMergeFlags(t *testing.T) {
"waka": {DefaultVariant: "on"},
"paka": {DefaultVariant: "on"},
}},
wantNotifs: map[string]interface{}{
"waka": map[string]interface{}{"type": "update", "source": ""},
"paka": map[string]interface{}{"type": "write", "source": ""},
},
},
{
name: "identical",
Expand All @@ -819,15 +831,17 @@ func TestMergeFlags(t *testing.T) {
want: eval.Flags{Flags: map[string]eval.Flag{
"hello": {DefaultVariant: "off"},
}},
wantNotifs: map[string]interface{}{},
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, _ := tt.current.Merge(logger.NewLogger(nil, false), tt.newSource, tt.new)
got, gotNotifs := tt.current.Merge(logger.NewLogger(nil, false), tt.newSource, tt.new)
require.Equal(t, tt.want, got)
require.Equal(t, tt.wantNotifs, gotNotifs)
})
}
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/eval/mock/ievaluator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions pkg/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ func (r *Runtime) updateState(ctx context.Context, syncr sync.ISync) error {
if err != nil {
return fmt.Errorf("set state: %w", err)
}
for _, n := range notifications {
r.Logger.Info(fmt.Sprintf("configuration change (%s) for flagKey %s (%s)", n.Type, n.FlagKey, n.Source))
r.Service.Notify(service.Notification{
Type: service.ConfigurationChange,
Data: n.ToMap(),
})
}

r.Service.Notify(service.Notification{
Type: service.ConfigurationChange,
Data: map[string]interface{}{
"flags": notifications,
},
})
return nil
}

0 comments on commit f9684b8

Please sign in to comment.