-
Notifications
You must be signed in to change notification settings - Fork 74
feat: custom flagging endpoint and assignment download refactoring. #2917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.71% +0.01%
============================================================
Files 834 835 +1
Lines 30380 30388 +8
Branches 5132 5132
============================================================
+ Hits 21476 21486 +10
+ Misses 7444 7440 -4
- Partials 1460 1462 +2
🚀 New features to boost your workflow:
|
...c/main/kotlin/com/datadog/android/flags/internal/net/PrecomputedAssignmentsRequestFactory.kt
Show resolved
Hide resolved
} | ||
|
||
private fun setupOkHttpClient() { | ||
callFactory = OkHttpCallFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a great idea, and thank you for bringing it up here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'll update my code here)
private val requestFactory: PrecomputedAssignmentsRequestFactory | ||
) : FlagsNetworkManager { | ||
|
||
internal lateinit var callFactory: OkHttpCallFactory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense for the noop call factory to return the request it was created with?
yes, this is better, indeed
) | ||
|
||
private fun createSiteConfigObject(dc: String? = null, tld: String? = null): JSONObject = try { | ||
JSONObject().apply { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree with you here. changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, #2922 is an even cleaner approach
...tadog/android/flags/featureflags/internal/repository/net/PrecomputedAssignmentsDownloader.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt
Outdated
Show resolved
Hide resolved
dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt
Outdated
Show resolved
Hide resolved
dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt
Outdated
Show resolved
Hide resolved
} | ||
|
||
val flagsNetworkManager = DefaultFlagsNetworkManager( | ||
val callFactory = sdkCore.createOkHttpCallFactory {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: seems like useCustomFlagEndpoint
call can go on another line. Formatting is off in this call chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems like this line should be removed and there is no actual usage of mockDatadogContext
above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹
...c/test/kotlin/com/datadog/android/flags/internal/net/PrecomputedAssignmentsDownloaderTest.kt
Outdated
Show resolved
Hide resolved
...c/test/kotlin/com/datadog/android/flags/internal/net/PrecomputedAssignmentsDownloaderTest.kt
Outdated
Show resolved
Hide resolved
val request = testedFactory.create(context, flagsContext) | ||
|
||
// Then | ||
assertThat(request).isNotNull |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it actually still the same, the suggested change is not applied here and in the tests below
5d66111
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹
val request = testedFactory.create(context, flagsContext) | ||
|
||
// Then | ||
assertThat(request).isNotNull |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now done
} | ||
|
||
/** @inheritDoc */ | ||
override fun createOkHttpCallFactory(block: okhttp3.OkHttpClient.Builder.() -> Unit): okhttp3.Call.Factory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
|
||
val flagsNetworkManager = DefaultFlagsNetworkManager( | ||
val callFactory = sdkCore.createOkHttpCallFactory {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added to the interface. empty ()
certainly looks more normal here, thx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
PrecomputedAssignmentsRequestFactory
to handle request buildingDefaultFlagsNetworkManager
→PrecomputedAssignmentsDownloader
to focus on request executionMotivation
Problem: The existing
DefaultFlagsNetworkManager
tightly 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
DefaultFlagsNetworkManager
UnknownHostException
,IOException
, etc.)DataOkHttpUploader
error handling3. EndpointsHelper
getFlaggingEndpoint()
,buildEndpointHost()
Additional Notes
Pattern Consistency:
This refactoring aligns the Flags feature with the SDK's standard patterns:
RequestFactory
→DataOkHttpUploader
ExposuresRequestFactory
→DataOkHttpUploader
PrecomputedAssignmentsRequestFactory
→PrecomputedAssignmentsDownloader
Review checklist (to be filled by reviewers)