package subsystems import ( "io" "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes" ) // PersistentDataStore is an interface for a data store that holds feature flags and related data in a // serialized form. // // This interface should be used for database integrations, or any other data store implementation that // stores data in some external service. The SDK will provide its own caching layer on top of the // persistent data store; the data store implementation should not provide caching, but simply do every // query or update that the SDK tells it to do. // // Implementations must be safe for concurrent access from multiple goroutines. // // Error handling is defined as follows: if any data store operation encounters a database error, or // is otherwise unable to complete its task, it should return an error value to make the SDK aware of // The SDK will log the exception and will assume that the data store is now in a non-operational // non-operational state; the SDK will then start polling IsStoreAvailable() to determine when the // store has started working again. // // Whenever a new implementation of this interface is written, it should have unit tests that use // testhelpers.storetest.PersistentDataStoreTestSuite. This ensures that all of the interface methods // are exercised consistently in various scenarios that might be encountered in real SDK usage. type PersistentDataStore interface { io.Closer // Init overwrites the store's contents with a set of items for each collection. // // All previous data should be discarded, regardless of versioning. // // The update should be done atomically. If it cannot be done atomically, then the store // must first add or update each item in the same order that they are given in the input // data, and then delete any previously stored items that were not in the input data. Init(allData []ldstoretypes.SerializedCollection) error // Get retrieves an item from the specified collection, if available. // // If the specified key does not exist in the collection, it should return a SerializedItemDescriptor // with a Version of -1 and an Item of nil. // // If the item has been deleted and the store contains a placeholder, it should return that // placeholder rather than filtering it out. Get(kind ldstoretypes.DataKind, key string) (ldstoretypes.SerializedItemDescriptor, error) // GetAll retrieves all items from the specified collection. // // If the store contains placeholders for deleted items, it should include them in the results, // not filter them out. GetAll(kind ldstoretypes.DataKind) ([]ldstoretypes.KeyedSerializedItemDescriptor, error) // Upsert updates or inserts an item in the specified collection. For updates, the object will only be // updated if the existing version is less than the new version. // // The SDK may pass a SerializedItemDescriptor that represents a placeholder for a deleted item. In // that case, assuming the version is greater than any existing version of that item, the store should // retain that placeholder rather than simply not storing anything. // // The method returns the updated item if the update was successful; or, if the update was not // successful because the store contained an equal or higher version, it returns the item that is // in the store. Upsert(kind ldstoretypes.DataKind, key string, item ldstoretypes.SerializedItemDescriptor) (bool, error) // IsInitialized returns true if the data store contains a data set, meaning that Init has been // called at least once. // // In a shared data store, it should be able to detect this even if Init was called in a // different process: that is, the test should be based on looking at what is in the data store. // Once this has been determined to be true, it can continue to return true without having to // check the store again; this method should be as fast as possible since it may be called during // feature flag evaluations. IsInitialized() bool // IsStoreAvailable tests whether the data store seems to be functioning normally. // // This should not be a detailed test of different kinds of operations, but just the smallest possible // operation to determine whether (for instance) we can reach the database. // // Whenever one of the store's other methods returns an error, the SDK will assume that it may have // become unavailable (e.g. the database connection was lost). The SDK will then call // IsStoreAvailable() at intervals until it returns true. IsStoreAvailable() bool }