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