|
| 1 | +module LaunchDarkly |
| 2 | + module Impl |
| 3 | + # |
| 4 | + # Mixin that defines the required methods of a data system implementation. The data system |
| 5 | + # is responsible for managing the SDK's data model, including storage, retrieval, and change |
| 6 | + # detection for feature flag configurations. |
| 7 | + # |
| 8 | + # This module also contains supporting classes and additional mixins for data system |
| 9 | + # implementations, such as DataAvailability, Update, and protocol-specific mixins. |
| 10 | + # |
| 11 | + # For operations that can fail, use {LaunchDarkly::Result} from util.rb. |
| 12 | + # |
| 13 | + # Application code should not need to implement this directly; it is used internally by the |
| 14 | + # SDK's data system implementations. |
| 15 | + # |
| 16 | + # @private |
| 17 | + # |
| 18 | + module DataSystem |
| 19 | + # |
| 20 | + # Starts the data system. |
| 21 | + # |
| 22 | + # This method will return immediately. The provided event will be set when the system |
| 23 | + # has reached an initial state (either permanently failed, e.g. due to bad auth, or succeeded). |
| 24 | + # |
| 25 | + # @param ready_event [Concurrent::Event] Event to set when initialization is complete |
| 26 | + # @return [void] |
| 27 | + # |
| 28 | + def start(ready_event) |
| 29 | + raise NotImplementedError, "#{self.class} must implement #start" |
| 30 | + end |
| 31 | + |
| 32 | + # |
| 33 | + # Halts the data system. Should be called when the client is closed to stop any long running |
| 34 | + # operations. |
| 35 | + # |
| 36 | + # @return [void] |
| 37 | + # |
| 38 | + def stop |
| 39 | + raise NotImplementedError, "#{self.class} must implement #stop" |
| 40 | + end |
| 41 | + |
| 42 | + # |
| 43 | + # Returns an interface for tracking the status of the data source. |
| 44 | + # |
| 45 | + # The data source is the mechanism that the SDK uses to get feature flag configurations, such |
| 46 | + # as a streaming connection (the default) or poll requests. |
| 47 | + # |
| 48 | + # @return [LaunchDarkly::Interfaces::DataSource::StatusProvider] |
| 49 | + # |
| 50 | + def data_source_status_provider |
| 51 | + raise NotImplementedError, "#{self.class} must implement #data_source_status_provider" |
| 52 | + end |
| 53 | + |
| 54 | + # |
| 55 | + # Returns an interface for tracking the status of a persistent data store. |
| 56 | + # |
| 57 | + # The provider has methods for checking whether the data store is (as far |
| 58 | + # as the SDK knows) currently operational, tracking changes in this |
| 59 | + # status, and getting cache statistics. These are only relevant for a |
| 60 | + # persistent data store; if you are using an in-memory data store, then |
| 61 | + # this method will return a stub object that provides no information. |
| 62 | + # |
| 63 | + # @return [LaunchDarkly::Interfaces::DataStore::StatusProvider] |
| 64 | + # |
| 65 | + def data_store_status_provider |
| 66 | + raise NotImplementedError, "#{self.class} must implement #data_store_status_provider" |
| 67 | + end |
| 68 | + |
| 69 | + # |
| 70 | + # Returns an interface for tracking changes in feature flag configurations. |
| 71 | + # |
| 72 | + # @return [LaunchDarkly::Interfaces::FlagTracker] |
| 73 | + # |
| 74 | + def flag_tracker |
| 75 | + raise NotImplementedError, "#{self.class} must implement #flag_tracker" |
| 76 | + end |
| 77 | + |
| 78 | + # |
| 79 | + # Indicates what form of data is currently available. |
| 80 | + # |
| 81 | + # @return [Symbol] One of DataAvailability constants |
| 82 | + # |
| 83 | + def data_availability |
| 84 | + raise NotImplementedError, "#{self.class} must implement #data_availability" |
| 85 | + end |
| 86 | + |
| 87 | + # |
| 88 | + # Indicates the ideal form of data attainable given the current configuration. |
| 89 | + # |
| 90 | + # @return [Symbol] One of DataAvailability constants |
| 91 | + # |
| 92 | + def target_availability |
| 93 | + raise NotImplementedError, "#{self.class} must implement #target_availability" |
| 94 | + end |
| 95 | + |
| 96 | + # |
| 97 | + # Returns the data store used by the data system. |
| 98 | + # |
| 99 | + # @return [Object] The read-only store |
| 100 | + # |
| 101 | + def store |
| 102 | + raise NotImplementedError, "#{self.class} must implement #store" |
| 103 | + end |
| 104 | + |
| 105 | + # |
| 106 | + # Injects the flag value evaluation function used by the flag tracker to |
| 107 | + # compute FlagValueChange events. The function signature should be |
| 108 | + # (key, context) -> value. |
| 109 | + # |
| 110 | + # This method must be called after initialization to enable the flag tracker |
| 111 | + # to compute value changes for flag change listeners. |
| 112 | + # |
| 113 | + # @param eval_fn [Proc] The evaluation function |
| 114 | + # @return [void] |
| 115 | + # |
| 116 | + def set_flag_value_eval_fn(eval_fn) |
| 117 | + raise NotImplementedError, "#{self.class} must implement #set_flag_value_eval_fn" |
| 118 | + end |
| 119 | + |
| 120 | + # |
| 121 | + # Represents the availability of data in the SDK. |
| 122 | + # |
| 123 | + class DataAvailability |
| 124 | + # The SDK has no data and will evaluate flags using the application-provided default values. |
| 125 | + DEFAULTS = :defaults |
| 126 | + |
| 127 | + # The SDK has data, not necessarily the latest, which will be used to evaluate flags. |
| 128 | + CACHED = :cached |
| 129 | + |
| 130 | + # The SDK has obtained, at least once, the latest known data from LaunchDarkly. |
| 131 | + REFRESHED = :refreshed |
| 132 | + |
| 133 | + ALL = [DEFAULTS, CACHED, REFRESHED].freeze |
| 134 | + |
| 135 | + # |
| 136 | + # Returns whether this availability level is **at least** as good as the other. |
| 137 | + # |
| 138 | + # @param [Symbol] self_level The current availability level |
| 139 | + # @param [Symbol] other The other availability level to compare against |
| 140 | + # @return [Boolean] true if this availability level is at least as good as the other |
| 141 | + # |
| 142 | + def self.at_least?(self_level, other) |
| 143 | + return true if self_level == other |
| 144 | + return true if self_level == REFRESHED |
| 145 | + return true if self_level == CACHED && other == DEFAULTS |
| 146 | + |
| 147 | + false |
| 148 | + end |
| 149 | + end |
| 150 | + |
| 151 | + # |
| 152 | + # Mixin that defines the required methods of a diagnostic accumulator implementation. |
| 153 | + # The diagnostic accumulator is used for collecting and reporting diagnostic events |
| 154 | + # to LaunchDarkly for monitoring SDK performance and behavior. |
| 155 | + # |
| 156 | + # Application code should not need to implement this directly; it is used internally by the SDK. |
| 157 | + # |
| 158 | + module DiagnosticAccumulator |
| 159 | + # |
| 160 | + # Record a stream initialization. |
| 161 | + # |
| 162 | + # @param timestamp [Float] The timestamp |
| 163 | + # @param duration [Float] The duration |
| 164 | + # @param failed [Boolean] Whether it failed |
| 165 | + # @return [void] |
| 166 | + # |
| 167 | + def record_stream_init(timestamp, duration, failed) |
| 168 | + raise NotImplementedError, "#{self.class} must implement #record_stream_init" |
| 169 | + end |
| 170 | + |
| 171 | + # |
| 172 | + # Record events in a batch. |
| 173 | + # |
| 174 | + # @param events_in_batch [Integer] The number of events |
| 175 | + # @return [void] |
| 176 | + # |
| 177 | + def record_events_in_batch(events_in_batch) |
| 178 | + raise NotImplementedError, "#{self.class} must implement #record_events_in_batch" |
| 179 | + end |
| 180 | + |
| 181 | + # |
| 182 | + # Create an event and reset the accumulator. |
| 183 | + # |
| 184 | + # @param dropped_events [Integer] The number of dropped events |
| 185 | + # @param deduplicated_users [Integer] The number of deduplicated users |
| 186 | + # @return [Object] The diagnostic event |
| 187 | + # |
| 188 | + def create_event_and_reset(dropped_events, deduplicated_users) |
| 189 | + raise NotImplementedError, "#{self.class} must implement #create_event_and_reset" |
| 190 | + end |
| 191 | + end |
| 192 | + |
| 193 | + # |
| 194 | + # Mixin that defines the required methods for components that can receive a diagnostic accumulator. |
| 195 | + # Components that include this mixin can report diagnostic information to LaunchDarkly for |
| 196 | + # monitoring SDK performance and behavior. |
| 197 | + # |
| 198 | + # Application code should not need to implement this directly; it is used internally by the SDK. |
| 199 | + # |
| 200 | + module DiagnosticSource |
| 201 | + # |
| 202 | + # Set the diagnostic_accumulator to be used for reporting diagnostic events. |
| 203 | + # |
| 204 | + # @param diagnostic_accumulator [DiagnosticAccumulator] The accumulator |
| 205 | + # @return [void] |
| 206 | + # |
| 207 | + def set_diagnostic_accumulator(diagnostic_accumulator) |
| 208 | + raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator" |
| 209 | + end |
| 210 | + end |
| 211 | + |
| 212 | + # |
| 213 | + # Mixin that defines the required methods of an initializer implementation. An initializer |
| 214 | + # is a component capable of retrieving a single data result, such as from the LaunchDarkly |
| 215 | + # polling API. |
| 216 | + # |
| 217 | + # The intent of initializers is to quickly fetch an initial set of data, which may be stale |
| 218 | + # but is fast to retrieve. This initial data serves as a foundation for a Synchronizer to |
| 219 | + # build upon, enabling it to provide updates as new changes occur. |
| 220 | + # |
| 221 | + # Application code should not need to implement this directly; it is used internally by the SDK. |
| 222 | + # |
| 223 | + module Initializer |
| 224 | + # |
| 225 | + # Fetch should retrieve the initial data set for the data source, returning |
| 226 | + # a Basis object on success, or an error message on failure. |
| 227 | + # |
| 228 | + # @return [LaunchDarkly::Result] A Result containing either a Basis or an error message |
| 229 | + # |
| 230 | + def fetch |
| 231 | + raise NotImplementedError, "#{self.class} must implement #fetch" |
| 232 | + end |
| 233 | + end |
| 234 | + |
| 235 | + # |
| 236 | + # Update represents the results of a synchronizer's ongoing sync method. |
| 237 | + # |
| 238 | + class Update |
| 239 | + # @return [Symbol] The state of the data source |
| 240 | + attr_reader :state |
| 241 | + |
| 242 | + # @return [ChangeSet, nil] The change set if available |
| 243 | + attr_reader :change_set |
| 244 | + |
| 245 | + # @return [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] Error information if applicable |
| 246 | + attr_reader :error |
| 247 | + |
| 248 | + # @return [Boolean] Whether to revert to FDv1 |
| 249 | + attr_reader :revert_to_fdv1 |
| 250 | + |
| 251 | + # @return [String, nil] The environment ID if available |
| 252 | + attr_reader :environment_id |
| 253 | + |
| 254 | + # |
| 255 | + # @param state [Symbol] The state of the data source |
| 256 | + # @param change_set [ChangeSet, nil] The change set if available |
| 257 | + # @param error [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] Error information if applicable |
| 258 | + # @param revert_to_fdv1 [Boolean] Whether to revert to FDv1 |
| 259 | + # @param environment_id [String, nil] The environment ID if available |
| 260 | + # |
| 261 | + def initialize(state:, change_set: nil, error: nil, revert_to_fdv1: false, environment_id: nil) |
| 262 | + @state = state |
| 263 | + @change_set = change_set |
| 264 | + @error = error |
| 265 | + @revert_to_fdv1 = revert_to_fdv1 |
| 266 | + @environment_id = environment_id |
| 267 | + end |
| 268 | + end |
| 269 | + |
| 270 | + # |
| 271 | + # Mixin that defines the required methods of a synchronizer implementation. A synchronizer |
| 272 | + # is a component capable of synchronizing data from an external data source, such as a |
| 273 | + # streaming or polling API. |
| 274 | + # |
| 275 | + # It is responsible for yielding Update objects that represent the current state of the |
| 276 | + # data source, including any changes that have occurred since the last synchronization. |
| 277 | + # |
| 278 | + # Application code should not need to implement this directly; it is used internally by the SDK. |
| 279 | + # |
| 280 | + module Synchronizer |
| 281 | + # |
| 282 | + # Sync should begin the synchronization process for the data source, yielding |
| 283 | + # Update objects until the connection is closed or an unrecoverable error |
| 284 | + # occurs. |
| 285 | + # |
| 286 | + # @yield [Update] Yields Update objects as synchronization progresses |
| 287 | + # @return [void] |
| 288 | + # |
| 289 | + def sync |
| 290 | + raise NotImplementedError, "#{self.class} must implement #sync" |
| 291 | + end |
| 292 | + end |
| 293 | + end |
| 294 | + end |
| 295 | +end |
| 296 | + |
0 commit comments