Skip to content

Conversation

@typotter
Copy link
Collaborator

Summary

Implement the complete foundation layer combining data objects, storage, and network. This PR provides all the building blocks needed for the SDK but does not include evaluation logic or the public client implementation yet.

What's Included

This PR combines Steps 1-3 from the implementation plan:

Step 1: Data Objects & Parsing

  • ✅ Model classes (ResolutionDetails, BanditResult, enums)
  • ✅ DTO models with @Serializable for JSON parsing
  • EppoPrecomputedConfig with Builder pattern and validation
  • ✅ Exception hierarchy (5 exception classes)
  • ✅ Utilities: Base64 (NO_WRAP), TypeConverter

Step 2: Storage Layer

  • FlagsState - Immutable state container
  • DecodedPrecomputedFlag/Bandit - Domain models
  • FlagsRepository - Thread-safe storage with StateFlow + DataStore
  • FlagsStateSerializer - Persistence serialization
  • AssignmentCache/BanditCache - LRU caches for deduplication

Step 3: Network Layer

  • PrecomputedRequestor - Coroutine-based HTTP client
  • RequestFactory - Builds POST requests to /api/v1/precomputed-flags
  • OkHttpExtensions - Call.await() with proper cancellation
  • Md5 - Hashing for flag key obfuscation

Files Added

21 source files:

  • 4 public models
  • 5 network DTOs
  • 5 storage/repository files
  • 3 utility files
  • 1 config file
  • 1 exception file
  • 2 decoded models

6 test files (66 test cases):

  • Base64ExtensionsTest.kt - 10 tests
  • Md5Test.kt - 11 tests
  • OkHttpExtensionsTest.kt - 7 tests
  • RequestFactoryTest.kt - 10 tests
  • PrecomputedRequestorTest.kt - 10 tests
  • FlagsRepositoryTest.kt - 18 tests

Key Features

Thread-Safe Storage:

// Lock-free reads via StateFlow
val flag = repository.getFlag(hashedKey)

// Atomic updates with Mutex
repository.updateFlags(...) // Readers see old or new, never partial

Type-Safe JSON:

@Serializable
data class PrecomputedFlagsResponse(...)

val response = json.decodeFromString<PrecomputedFlagsResponse>(body)

Coroutine HTTP:

suspend fun fetchPrecomputedFlags(...): Result<PrecomputedFlagsResponse> =
    withContext(Dispatchers.IO) {
        runCatching {
            httpClient.newCall(request).await()
        }
    }

Code Review Fixes Applied

  • ✅ OkHttp: Removed deprecated continuation.isCancelled checks
  • ✅ Base64: Using Base64.NO_WRAP flag (no newlines)
  • ✅ Builder: Added validation for banditActions
  • ✅ Java Interop: Proper handling of sdk-common-jvm types

Build Verification

$ ./gradlew :eppo-kotlin:assemble
BUILD SUCCESSFUL ✅

$ ./gradlew :eppo-kotlin:compileDebugUnitTestKotlin
BUILD SUCCESSFUL ✅

What's NOT in This PR

  • ❌ Evaluation logic (FlagEvaluator, BanditEvaluator) - PR 2
  • ❌ Public client implementation (DefaultPrecomputedClient) - PR 3
  • ❌ Factory (Eppo.createPrecomputedClient) - PR 3
  • ❌ Polling (PollingManager) - PR 4

This PR provides the foundation but you cannot yet create a working client or evaluate flags.

Test Plan

# All utilities work
./gradlew :eppo-kotlin:test --tests "cloud.eppo.kotlin.internal.util.*"

# Network layer works
./gradlew :eppo-kotlin:test --tests "cloud.eppo.kotlin.internal.network.*"

# Storage works
./gradlew :eppo-kotlin:test --tests "cloud.eppo.kotlin.internal.repository.*"

Next PR

PR 2 will add the evaluation layer (FlagEvaluator, BanditEvaluator, assignment logging).

🤖 Generated with Claude Code

Implement complete foundation combining data objects, storage, and network:

**Step 1 - Data Objects & Parsing:**
- Add model classes (ResolutionDetails, BanditResult, enums)
- Add DTO models with @serializable for JSON (Request/Response/Flag/Bandit)
- Add EppoPrecomputedConfig with Builder pattern and validation
- Add exception hierarchy (5 exception classes)
- Add Base64 encode/decode with NO_WRAP flag
- Add TypeConverter for Base64→typed values

**Step 2 - Storage Layer:**
- Add FlagsState (immutable state container)
- Add DecodedPrecomputedFlag/Bandit models
- Add FlagsRepository with StateFlow (lock-free reads) + DataStore (persistence)
- Add FlagsStateSerializer for disk persistence
- Add AssignmentCache/BanditCache (LRU deduplication)
- Thread-safe with Mutex for writes, atomic state updates

**Step 3 - Network Layer:**
- Add PrecomputedRequestor (coroutine HTTP client)
- Add RequestFactory (builds POST requests with JSON)
- Add OkHttpExtensions (Call.await() with proper cancellation)
- Add Md5 hashing for flag key obfuscation

**Tests:** 66 test cases across utils, network, and repository layers

All code includes fixes from code review:
- OkHttp: removed deprecated continuation.isCancelled
- Base64: using NO_WRAP flag
- Builder: banditActions validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <[email 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.

2 participants