feat: custom flagging endpoint and assignment download refactoring.#2917
Conversation
…s-enable' into typo/FFL-1112-flagging-proxy-custom-precomputed-assignments-endpoint
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## feature/feature-flagging #2917 +/- ##
============================================================
+ Coverage 70.69% 70.87% +0.18%
============================================================
Files 834 835 +1
Lines 30380 30391 +11
Branches 5132 5132
============================================================
+ Hits 21476 21537 +61
+ Misses 7444 7422 -22
+ Partials 1460 1432 -28
🚀 New features to boost your workflow:
|
| } | ||
|
|
||
| private fun setupOkHttpClient() { | ||
| callFactory = OkHttpCallFactory { |
There was a problem hiding this comment.
Maybe this is not a topic for this PR, but I would like to discuss it anyway.
Up until now we've had only one OkHttpClient in the whole SDK here.
If you create OkHttpClient the way you do here it will create its own thread pool and connection pool which is most likely is a waste of resources for no reason. See this doc for example.
I suggest we do something like this to share the underlying thread pool and connection pool between different OkHttpClients in our SDK.
This also has the benefit that we can add some common settings for all clients (like cache). Not sure that we need it in this particular case though.
WDYT?
There was a problem hiding this comment.
I think that's a great idea, and thank you for bringing it up here!
There was a problem hiding this comment.
Great!
One more question. Do you think that OkHttpClient for the flags feature should have the same configuration as the one we already had in the SDK (link)? Or it should be different?
It of course depends on the backend. As far as I see, the host for flags will be different than for the rest of the SDK (this).
No strong opinion from me here.
There was a problem hiding this comment.
It should have the same configuration given Flags are supposed to work in GovCloud as well, which puts restrictions on the set of TLS suites allowed for the FIPS compliance.
There was a problem hiding this comment.
just to note, Flags is not currently supported in GovCloud, (the intent is for it to be, but we've not scoped a GovCloud deploy yet).
I think it's useful to use the same existing configuration and just allow certain overrides in the FlagsClient on parameters like timeout and number of retries.
We're not speccing any customization in the API at current, so I think copying the existing configuration makes the most sense, especially given it is already hardened
There was a problem hiding this comment.
(I'll update my code here)
| private val requestFactory: PrecomputedAssignmentsRequestFactory | ||
| ) : FlagsNetworkManager { | ||
|
|
||
| internal lateinit var callFactory: OkHttpCallFactory |
There was a problem hiding this comment.
internal lateinit var callFactory: OkHttpCallFactory -> private val callFactory: OkHttpCallFactory
f34c5b8 to
b4f552b
Compare
| } | ||
|
|
||
| override fun createOkHttpCallFactory(block: okhttp3.OkHttpClient.Builder.() -> Unit): okhttp3.Call.Factory { | ||
| return okhttp3.Call.Factory { request -> |
There was a problem hiding this comment.
This isn't actually a "no-op" implementation. It will create a real OkHttpClient. You have to create a NoOpCallFactory.
Here it is:
object NoOpCallFactory: Call.Factory {
object NoOpCall: Call {
override fun cancel() {
}
override fun clone(): Call {
return this
}
override fun enqueue(responseCallback: Callback) {
}
override fun execute(): Response {
return Response.Builder().build()
}
override fun isCanceled(): Boolean {
return false
}
override fun isExecuted(): Boolean {
return false
}
override fun request(): Request {
return Request.Builder().build()
}
override fun timeout(): Timeout {
return Timeout.NONE
}
}
override fun newCall(request: Request): Call {
return NoOpCall
}
}
There was a problem hiding this comment.
thanks!
Would it make sense for the noop call factory to return the request it was created with?
There was a problem hiding this comment.
Would it make sense for the noop call factory to return the request it was created with?
yes, this is better, indeed
There was a problem hiding this comment.
Why are we using JSON here? You can create something like data class SiteConfig(val dc: String, val tld: String).
And siteConfig can become Map<DatadogSite, SiteConfig>.
There was a problem hiding this comment.
agree with you here. changed
There was a problem hiding this comment.
actually, #2922 is an even cleaner approach
0xnm
left a comment
There was a problem hiding this comment.
lgtm, but I left some non-blocking suggestions
| } | ||
|
|
||
| /** @inheritDoc */ | ||
| override fun createOkHttpCallFactory(block: okhttp3.OkHttpClient.Builder.() -> Unit): okhttp3.Call.Factory { |
There was a problem hiding this comment.
minor: okhttp3 should go to import
| override fun createOkHttpCallFactory(block: okhttp3.OkHttpClient.Builder.() -> Unit): okhttp3.Call.Factory { | |
| override fun createOkHttpCallFactory(block: OkHttpClient.Builder.() -> Unit): Call.Factory { |
| } | ||
|
|
||
| val flagsNetworkManager = DefaultFlagsNetworkManager( | ||
| val callFactory = sdkCore.createOkHttpCallFactory {} |
There was a problem hiding this comment.
minor: maybe it is worth to use default lambda argument in the createOkHttpCallFactory signature? Otherwise such call with empty lambda looks a bit strange.
There was a problem hiding this comment.
added to the interface. empty () certainly looks more normal here, thx
| builder.trackExposures(fakeTrackExposuresState) | ||
| builder.trackExposures( | ||
| fakeTrackExposuresState | ||
| ).useCustomExposureEndpoint(fakeCustomExposureEndpoint).useCustomFlagEndpoint(fakeCustomFlagEndpoint) |
There was a problem hiding this comment.
minor: seems like useCustomFlagEndpoint call can go on another line. Formatting is off in this call chain.
| fun `set up`() { | ||
| whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger | ||
| whenever(mockSdkCore.createSingleThreadExecutorService(any())) doReturn mockExecutorService | ||
| // whenever(mockSdkCore.getDatadogContext()) doReturn mockDatadogContext |
There was a problem hiding this comment.
seems like this line should be removed and there is no actual usage of mockDatadogContext above?
| val request = testedFactory.create(context, flagsContext) | ||
|
|
||
| // Then | ||
| assertThat(request).isNotNull |
There was a problem hiding this comment.
it actually still the same, the suggested change is not applied here and in the tests below
5d66111
typotter
left a comment
There was a problem hiding this comment.
Thanks again for the review. Sorry for missing that handful of fixes.
The last commit to address comments requires another stamp. PTAL
| fun `set up`() { | ||
| whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger | ||
| whenever(mockSdkCore.createSingleThreadExecutorService(any())) doReturn mockExecutorService | ||
| // whenever(mockSdkCore.getDatadogContext()) doReturn mockDatadogContext |
| val request = testedFactory.create(context, flagsContext) | ||
|
|
||
| // Then | ||
| assertThat(request).isNotNull |
| } | ||
|
|
||
| /** @inheritDoc */ | ||
| override fun createOkHttpCallFactory(block: okhttp3.OkHttpClient.Builder.() -> Unit): okhttp3.Call.Factory { |
| } | ||
|
|
||
| val flagsNetworkManager = DefaultFlagsNetworkManager( | ||
| val callFactory = sdkCore.createOkHttpCallFactory {} |
There was a problem hiding this comment.
added to the interface. empty () certainly looks more normal here, thx
There was a problem hiding this comment.
fixed; I'll connect w/Jonathan on verifying my ktlint config
|
/merge |
|
View all feedbacks in Devflow UI.
This merge request is not mergeable according to GitHub. Common reasons include pending required checks, missing approvals, or merge conflicts — but it could also be blocked by other repository rules or settings.
The expected merge time in
|
What does this PR do?
This PR refactors the precomputed assignments request mechanism to follow the SDK-wide Factory + Executor pattern, decoupling request construction from execution. The main changes include:
PrecomputedAssignmentsRequestFactoryto handle request buildingDefaultFlagsNetworkManager→PrecomputedAssignmentsDownloaderto focus on request executionMotivation
Problem: The existing
DefaultFlagsNetworkManagertightly coupled request construction (building headers, body, URL) with request execution (network calls, error handling), making it difficult to test request construction independently and inconsistent with patterns used throughout the SDK.Goals:
DataOkHttpUploader,ExposuresRequestFactory, and other featuresArchitecture Changes
Before:
After (Factory + Executor Pattern):
Key Components
1. PrecomputedAssignmentsRequestFactory
EndpointsHelper(supports custom endpoints)2. PrecomputedAssignmentsDownloader
DefaultFlagsNetworkManagerUnknownHostException,IOException, etc.)DataOkHttpUploadererror handling3. EndpointsHelper
getFlaggingEndpoint(),buildEndpointHost()Additional Notes
Pattern Consistency:
This refactoring aligns the Flags feature with the SDK's standard patterns:
RequestFactory→DataOkHttpUploaderExposuresRequestFactory→DataOkHttpUploaderPrecomputedAssignmentsRequestFactory→PrecomputedAssignmentsDownloaderReview checklist (to be filled by reviewers)