Skip to content

Conversation

@jblacker
Copy link
Contributor

@jblacker jblacker commented May 4, 2025

This PR

  • Adds Event Handling to Multiprovider
  • Adds Hooks support to Multiprovider
  • Adds basic logging functionality to Multiprovider (separate from logging hooks)

Related Issues

Fixes #599

Notes

This should complete the implementation of the multiprovider per the specification. The hooks support implementation within HookIsolator is largely based on the code in the main OpenFeature SDK for Go along with some inspiration from the JS implementation of their multiprovider.

The strategies could probably use a refactoring to clean them up at this point, but not a blocker.

Follow-up Tasks

  • e2e tests should be added
  • Additional logging is a nice to have
  • Additional documentation

How to test

This can be tested by building two providers, adding them to a multiprovider and testing it provides the expected results based on the selected strategy.

jblacker added 11 commits May 4, 2025 13:30
- remove event publishing toggle options, this will always be enabled now
- update options for logging

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
This allows for always calling the logger, but can act like a no-op depending on the config

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Added a ton of logging for testing purposes at the debug level

Implemented the eventing based on the multi-provider specification in the specification's appendix.
This also now includes state change handling

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Updated makefile to have this automatically generated

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
this is used to prevent context changes performed by hooks from leaking to other providers

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Signed-off-by: Jordan Blacker <jblacker@justworks.com>
…phase

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Signed-off-by: Jordan Blacker <jblacker@justworks.com>
@jblacker jblacker requested review from a team as code owners May 4, 2025 21:39
jblacker added 2 commits May 4, 2025 17:40
Signed-off-by: Jordan Blacker <jblacker@justworks.com>
- Made sure the hooks exposed from `HookIsolators` are exposed to the global hooks method
- Combined two loops into one for validation & wrapping

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
@jblacker jblacker force-pushed the 599/improve-multiprovider branch from 739c5bd to dcf8ab2 Compare May 4, 2025 21:40
@jblacker jblacker changed the title feat: Complete Implementation of Multiprovider feat: Complete Implementation of Multi-Provider May 4, 2025
@aepfli
Copy link
Member

aepfli commented May 6, 2025

thank you for this great contribution, multiprovider is a feature a lot of people are using, I am still wondering, as this is spec'd within our spec repo, should we maybe move the multiprovider to the go-sdk, and maybe start writing some gherkin tests to unifiy this behavior

@toddbaert
Copy link
Member

@jblacker Thanks! I will formally review this tomorrow. I do suspect that you could reduce some code by adding this directly to the SDK as @aepfli suggested (the rest of the community also supports this).

Pending review, I am happy to merge this here and then deprecate/move it later, but I'd also support a refactored addition directly to the SDK; that's up to you.

@jblacker
Copy link
Contributor Author

jblacker commented May 6, 2025

These are great points. I'm thinking that assuming this looks good to go we merge it in here just in case anyone else has a need for it while I work on a cleaned up refactored version for directly in the SDK.

Makefile updated to remove hardcoding of sdk version and clean up of unnecessary comments

Co-authored-by: Roman Dmytrenko <rdmytrenko@gmail.com>
Signed-off-by: Jordan Blacker <jblacker@users.noreply.github.com>
@jblacker jblacker requested a review from erka May 9, 2025 13:44
// WithoutLogging Disables logging functionality
func WithoutLogging() Option {
return func(conf *Configuration) {
conf.logger = nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
conf.logger = nil
conf.logger = slog.New(slog.DiscardHandler)

it's probably a better option and ConditionalLogger could be removed. wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great idea. I'm not super familiar with slog usage in libraries so I wasn't sure if it was acceptable to "override" the handler being used in the main application. I'm now realizing that this would only apply to the logging calls within the multiprovider so it's not overriding anything. 🙂
I'll accept this change and clean it all up

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I'm almost done with the ported implementation as a package in the go-sdk with a more streamlined implementation of the strategies. I can still make that change here, but let me know if it's worthwhile.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jblacker, it's up to you but we'll likely deprecate this package once it's available in the SDK.

}

// Status the current state of the MultiProvider
func (mp *MultiProvider) Status() of.State {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as FYI. The latest version (v1.14.1) of go-sdk doesn't have this func anymore. If the latest version of sdk is used it could simplify this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State management moved to the SDK. Good catch @erka.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately issue #671 prevents us from using a version of the SDK above 13.1 in any module.

jblacker added 2 commits May 14, 2025 21:57
Used package generation to avoid needing to specify version & not including home dir info

Signed-off-by: Jordan Blacker <jblacker@justworks.com>
Signed-off-by: Jordan Blacker <jblacker@justworks.com>
@jblacker jblacker force-pushed the 599/improve-multiprovider branch from 54490ee to dd50b78 Compare May 15, 2025 01:58
@erka
Copy link
Contributor

erka commented May 15, 2025

Some observations.

I tested this provider with go-sdk@v0.14.1, and it doesn't seem to work correctly. Specifically:

  • With SetNamedProvider, it fails with provider not yet initialized.
  • With SetNamedProviderAndWait, it hangs indefinitely.

The providerA and providerB work fine if they are set in SetNamedProvider. MultiProvider works with v0.13.1. There seems to be something off with how this provider interacts with go-sdk@v0.14.1.

This is my code

package main

import (
	"context"
	"fmt"

	flipt "github.com/open-feature/go-sdk-contrib/providers/flipt/pkg/provider"
	gofeatureflag "github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg"
	mp "github.com/open-feature/go-sdk-contrib/providers/multi-provider/pkg"

	"github.com/open-feature/go-sdk/openfeature"
)

func main() {
	ctx := context.Background()
	providerA, _ := gofeatureflag.NewProviderWithContext(ctx, gofeatureflag.ProviderOptions{
		Endpoint: "http://localhost:1031",
	},
	)
	providerB := flipt.NewProvider()

	providermap := make(mp.ProviderMap)
	providermap["goff"] = providerA
	providermap["flipt"] = providerB
	provider, err := mp.NewMultiProvider(providermap, mp.StrategyFirstMatch)
	if err != nil {
		panic(err)
	}
	openfeature.SetNamedProvider("multi", provider)
	client := openfeature.NewClient("multi")
	res, err := client.BooleanValueDetails(ctx, "this-demo", false, openfeature.NewEvaluationContext("some", nil))
	if err != nil {
		panic(err)
	}
	fmt.Println(res)
}

@jblacker
Copy link
Contributor Author

@erka Interesting findings, I'll do some digging into finding out what's going on here. I'm going to see if this issue occurs with the implementation that I have a draft PR open here on the main SDK: open-feature/go-sdk#354

@erka
Copy link
Contributor

erka commented May 19, 2025

Should this PR be closed in this case? Having multiple similar PRs can make it hard to focus and determine where to continue the discussion. wdyt @jblacker?

@jblacker
Copy link
Contributor Author

I'll close this out. My draft PR is almost done; just finishing up testing. Hoping to have it ready for review by the end of the week.

@jblacker jblacker closed this May 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Multi-Provider

5 participants