Skip to content

Commit 8aedef7

Browse files
authored
chore: Create FDv1 datasystem implementation (#339)
1 parent 5b82f54 commit 8aedef7

File tree

9 files changed

+612
-50
lines changed

9 files changed

+612
-50
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require 'concurrent'
2+
require 'ldclient-rb/interfaces'
3+
4+
module LaunchDarkly
5+
module Impl
6+
module DataSource
7+
#
8+
# A minimal UpdateProcessor implementation used when the SDK is in offline mode
9+
# or daemon (LDD) mode. It does nothing except mark itself as initialized.
10+
#
11+
class NullUpdateProcessor
12+
include LaunchDarkly::Interfaces::DataSource
13+
14+
#
15+
# Creates a new NullUpdateProcessor.
16+
#
17+
def initialize
18+
@ready = Concurrent::Event.new
19+
end
20+
21+
#
22+
# Starts the data source. Since this is a null implementation, it immediately
23+
# sets the ready event to indicate initialization is complete.
24+
#
25+
# @return [Concurrent::Event] The ready event
26+
#
27+
def start
28+
@ready.set
29+
@ready
30+
end
31+
32+
#
33+
# Stops the data source. This is a no-op for the null implementation.
34+
#
35+
# @return [void]
36+
#
37+
def stop
38+
# Nothing to do
39+
end
40+
41+
#
42+
# Checks if the data source has been initialized.
43+
#
44+
# @return [Boolean] Always returns true since this is a null implementation
45+
#
46+
def initialized?
47+
true
48+
end
49+
end
50+
end
51+
end
52+
end
Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,20 @@ module DataSystem
1919
#
2020
# Starts the data system.
2121
#
22-
# This method will return immediately. The provided event will be set when the system
22+
# This method will return immediately. The returned event will be set when the system
2323
# has reached an initial state (either permanently failed, e.g. due to bad auth, or succeeded).
2424
#
25-
# @param ready_event [Concurrent::Event] Event to set when initialization is complete
26-
# @return [void]
25+
# If called multiple times, returns the same event as the first call.
26+
#
27+
# @return [Concurrent::Event] Event that will be set when initialization is complete
2728
#
28-
def start(ready_event)
29+
def start
2930
raise NotImplementedError, "#{self.class} must implement #start"
3031
end
3132

3233
#
3334
# Halts the data system. Should be called when the client is closed to stop any long running
34-
# operations.
35+
# operations. Makes the data system no longer usable.
3536
#
3637
# @return [void]
3738
#
@@ -67,18 +68,23 @@ def data_store_status_provider
6768
end
6869

6970
#
70-
# Returns an interface for tracking changes in feature flag configurations.
71+
# Returns the broadcaster for flag change notifications.
72+
#
73+
# Consumers can use this broadcaster to build their own flag tracker
74+
# or listen for flag changes directly.
7175
#
72-
# @return [LaunchDarkly::Interfaces::FlagTracker]
76+
# @return [LaunchDarkly::Impl::Broadcaster]
7377
#
74-
def flag_tracker
75-
raise NotImplementedError, "#{self.class} must implement #flag_tracker"
78+
def flag_change_broadcaster
79+
raise NotImplementedError, "#{self.class} must implement #flag_change_broadcaster"
7680
end
7781

7882
#
7983
# Indicates what form of data is currently available.
8084
#
81-
# @return [Symbol] One of DataAvailability constants
85+
# This is calculated dynamically based on current system state.
86+
#
87+
# @return [Symbol] one of the {DataAvailability} constants
8288
#
8389
def data_availability
8490
raise NotImplementedError, "#{self.class} must implement #data_availability"
@@ -87,7 +93,7 @@ def data_availability
8793
#
8894
# Indicates the ideal form of data attainable given the current configuration.
8995
#
90-
# @return [Symbol] One of DataAvailability constants
96+
# @return [Symbol] one of the {#DataAvailability} constants
9197
#
9298
def target_availability
9399
raise NotImplementedError, "#{self.class} must implement #target_availability"
@@ -103,18 +109,14 @@ def store
103109
end
104110

105111
#
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+
# Sets the diagnostic accumulator for streaming initialization metrics.
113+
# This should be called before start() to ensure metrics are collected.
112114
#
113-
# @param eval_fn [Proc] The evaluation function
115+
# @param diagnostic_accumulator [DiagnosticAccumulator] The diagnostic accumulator
114116
# @return [void]
115117
#
116-
def set_flag_value_eval_fn(eval_fn)
117-
raise NotImplementedError, "#{self.class} must implement #set_flag_value_eval_fn"
118+
def set_diagnostic_accumulator(diagnostic_accumulator)
119+
raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator"
118120
end
119121

120122
#
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
require 'concurrent'
2+
require 'ldclient-rb/impl/broadcaster'
3+
require 'ldclient-rb/impl/data_source'
4+
require 'ldclient-rb/impl/data_source/null_processor'
5+
require 'ldclient-rb/impl/data_store'
6+
require 'ldclient-rb/impl/data_system'
7+
require 'ldclient-rb/impl/store_client_wrapper'
8+
9+
module LaunchDarkly
10+
module Impl
11+
module DataSystem
12+
#
13+
# FDv1 wires the existing v1 data source and store behavior behind the
14+
# generic DataSystem surface.
15+
#
16+
# @see DataSystem
17+
#
18+
class FDv1
19+
include LaunchDarkly::Impl::DataSystem
20+
21+
#
22+
# Creates a new FDv1 data system.
23+
#
24+
# @param sdk_key [String] The SDK key
25+
# @param config [LaunchDarkly::Config] The SDK configuration
26+
#
27+
def initialize(sdk_key, config)
28+
@sdk_key = sdk_key
29+
@config = config
30+
@shared_executor = Concurrent::SingleThreadExecutor.new
31+
32+
# Set up data store plumbing
33+
@data_store_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
34+
@data_store_update_sink = LaunchDarkly::Impl::DataStore::UpdateSink.new(
35+
@data_store_broadcaster
36+
)
37+
38+
# Wrap the data store with client wrapper (must be created before status provider)
39+
@store_wrapper = LaunchDarkly::Impl::FeatureStoreClientWrapper.new(
40+
@config.feature_store,
41+
@data_store_update_sink,
42+
@config.logger
43+
)
44+
45+
# Create status provider with store wrapper
46+
@data_store_status_provider = LaunchDarkly::Impl::DataStore::StatusProvider.new(
47+
@store_wrapper,
48+
@data_store_update_sink
49+
)
50+
51+
# Set up data source plumbing
52+
@data_source_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
53+
@flag_change_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
54+
@data_source_update_sink = LaunchDarkly::Impl::DataSource::UpdateSink.new(
55+
@store_wrapper,
56+
@data_source_broadcaster,
57+
@flag_change_broadcaster
58+
)
59+
@data_source_status_provider = LaunchDarkly::Impl::DataSource::StatusProvider.new(
60+
@data_source_broadcaster,
61+
@data_source_update_sink
62+
)
63+
64+
# Ensure v1 processors can find the sink via config for status updates
65+
@config.data_source_update_sink = @data_source_update_sink
66+
67+
# Update processor created in start()
68+
@update_processor = nil
69+
70+
# Diagnostic accumulator provided by client for streaming metrics
71+
@diagnostic_accumulator = nil
72+
end
73+
74+
# (see DataSystem#start)
75+
def start
76+
@update_processor ||= make_update_processor
77+
@update_processor.start
78+
end
79+
80+
# (see DataSystem#stop)
81+
def stop
82+
@update_processor&.stop
83+
@shared_executor.shutdown
84+
end
85+
86+
# (see DataSystem#store)
87+
def store
88+
@store_wrapper
89+
end
90+
91+
# (see DataSystem#set_diagnostic_accumulator)
92+
def set_diagnostic_accumulator(diagnostic_accumulator)
93+
@diagnostic_accumulator = diagnostic_accumulator
94+
end
95+
96+
# (see DataSystem#data_source_status_provider)
97+
def data_source_status_provider
98+
@data_source_status_provider
99+
end
100+
101+
# (see DataSystem#data_store_status_provider)
102+
def data_store_status_provider
103+
@data_store_status_provider
104+
end
105+
106+
# (see DataSystem#flag_change_broadcaster)
107+
def flag_change_broadcaster
108+
@flag_change_broadcaster
109+
end
110+
111+
#
112+
# (see DataSystem#data_availability)
113+
#
114+
# In LDD mode, always returns CACHED for backwards compatibility,
115+
# even if the store is empty.
116+
#
117+
def data_availability
118+
return DataAvailability::DEFAULTS if @config.offline?
119+
return DataAvailability::REFRESHED if @update_processor && @update_processor.initialized?
120+
return DataAvailability::CACHED if @store_wrapper.initialized?
121+
122+
DataAvailability::DEFAULTS
123+
end
124+
125+
# (see DataSystem#target_availability)
126+
def target_availability
127+
return DataAvailability::DEFAULTS if @config.offline?
128+
129+
DataAvailability::REFRESHED
130+
end
131+
132+
#
133+
# Creates the appropriate update processor based on the configuration.
134+
#
135+
# @return [Object] The update processor
136+
#
137+
private def make_update_processor
138+
# Handle custom data source (factory or instance)
139+
if @config.data_source
140+
return @config.data_source unless @config.data_source.respond_to?(:call)
141+
142+
# Factory - call with appropriate arity
143+
return @config.data_source.arity == 3 ?
144+
@config.data_source.call(@sdk_key, @config, @diagnostic_accumulator) :
145+
@config.data_source.call(@sdk_key, @config)
146+
end
147+
148+
# Create default data source based on config
149+
return LaunchDarkly::Impl::DataSource::NullUpdateProcessor.new if @config.offline? || @config.use_ldd?
150+
151+
if @config.stream?
152+
require 'ldclient-rb/stream'
153+
return LaunchDarkly::StreamProcessor.new(@sdk_key, @config, @diagnostic_accumulator)
154+
end
155+
156+
# Polling processor
157+
require 'ldclient-rb/polling'
158+
requestor = LaunchDarkly::Requestor.new(@sdk_key, @config)
159+
LaunchDarkly::PollingProcessor.new(@config, requestor)
160+
end
161+
end
162+
end
163+
end
164+
end
165+

lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ module LaunchDarkly
55
module Impl
66
module Integrations
77
module TestData
8-
# @private
98
class TestDataSource
109
include LaunchDarkly::Interfaces::DataSource
1110

lib/ldclient-rb/ldclient.rb

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require "ldclient-rb/impl/broadcaster"
33
require "ldclient-rb/impl/data_source"
44
require "ldclient-rb/impl/data_store"
5+
require "ldclient-rb/impl/data_source/null_processor"
56
require "ldclient-rb/impl/diagnostic_events"
67
require "ldclient-rb/impl/evaluator"
78
require "ldclient-rb/impl/evaluation_with_hook_result"
@@ -132,7 +133,7 @@ def postfork(wait_for_sec = 5)
132133

133134
if @config.use_ldd?
134135
@config.logger.info { "[LDClient] Started LaunchDarkly Client in LDD mode" }
135-
@data_source = NullUpdateProcessor.new
136+
@data_source = LaunchDarkly::Impl::DataSource::NullUpdateProcessor.new
136137
return # requestor and update processor are not used in this mode
137138
end
138139

@@ -710,7 +711,7 @@ def close
710711

711712
def create_default_data_source(sdk_key, config, diagnostic_accumulator)
712713
if config.offline?
713-
return NullUpdateProcessor.new
714+
return LaunchDarkly::Impl::DataSource::NullUpdateProcessor.new
714715
end
715716
raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil? # see LDClient constructor comment on sdk_key
716717
if config.stream?
@@ -877,23 +878,4 @@ def evaluate_internal(key, context, default, with_reasons)
877878
false
878879
end
879880
end
880-
881-
#
882-
# Used internally when the client is offline.
883-
# @private
884-
#
885-
class NullUpdateProcessor
886-
def start
887-
e = Concurrent::Event.new
888-
e.set
889-
e
890-
end
891-
892-
def initialized?
893-
true
894-
end
895-
896-
def stop
897-
end
898-
end
899881
end

0 commit comments

Comments
 (0)