Skip to content

Conversation

aarsilv
Copy link
Contributor

@aarsilv aarsilv commented Aug 13, 2024

Eppo Internal:
🎟️ Ticket: FF-3033 - Update common SDK repository for upstream use by Java SDK
πŸ‘―β€β™€οΈ Related PR: java-server-sdk #70

This update makes this common shared SDK easier to integrate with upstream SDKs. It accomplishes this with a few key changes:

  • Removing the singleton pattern and builder (Upstream SDKs can still use those patterns)
  • Make EppoHttpClient's request callback a public interface, EppoHttpClientRequestCallback
  • Reorganize and exposing testing functionality so they can be used by upstream SDKs as well

I also implemented some additional random cleanup as recommended by the IntelliJ IDE Inspections

withSourcesJar()
}

tasks.register('testJar', Jar) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also providing a testJar artifact so upstream SDKs can use our handy test utilities. Sharing is caring!

// Custom task to ensure we can conditionally publish either a release or snapshot artifact
// based on a command line switch. See github workflow files for more details on usage.
task checkVersion {
tasks.register('checkVersion') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntelliJ said tasks.register() and later on configureEach below is more 1337

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

h4x0r

private static final boolean DEFAULT_IS_GRACEFUL_MODE = true;

protected static final String DEFAULT_HOST = "https://fscdn.eppo.cloud";
protected final ConfigurationRequestor requestor;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected so upstream SDKs can trigger a (re)load

Comment on lines +52 to +61
if (apiKey == null) {
throw new IllegalArgumentException("Unable to initialize Eppo SDK due to missing API key");
}
if (sdkName == null || sdkVersion == null) {
throw new IllegalArgumentException(
"Unable to initialize Eppo SDK due to missing SDK name or version");
}
if (host == null) {
host = DEFAULT_HOST;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved validation to the constructor so each SDK's builder doesn't have to worry about it


EppoHttpClient httpClient = buildHttpClient(host, apiKey, sdkName, sdkVersion);
this.configurationStore = configurationStore;
this.configurationStore = new ConfigurationStore();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we were always just passing in new ConfigurationStore() so in-lining this for now until we handle caching at which point I reckon this may find its way back to the constructor, but didn't want to put the cart before the horse.

private final ObjectMapper mapper =
new ObjectMapper().registerModule(AssignmentTestCase.assignmentTestCaseModule());

private BaseEppoClient eppoClient;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not static because for these tests we often initialize a new client, so we use the two methods below to get a new client for each test as needed


private void initClient(
String host, boolean isGracefulMode, boolean isConfigObfuscated, String apiKey) {
private void initClient(boolean isGracefulMode, boolean isConfigObfuscated) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to pass in host and API key as they are always the same

verify(mockAssignmentLogger, times(1)).logAssignment(assignmentLogCaptor.capture());
}

private void mockHttpResponse(String responseBody) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to a newly-created TestUtils class so it can be used upstream


public void setFileName(String fileName) {
this.fileName = fileName;
private static final ObjectMapper mapper =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below is where I relocated all the useful AssignmentTestCase related utility methods. They are public so they can be used by upstream SDks


public void setFileName(String fileName) {
this.fileName = fileName;
public static Stream<Arguments> getBanditTestData() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here is why I relocated all the BanditTestCase-related utility methods, also public.

Copy link
Collaborator

@typotter typotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. appreciate all the comments to navigate the changes!

private static EppoHttpClient httpClientOverride = null;

private BaseEppoClient(
public BaseEppoClient(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be protected so it's only callable by subclasses?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I had it public as it's used by automated tests, but forgot it will still have package access. Will change!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to making this protected

this.sdkVersion = sdkVersion;
// For now, the configuration is only obfuscated for Android clients
this.isConfigObfuscated = sdkName.toLowerCase().contains("android");
this.isConfigObfuscated = expectObfuscatedConfig;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘

default:
log.debug("Fetch failed with status code: " + response.code());
callback.onFailure("Bad response from URL " + httpUrl);
if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

@typotter typotter assigned aarsilv and unassigned typotter Aug 14, 2024
Copy link
Contributor

@felipecsl felipecsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

private static EppoHttpClient httpClientOverride = null;

private BaseEppoClient(
public BaseEppoClient(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to making this protected

import java.lang.reflect.Field;
import okhttp3.*;

public class TestUtils {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super minor nit: good practice to make "utils"-like classes final to tell users they're not supposed to subclass it

.get(anyString(), any(EppoHttpClientRequestCallback.class));

setBaseClientHttpClientOverrideField(mockHttpClient);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd honestly prefer if we used OkHttp's excellent MockWebServer (https://github.com/square/okhttp/tree/master/mockwebserver) but this is low priority

@aarsilv aarsilv merged commit ee5abd1 into aaron/ff-2710/ufc Aug 15, 2024
@aarsilv aarsilv deleted the aaron/ff-3033/update-for-java-sdk branch August 15, 2024 03:47
aarsilv added a commit that referenced this pull request Aug 15, 2024
* Eppo Client with shared UFC tests passing (#23)

* tests passing for rule evaluator, flag evaluator, and eppo value

* work in progress

* shared UFC tests passing

* don't check in test data

* changes from self-review of PR

* apply spotless linter

* working on tests

* better test logging

* use make test for tests

* Add bandit support (#25)

* bandit test harness ready

* add bandit result

* set up for dropping in bandit evaluation

* bandit deserialization wired up

* loading bandit parameters

* bandit stuff happening

* shared bandit tests passing

* bandit logger classes

* bandit log test passing

* more tests for logger

* bandit tests for graceful mode

* apply spotless formatting autofix

* changes from self-review of PR so far

* more changes from self-review of PR

* more changes from self-review

* make test less fragile

* bump version; don't sign local maven

* bandit logging errors should be non-fatal

* use normalized probability floor

* update result before even attempting to log bandit

* spotless πŸ™„

* do the rename (#26)

* Remove singleton pattern (#28)

* work in progress

* remove singleton for base client

* linter

* expose bandit test harnesses

* expose test uilities

* changes from self-review of PR

* make base client constructor protected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants