feat: Add environment ID support for hooks - #202
Conversation
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
@cursor review |
| * | ||
| * @param environmentId the environment ID reported by LaunchDarkly | ||
| */ | ||
| default void setEnvironmentId(String environmentId) {} |
There was a problem hiding this comment.
This is an interesting different approach. Because it changes the interface the data sources use instead of changing the interfaces of the sources themselves.
I do see that a downside could be that it makes it a 2 step process instead of a single step.
I also don't like that the environment ID doesn't live alongside the store.
There was a problem hiding this comment.
Agreed on both counts — I went this way because FDv1 has no metadata channel at all (DataStore.init(FullDataSet) carries only data + shouldPersist), so the sink was the one place both generations already meet.
The store-adjacent alternative, mirroring how the selector is handled: TransactionalDataStore already receives the whole change set, so it could expose getEnvironmentId() next to getSelector(), InMemoryDataStore retains what applyFullPayload is already handed, and FDv2DataSystem reads it from the store instead of the sink — one step, and it lives with the data. FDv1 would still need a channel; the least invasive is putting it on FullDataSet alongside shouldPersist so init stays single-step, with FDv1DataSystem reading it back off the store as well.
That's a bigger change to the store interfaces than what's here. Want me to rework it that way?
There was a problem hiding this comment.
I am going to leave it as is until there are more opinions. This code will be deprecated when we remove FDv1 and the FDv2 path should be cleaner.
There was a problem hiding this comment.
Sounds good — leaving it as is.
Requirements
Related issues
Companion to the equivalent work in the other SDKs: launchdarkly/python-server-sdk#484, launchdarkly/cpp-sdks#594, launchdarkly/ruby-server-sdk#414.
Describe the solution you've provided
EvaluationSeriesContextgains anenvironmentIdfield, populated from theX-LD-EnvIDresponse header LaunchDarkly sends on flag delivery responses.The value is latched into
DataSourceUpdatesImpland read back per evaluation through a supplier given toEvaluatorWithHooks, so it reflects the current data system state:Capture points:
DefaultFeatureRequestorrecords the header after the response is confirmed successful;PollingProcessorreports it to the update sink.StreamProcessorreads it from the message event headers.DataSourceUpdatesImpl.applynow latches it, but only once the data has been applied to the store successfully.DataSourceSynchronizerAdapterforwards the ID onto the change sets it synthesizes.Only non-empty values are latched, so a missing/empty header or an error response never clears a previously reported ID, and nothing is exposed before a successful response. The contract test service reports
environmentIdand declareshook-environment-id.Describe alternatives you've considered
Storing the ID as data store metadata (as dotnet-core does) — the change set already carries it here, so latching in the update sink keeps the existing architecture and covers FDv1, which has no change set.
Additional context
Verified against the released harnesses:
hooks/evaluation/provides the environment IDpasses on v2.39.0 (FDv1, default and polling) and v3.2.0-alpha.6 (FDv2). The repo's v3 contract test run is still pinned tov3.0.0-alpha.6, so it won't exercise the new test until that pin is bumped.Track hooks are not implemented in this SDK, so
TrackSeriesContextis unaffected.Link to Devin session: https://app.devin.ai/sessions/bfe54128e2804a96bb100e6120e9a3ef
Requested by: @kinyoklion
Note
Overview
Adds
environmentIdtoEvaluationSeriesContextso evaluation hooks can see which LaunchDarkly environment supplied the flag data. The value comes from theX-LD-EnvIDheader on successful flag delivery responses.The SDK latches a non-empty environment ID in
DataSourceUpdatesImpl(empty/null never clears a prior value).EvaluatorWithHooksreads it per evaluation viadataSystem::getEnvironmentIdand passes it into hookbeforeEvaluation/afterEvaluation.Capture paths: FDv1 polling (
DefaultFeatureRequestor→PollingProcessor), FDv1 streaming (StreamProcessormessage headers), FDv2 (applyon change sets after store success), and FDv1-under-FDv2 fallback (DataSourceSynchronizerAdapteron synthesized change sets). Contract tests declarehook-environment-idand forwardenvironmentIdin hook callbacks.Reviewed by Cursor Bugbot for commit cc24d67. Bugbot is set up for automated code reviews on this repo. Configure here.