Skip to content

Commit d908f5e

Browse files
committed
feat: Add datasystem contract definition
1 parent 498b99d commit d908f5e

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed

lib/ldclient-rb/impl/datasystem.rb

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
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

Comments
 (0)