launchdarkly_server_sdk/stores/
persistent_store_builders.rs

1use parking_lot::RwLock;
2use std::sync::Arc;
3use std::time::Duration;
4
5use super::persistent_store_wrapper::PersistentDataStoreWrapper;
6use super::store_builders::{BuildError, DataStoreFactory};
7use super::{persistent_store::PersistentDataStore, store::DataStore};
8
9const DEFAULT_CACHE_TIME: Duration = Duration::from_secs(15);
10
11/// PersistentDataStoreFactory is an interface for a factory that creates some implementation of a
12/// [PersistentDataStore].
13///
14/// This interface is implemented by database integrations. Usage is described in
15/// [PersistentDataStore].
16pub trait PersistentDataStoreFactory {
17    /// This is called by the SDK to create the implementation instance.
18    fn create_persistent_data_store(&self) -> Result<Box<dyn PersistentDataStore>, std::io::Error>;
19}
20
21/// Used to create a PersistentDataStoreWrapper instance, which wraps a [PersistentDataStore].
22#[derive(Clone)]
23pub struct PersistentDataStoreBuilder {
24    cache_ttl: Option<Duration>,
25    factory: Arc<dyn PersistentDataStoreFactory>,
26}
27
28impl PersistentDataStoreBuilder {
29    /// Create a new [PersistentDataStoreBuilder] configured with the provided
30    /// [PersistentDataStoreFactory] and a default cache lifetime.
31    pub fn new(factory: Arc<dyn PersistentDataStoreFactory>) -> Self {
32        Self {
33            cache_ttl: Some(DEFAULT_CACHE_TIME),
34            factory,
35        }
36    }
37
38    /// Specifies the cache TTL. Items will be evicted from the cache after this amount of time
39    /// from the time when they were originally cached.
40    ///
41    /// If the value is zero, caching is disabled (equivalent to [PersistentDataStoreBuilder::no_caching]).
42    pub fn cache_time(&mut self, cache_ttl: Duration) -> &mut Self {
43        self.cache_ttl = Some(cache_ttl);
44        self
45    }
46
47    /// Shortcut for calling [PersistentDataStoreBuilder::cache_time] with a duration in seconds.
48    pub fn cache_seconds(&mut self, seconds: u64) -> &mut Self {
49        self.cache_ttl = Some(Duration::from_secs(seconds));
50        self
51    }
52
53    /// Specifies that the in-memory cache should never expire. In this mode, data will be written
54    /// to both the underlying persistent store and the cache, but will only ever be read from the
55    /// persistent store if the SDK is restarted.
56    ///
57    /// Use this mode with caution: it means that in a scenario where multiple processes are
58    /// sharing the database, and the current process loses connectivity to LaunchDarkly while
59    /// other processes are still receiving updates and writing them to the database, the current
60    /// process will have stale data.
61    pub fn cache_forever(&mut self) -> &mut Self {
62        self.cache_ttl = None;
63        self
64    }
65
66    /// Specifies that the SDK should not use an in-memory cache for the persistent data store.
67    /// This means that every feature flag evaluation will trigger a data store query.
68    pub fn no_caching(&mut self) -> &mut Self {
69        self.cache_ttl = Some(Duration::from_secs(0));
70        self
71    }
72}
73
74impl DataStoreFactory for PersistentDataStoreBuilder {
75    fn build(&self) -> Result<Arc<RwLock<dyn DataStore>>, BuildError> {
76        let store = self
77            .factory
78            .create_persistent_data_store()
79            .map_err(|e| BuildError::InvalidConfig(e.to_string()))?;
80        Ok(Arc::new(RwLock::new(PersistentDataStoreWrapper::new(
81            store,
82            self.cache_ttl,
83        ))))
84    }
85
86    fn to_owned(&self) -> Box<dyn DataStoreFactory> {
87        Box::new(self.clone())
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use crate::stores::persistent_store::tests::InMemoryPersistentDataStore;
94    use std::collections::HashMap;
95
96    use crate::stores::store_types::AllData;
97
98    use super::*;
99
100    struct InMemoryPersistentDataStoreFactory {}
101
102    impl PersistentDataStoreFactory for InMemoryPersistentDataStoreFactory {
103        fn create_persistent_data_store(
104            &self,
105        ) -> Result<Box<(dyn PersistentDataStore + 'static)>, std::io::Error> {
106            Ok(Box::new(InMemoryPersistentDataStore {
107                data: AllData {
108                    flags: HashMap::new(),
109                    segments: HashMap::new(),
110                },
111                initialized: false,
112            }))
113        }
114    }
115
116    #[test]
117    fn builder_can_support_different_cache_ttl_options() {
118        let factory = InMemoryPersistentDataStoreFactory {};
119        let mut builder = PersistentDataStoreBuilder::new(Arc::new(factory));
120
121        assert_eq!(builder.cache_ttl, Some(DEFAULT_CACHE_TIME));
122
123        builder.cache_time(Duration::from_secs(100));
124        assert_eq!(builder.cache_ttl, Some(Duration::from_secs(100)));
125
126        builder.cache_seconds(1000);
127        assert_eq!(builder.cache_ttl, Some(Duration::from_secs(1000)));
128
129        builder.cache_forever();
130        assert_eq!(builder.cache_ttl, None);
131    }
132}