-
Notifications
You must be signed in to change notification settings - Fork 3
feat: add EppoPrecomputedClient #240
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
base: main
Are you sure you want to change the base?
Conversation
Add the foundational data structures and utilities for the precomputed client feature: - ObfuscationUtils: MD5 hashing for flag key obfuscation - PrecomputedFlag: DTO for precomputed flag assignments - PrecomputedBandit: DTO for precomputed bandit assignments - PrecomputedConfigurationResponse: Wire protocol response parsing - BanditResult: Result container for bandit action lookups - MissingSubjectKeyException: Validation exception Includes comprehensive unit tests for serialization round-trips and MD5 hash consistency.
fa04c22 to
7524fa0
Compare
8f4e45c to
e80e6f9
Compare
7524fa0 to
1abb120
Compare
Add md5HexPrefix() method that only converts the bytes needed for a given prefix length, avoiding unnecessary work when only a prefix is required (e.g., cache file naming uses first 8 chars). Includes unrolled loop for the common 4-byte (8 hex char) case to help compiler optimization, following iOS SDK PR #93 approach.
Extract common file caching functionality from ConfigCacheFile into a new BaseCacheFile base class. This enables reuse for the upcoming precomputed configuration cache without code duplication. - Add BaseCacheFile with common read/write/delete operations - Refactor ConfigCacheFile to extend BaseCacheFile - No functional changes to existing behavior
Add the storage layer for precomputed flag configurations: - PrecomputedCacheFile: Disk cache file extending BaseCacheFile - PrecomputedConfigurationStore: In-memory + disk storage with async save/load operations and proper thread synchronization - Updates in-memory config even if disk write fails for resilience Also adds test data files to Makefile for integration testing. Includes unit tests for cache operations and failure scenarios.
e80e6f9 to
dd5309f
Compare
1abb120 to
f85971f
Compare
- Return null consistently in loadConfigFromCache for both file-not-found and read-error cases - Use static EMPTY singleton in PrecomputedConfigurationResponse.empty() - Use Collections.singletonMap() in getEnvironment() for memory efficiency
Add the main precomputed client implementation with: - Server-side precomputed flag assignments with instant lookups - Support for all flag types: string, boolean, integer, numeric, JSON - Bandit action support with attribute decoding - Builder pattern with extensive configuration options - Offline mode with initial configuration support - Background polling with configurable interval and jitter - Assignment and bandit logging with deduplication caches - Graceful error handling mode The client fetches precomputed assignments from the edge endpoint, eliminating client-side flag evaluation overhead. Includes comprehensive instrumented tests covering: - All flag type assignments - Assignment logging and deduplication - Bandit actions - Offline mode - SDK test data integration
42bc3da to
590804e
Compare
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.
Pull request overview
This PR introduces EppoPrecomputedClient, a new client for Eppo feature flags that receives precomputed assignments from the server rather than performing client-side evaluation. This eliminates evaluation overhead and provides instant flag lookups.
Changes:
- Implements
EppoPrecomputedClientwith builder pattern initialization, assignment methods for all flag types (string, boolean, integer, numeric, JSON), bandit action support, and configurable polling - Adds comprehensive test suite covering offline mode, assignment logging, cache deduplication, type mismatches, and integration with sdk-test-data files
- Supports both online (fetch + poll) and offline modes, graceful/non-graceful error handling, and disk cache persistence with network racing for fast initialization
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| EppoPrecomputedClient.java | Core client implementation with assignment retrieval, HTTP fetching, polling lifecycle, logging, and builder-based initialization |
| EppoPrecomputedClientTest.java | Test suite covering builder validation, flag assignments, logging, caching, and sdk-test-data integration tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
eppo/src/main/java/cloud/eppo/android/EppoPrecomputedClient.java
Outdated
Show resolved
Hide resolved
eppo/src/main/java/cloud/eppo/android/EppoPrecomputedClient.java
Outdated
Show resolved
Hide resolved
eppo/src/main/java/cloud/eppo/android/EppoPrecomputedClient.java
Outdated
Show resolved
Hide resolved
eppo/src/main/java/cloud/eppo/android/EppoPrecomputedClient.java
Outdated
Show resolved
Hide resolved
eppo/src/main/java/cloud/eppo/android/EppoPrecomputedClient.java
Outdated
Show resolved
Hide resolved
- Make polling fields volatile for thread safety - Return user's default value on parse failure instead of hardcoded 0 - Extract magic string to NO_ACTION_CACHE_KEY constant - Fix banditActions serialization to match JS SDK wire format - Add comments for hash length and jitter calculation - Rename misleading test to testNonGracefulModeCanBeConfigured
Extract environment prefix from SDK key to automatically construct the correct edge endpoint URL (e.g., https://5qhpgd.fs-edge-assignment.eppo.cloud). This removes the need for users to manually configure the base URL.
| // No dot separator | ||
| assertNull(Utils.getEnvironmentFromSdkKey("invalidKeyWithoutDot")); | ||
|
|
||
| // Null key |
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.
A nit: would clean up these comments that are stating what is self-evident in the assertions
| metaData.put("obfuscated", "true"); | ||
| metaData.put("sdkLanguage", "android"); | ||
| metaData.put("sdkLibVersion", BuildConfig.EPPO_VERSION); |
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.
Oh interesting, this metaData differs from iOS (which has obfuscated, sdkName, and sdkVersion).
But looks consistent with Android's EppoClient
android-sdk/eppo/src/androidTest/java/cloud/eppo/android/EppoClientTest.java
Lines 1043 to 1045 in 3209265
| expectedMeta.put("obfuscated", "true"); | |
| expectedMeta.put("sdkLanguage", "android"); | |
| expectedMeta.put("sdkLibVersion", BuildConfig.EPPO_VERSION); |
Motivation
The precomputed client provides a new way to use Eppo feature flags where all assignments are computed server-side for a specific subject. This eliminates client-side evaluation overhead and provides instant lookups.
Changes
Client (
EppoPrecomputedClient):Assignment Methods:
getStringAssignment(),getBooleanAssignment(),getIntegerAssignment(),getNumericAssignment(),getJSONAssignment()getBanditAction()- Returns variation + recommended actionInitialization:
Polling:
Logging:
Error Handling:
Tests (
EppoPrecomputedClientTest):Decisions
EppoClientAPIPR Stack
This PR is part of the precomputed client feature, split for easier review:
Why this structure: The client is the largest piece (~1000 lines) but depends on all the infrastructure built in previous PRs. Reviewing it separately allows focus on the client logic without being distracted by DTO definitions or storage implementation.
Merge order: PRs 1, 2, and 3 must be merged first.