Skip to content

Commit 5b82f54

Browse files
authored
chore: Add DataSystem contract definition (#338)
1 parent 498b99d commit 5b82f54

File tree

2 files changed

+487
-0
lines changed

2 files changed

+487
-0
lines changed

lib/ldclient-rb/impl/datasystem.rb

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

Comments
 (0)