launchdarkly / c-client-sdk

LaunchDarkly Client-side SDK for C/C++
Other
7 stars 15 forks source link

Allow registering a general feature flag listener #83

Closed ehsan closed 1 year ago

ehsan commented 2 years ago

Is your feature request related to a problem? Please describe. We're using LaunchDarkly in our product and one of our use cases has been to cache the LDClientCPP flags at runtime so that we can use LDClientCPP::restoreFlags() early at startup to ensure the latest copy of the feature flags are available before LaunchDarkly has had a chance to read the remote flags.

In order to do this, we needed to register a flag listener function which doesn't filter on the key (flag name) but rather is invoked for every feature flag change.

We did this with a small change to LDi_listenersDispatch() to accept any flags if the listener has been registered with the flag name set to "*". It would be great if the upstream SDK supported this.

Describe the solution you'd like I'd like to be able to register a flag listener that's invoked for changes to any feature flags. I'm not married to the "*" solution, a separate API for example would work just as well for our needs.

Describe alternatives you've considered The only other alternative for our use case would be to have a list of flag names to listen for, but that's impractical for us given that various engineers can add or archive feature flags at will.

Additional context For reference, this is the change we made on our end to satisfy this use case: image

cwaldren-ld commented 2 years ago

Hi @ehsan , thanks for this request.

If I understand correctly, the use-case here is saving/restoring the flag data so that it can be used to bootstrap initialization of the SDK before it has connected to LaunchDarkly, or even if it is offline.

A similar feature exists in some of our other client SDKs, and I'm wondering if an implementation of that in the C Client would satisfy your needs.

Please take a look at the .NET Client SDK (direct link), and let me know if that would be suitable.

Filed internally as 161128.

ehsan commented 2 years ago

If I understand correctly, the use-case here is saving/restoring the flag data so that it can be used to bootstrap initialization of the SDK before it has connected to LaunchDarkly, or even if it is offline.

That's right!

A similar feature exists in some of our other client SDKs, and I'm wondering if an implementation of that in the C Client would satisfy your needs.

Oh yes totally, that would be great! But I should also mention that we also have this problem in the node and Electron SDKs. We were planning to expand our C++ SDK solution to cover those too eventually though so far we haven't found equivalents to saveFlags() and restoreFlags().

Are you planning to extend this feature to all of the LaunchDarkly SDKs eventually?

cwaldren-ld commented 2 years ago

Oh yes totally, that would be great!

Great, I'll make a note of that 👍

Are you planning to extend this feature to all of the LaunchDarkly SDKs eventually?

Please file an issue on those repos if you are interested in the feature, as this helps us allocate resources.

kinyoklion commented 1 year ago

Hello @ehsan,

We have released a new C++ SDK, with C bindings, which has first class support for persistence. The persistence interface can be implemented in either C++ or C.

The SDK uses this to restore flag state and to store flag state, so your application doesn't have to track the flag state manually.

This is an example of a basic implementation of the interface.

class FilePersistence : public IPersistence {
   public:
    FilePersistence(std::string directory) : directory_(std::move(directory)) {
        std::filesystem::create_directories(directory_);
    }
    void Set(std::string storage_namespace,
                  std::string key,
                  std::string data) noexcept override {
        try {
            std::ofstream file;
            file.open(MakePath(storage_namespace, key));
            file << data;
            file.close();
        } catch (...) {
            std::cout << "Problem writing" << std::endl;
        }
    }

    void Remove(std::string storage_namespace,
                     std::string key) noexcept override {
        std::filesystem::remove(MakePath(storage_namespace, key));
    }

    std::optional<std::string> Read(std::string storage_namespace,
                                    std::string key) noexcept override {
        auto path = MakePath(storage_namespace, key);

        try {
            if (std::filesystem::exists(path)) {
                std::ifstream file(path);
                std::stringstream buffer;
                buffer << file.rdbuf();
                return buffer.str();
            }
        } catch (...) {
            std::cout << "Problem reading" << std::endl;
        }
        return std::nullopt;
    }

   private:
    std::string MakePath(std::string storage_namespace, std::string key) {
        return directory_ + "/" + storage_namespace + "_" + key;
    }
    std::string directory_;
};

Thanks, Ryan

ehsan commented 1 year ago

Thanks Ryan. We've actually pivoted to a different business, no longer using LD at this time. Bit I do appreciate your update.