Skip to content

Commit ee5abd1

Browse files
authored
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
1 parent 5d0ebd3 commit ee5abd1

11 files changed

+410
-475
lines changed

build.gradle

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,17 @@ java {
6262
withSourcesJar()
6363
}
6464

65+
tasks.register('testJar', Jar) {
66+
archiveClassifier.set('tests')
67+
from sourceSets.test.output
68+
}
69+
6570
publishing {
6671
publications {
6772
mavenJava(MavenPublication) {
6873
artifactId = 'sdk-common-jvm'
6974
from components.java
75+
artifact testJar // Include the test-jar in the published artifacts
7076
versionMapping {
7177
usage('java-api') {
7278
fromResolutionOf('runtimeClasspath')
@@ -114,7 +120,7 @@ publishing {
114120

115121
// Custom task to ensure we can conditionally publish either a release or snapshot artifact
116122
// based on a command line switch. See github workflow files for more details on usage.
117-
task checkVersion {
123+
tasks.register('checkVersion') {
118124
doLast {
119125
if (!project.hasProperty('release') && !project.hasProperty('snapshot')) {
120126
throw new GradleException("You must specify either -Prelease or -Psnapshot")
@@ -135,7 +141,7 @@ tasks.named('publish').configure {
135141
}
136142

137143
// Conditionally enable or disable publishing tasks
138-
tasks.withType(PublishToMavenRepository) {
144+
tasks.withType(PublishToMavenRepository).configureEach {
139145
onlyIf {
140146
project.ext.has('shouldPublish') && project.ext.shouldPublish
141147
}

src/main/java/cloud/eppo/BaseEppoClient.java

Lines changed: 22 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,46 @@ public class BaseEppoClient {
2222
private final ObjectMapper mapper =
2323
new ObjectMapper()
2424
.registerModule(EppoModule.eppoModule()); // TODO: is this the best place for this?
25-
private static final String DEFAULT_HOST = "https://fscdn.eppo.cloud";
26-
private static final boolean DEFAULT_IS_GRACEFUL_MODE = true;
25+
26+
protected static final String DEFAULT_HOST = "https://fscdn.eppo.cloud";
27+
protected final ConfigurationRequestor requestor;
2728

2829
private final ConfigurationStore configurationStore;
29-
private final ConfigurationRequestor requestor;
3030
private final AssignmentLogger assignmentLogger;
3131
private final BanditLogger banditLogger;
3232
private final String sdkName;
3333
private final String sdkVersion;
3434
private final boolean isConfigObfuscated;
3535
private boolean isGracefulMode;
3636

37-
private static BaseEppoClient instance;
38-
3937
// Fields useful for testing in situations where we want to mock the http client or configuration
4038
// store (accessed via reflection)
4139
/** @noinspection FieldMayBeFinal */
4240
private static EppoHttpClient httpClientOverride = null;
4341

44-
private BaseEppoClient(
42+
protected BaseEppoClient(
4543
String apiKey,
4644
String sdkName,
4745
String sdkVersion,
4846
String host,
49-
ConfigurationStore configurationStore,
5047
AssignmentLogger assignmentLogger,
5148
BanditLogger banditLogger,
52-
boolean isGracefulMode) {
49+
boolean isGracefulMode,
50+
boolean expectObfuscatedConfig) {
51+
52+
if (apiKey == null) {
53+
throw new IllegalArgumentException("Unable to initialize Eppo SDK due to missing API key");
54+
}
55+
if (sdkName == null || sdkVersion == null) {
56+
throw new IllegalArgumentException(
57+
"Unable to initialize Eppo SDK due to missing SDK name or version");
58+
}
59+
if (host == null) {
60+
host = DEFAULT_HOST;
61+
}
5362

5463
EppoHttpClient httpClient = buildHttpClient(host, apiKey, sdkName, sdkVersion);
55-
this.configurationStore = configurationStore;
64+
this.configurationStore = new ConfigurationStore();
5665
requestor = new ConfigurationRequestor(configurationStore, httpClient);
5766
this.assignmentLogger = assignmentLogger;
5867
this.banditLogger = banditLogger;
@@ -61,8 +70,10 @@ private BaseEppoClient(
6170
this.sdkName = sdkName;
6271
this.sdkVersion = sdkVersion;
6372
// For now, the configuration is only obfuscated for Android clients
64-
this.isConfigObfuscated = sdkName.toLowerCase().contains("android");
73+
this.isConfigObfuscated = expectObfuscatedConfig;
74+
6575
// TODO: caching initialization (such as setting an API-key-specific prefix
76+
// will probably involve passing in configurationStore to the constructor
6677
}
6778

6879
private EppoHttpClient buildHttpClient(
@@ -78,51 +89,7 @@ private EppoHttpClient buildHttpClient(
7889
return httpClient;
7990
}
8091

81-
public static BaseEppoClient init(
82-
String apiKey,
83-
String sdkName,
84-
String sdkVersion,
85-
String host,
86-
AssignmentLogger assignmentLogger,
87-
BanditLogger banditLogger,
88-
boolean isGracefulMode) {
89-
90-
if (apiKey == null) {
91-
throw new IllegalArgumentException("Unable to initialize Eppo SDK due to missing API key");
92-
}
93-
if (sdkName == null || sdkVersion == null) {
94-
throw new IllegalArgumentException(
95-
"Unable to initialize Eppo SDK due to missing SDK name or version");
96-
}
97-
98-
if (instance != null) {
99-
// TODO: also check we're not running a test
100-
log.warn("Reinitializing an Eppo Client instance that was already initialized");
101-
}
102-
instance =
103-
new BaseEppoClient(
104-
apiKey,
105-
sdkName,
106-
sdkVersion,
107-
host,
108-
new ConfigurationStore(),
109-
assignmentLogger,
110-
banditLogger,
111-
isGracefulMode);
112-
instance.refreshConfiguration();
113-
114-
return instance;
115-
}
116-
117-
/**
118-
* Ability to ad-hoc kick off a configuration load. Will load from a filesystem cached file as
119-
* well as fire off an HTTPS request for an updated configuration. If the cache load finishes
120-
* first, those assignments will be used until the fetch completes.
121-
*
122-
* <p>Deprecated, as we plan to make a more targeted and configurable way to do so in the future.
123-
*/
124-
@Deprecated
125-
public void refreshConfiguration() {
92+
protected void loadConfiguration() {
12693
requestor.load();
12794
}
12895

@@ -479,65 +446,7 @@ private <T> T throwIfNotGraceful(Exception e, T defaultValue) {
479446
throw new RuntimeException(e);
480447
}
481448

482-
public static BaseEppoClient getInstance() {
483-
if (BaseEppoClient.instance == null) {
484-
throw new IllegalStateException("Eppo SDK has not been initialized");
485-
}
486-
487-
return BaseEppoClient.instance;
488-
}
489-
490449
public void setIsGracefulFailureMode(boolean isGracefulFailureMode) {
491450
this.isGracefulMode = isGracefulFailureMode;
492451
}
493-
494-
public static class Builder {
495-
private String apiKey;
496-
private String sdkName;
497-
private String sdkVersion;
498-
private String host = DEFAULT_HOST;
499-
private AssignmentLogger assignmentLogger;
500-
private BanditLogger banditLogger;
501-
private boolean isGracefulMode = DEFAULT_IS_GRACEFUL_MODE;
502-
503-
public Builder apiKey(String apiKey) {
504-
this.apiKey = apiKey;
505-
return this;
506-
}
507-
508-
public Builder sdkName(String sdkName) {
509-
this.sdkName = sdkName;
510-
return this;
511-
}
512-
513-
public Builder sdkVersion(String sdkVersion) {
514-
this.sdkVersion = sdkVersion;
515-
return this;
516-
}
517-
518-
public Builder host(String host) {
519-
this.host = host;
520-
return this;
521-
}
522-
523-
public Builder assignmentLogger(AssignmentLogger assignmentLogger) {
524-
this.assignmentLogger = assignmentLogger;
525-
return this;
526-
}
527-
528-
public Builder banditLogger(BanditLogger banditLogger) {
529-
this.banditLogger = banditLogger;
530-
return this;
531-
}
532-
533-
public Builder isGracefulMode(boolean isGracefulMode) {
534-
this.isGracefulMode = isGracefulMode;
535-
return this;
536-
}
537-
538-
public BaseEppoClient buildAndInit() {
539-
return BaseEppoClient.init(
540-
apiKey, sdkName, sdkVersion, host, assignmentLogger, banditLogger, isGracefulMode);
541-
}
542-
}
543452
}

src/main/java/cloud/eppo/EppoHttpClient.java

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public Response get(String path) {
5959
}
6060
}
6161

62-
public void get(String path, RequestCallback callback) {
62+
public void get(String path, EppoHttpClientRequestCallback callback) {
6363
HttpUrl httpUrl =
6464
HttpUrl.parse(baseUrl + path)
6565
.newBuilder()
@@ -83,13 +83,11 @@ public void onResponse(Call call, Response response) {
8383
callback.onFailure("Failed to read response from URL " + httpUrl);
8484
}
8585
} else {
86-
switch (response.code()) {
87-
case HttpURLConnection.HTTP_FORBIDDEN:
88-
callback.onFailure("Invalid API key");
89-
break;
90-
default:
91-
log.debug("Fetch failed with status code: " + response.code());
92-
callback.onFailure("Bad response from URL " + httpUrl);
86+
if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
87+
callback.onFailure("Invalid API key");
88+
} else {
89+
log.debug("Fetch failed with status code: {}", response.code());
90+
callback.onFailure("Bad response from URL " + httpUrl);
9391
}
9492
}
9593
response.close();
@@ -98,19 +96,12 @@ public void onResponse(Call call, Response response) {
9896
@Override
9997
public void onFailure(Call call, IOException e) {
10098
log.error(
101-
"Http request failure: "
102-
+ e.getMessage()
103-
+ " "
104-
+ Arrays.toString(e.getStackTrace()),
99+
"Http request failure: {} {}",
100+
e.getMessage(),
101+
Arrays.toString(e.getStackTrace()),
105102
e);
106103
callback.onFailure("Unable to fetch from URL " + httpUrl);
107104
}
108105
});
109106
}
110107
}
111-
112-
interface RequestCallback {
113-
void onSuccess(String responseBody);
114-
115-
void onFailure(String errorMessage);
116-
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package cloud.eppo;
2+
3+
public interface EppoHttpClientRequestCallback {
4+
void onSuccess(String responseBody);
5+
6+
void onFailure(String errorMessage);
7+
}

src/main/java/cloud/eppo/ufc/dto/DiscriminableAttributes.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
public interface DiscriminableAttributes {
44

5-
public Attributes getNumericAttributes();
5+
Attributes getNumericAttributes();
66

7-
public Attributes getCategoricalAttributes();
7+
Attributes getCategoricalAttributes();
88

9-
public Attributes getAllAttributes();
9+
Attributes getAllAttributes();
1010
}

0 commit comments

Comments
 (0)