Skip to content

Commit 91ef701

Browse files
committed
Merge continued-modularization (includes mod_baseline)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> AI-Session-Id: e7f88852-4cb2-46f4-bd22-c7d891d5649c AI-Tool: claude-code AI-Model: unknown
2 parents e1e4f0a + 67cb73e commit 91ef701

25 files changed

Lines changed: 1727 additions & 0 deletions

File tree

.github/App

Whitespace-only changes.

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"java.compile.nullAnalysis.mode": "automatic"
3+
}

AGENTS.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# AGENTS.md — Split Java SDK
2+
3+
## Project Overview
4+
5+
Java SDK for [Split](https://www.split.io) — a Feature Delivery Platform. The SDK evaluates feature flags via the Split API, supporting both streaming (SSE) and polling synchronization modes, with optional pluggable storage backends (e.g., Redis).
6+
7+
**Modules:**
8+
- `client/` — Core SDK (344+ Java files). Public API + evaluation engine.
9+
- `pluggable-storage/``CustomStorageWrapper` interface for custom storage backends.
10+
- `redis-wrapper/` — Redis implementation of `CustomStorageWrapper`.
11+
- `okhttp-modules/` — Optional OkHttp HTTP client (alternative to Apache).
12+
- `testing/` — Published test-support library (`SplitClientForTest`, JUnit 4 runner).
13+
14+
## Build System
15+
16+
- **Build tool**: Maven (multi-module parent POM at root)
17+
- **Java compatibility**: Java 8 (compiled with `-source 1.8 -target 1.8`)
18+
- **Build all modules**: `mvn --batch-mode clean install`
19+
- **Build single module**: `mvn -pl :client clean install -am`
20+
- **Skip tests**: `mvn clean install -DskipTests`
21+
22+
## Testing
23+
24+
- **Run all tests**: `mvn --batch-mode clean install`
25+
- **Run tests in one module**: `mvn -pl :client test`
26+
- **Run single test class**: `mvn -pl :client -Dtest=MyClassTest test`
27+
- **Test framework**: JUnit 4 (`junit:junit:4.13.1`)
28+
- **Note**: Tests in `client/` require a running Redis instance on `localhost:6379` for integration tests.
29+
30+
## Linting & Formatting
31+
32+
- **Lint check**: `mvn checkstyle:check`
33+
- **Style guide**: Google Java Style (`/.github/linter/google-java-style.xml`)
34+
- **Suppressions**: `/.github/linter/checkstyle-suppressions.xml`
35+
- **Runs automatically**: On every PR in CI for JDK 8 builds.
36+
37+
## Git Workflow
38+
39+
- **Default branch**: `master`
40+
- **Integration branch**: `development` (PRs target `development`, not `master`)
41+
- **Branch naming**: `FME-1234-short-description` (Jira ticket prefix + description)
42+
- **PR title format**: Match branch name or describe the change with Jira ticket
43+
44+
## DOs
45+
46+
- Always run `mvn checkstyle:check` before committing.
47+
- Follow the naming convention: interface `Foo`, implementation `FooImp` or `FooImpl`.
48+
- Keep DTOs in `client/dtos/` as pure wire-format classes — no business logic.
49+
- Call input validation via `inputValidation/` package before any public SDK operation.
50+
- Target PRs to the `development` branch, not `master`.
51+
- Match existing code style (Google Java Style).
52+
53+
## DON'Ts
54+
55+
- Never force push to `master` or `development`.
56+
- Never commit secrets, `.env` files, or credentials.
57+
- Never run `git commit --no-verify` (never skip hooks).
58+
- Never add business logic to DTO classes in `client/src/main/java/io/split/client/dtos/`.
59+
- Never add validation logic directly in `SplitClientImpl` — use `inputValidation/` package.
60+
- Never take runtime dependencies from the `testing/` module on `client/` internals.
61+
62+
## Commands to Never Run
63+
64+
- `git push --force origin master`
65+
- `git push --force origin development`
66+
- `git commit --no-verify` or `git push --no-verify`
67+
- `rm -rf` on any project directory without confirmation
68+
69+
## Project Structure
70+
71+
```
72+
java-client/
73+
├── client/ # Core SDK module (main artifact)
74+
│ └── src/main/java/io/split/
75+
│ ├── client/ # Public API: SplitClient, SplitFactory, SplitClientConfig
76+
│ │ ├── impressions/ # Impression tracking (3 modes: DEBUG/OPTIMIZED/NONE)
77+
│ │ ├── dtos/ # Wire-format DTOs (51 classes, no business logic)
78+
│ │ ├── events/ # Event tracking
79+
│ │ └── api/ # SplitView and other public value objects
80+
│ ├── engine/ # Evaluation engine (most complex package)
81+
│ │ ├── evaluator/ # Core treatment resolution
82+
│ │ ├── experiments/ # ParsedSplit domain model + SplitFetcher
83+
│ │ ├── matchers/ # All matcher implementations (29 classes)
84+
│ │ ├── sse/ # SSE streaming subsystem (38 classes)
85+
│ │ ├── common/ # SyncManager: polling-vs-streaming orchestration
86+
│ │ └── segments/ # Segment sync tasks
87+
│ ├── storages/ # Storage abstraction (Consumer/Producer split)
88+
│ │ ├── memory/ # In-memory implementations
89+
│ │ └── pluggable/ # Adapters for CustomStorageWrapper backends
90+
│ ├── telemetry/ # SDK internal observability (not user-facing events)
91+
│ │ ├── domain/ # 23 domain classes for telemetry data
92+
│ │ ├── storage/ # TelemetryProducer/Consumer interfaces + impls
93+
│ │ └── synchronizer/ # Periodic telemetry flush to Split API
94+
│ ├── inputValidation/ # Defensive validators for all public API calls
95+
│ ├── grammar/ # Treatments constants (Treatments.java)
96+
│ ├── integrations/ # Third-party integrations (impression listeners)
97+
│ └── service/ # HTTP service layer
98+
├── pluggable-storage/ # CustomStorageWrapper contract (5 files)
99+
├── redis-wrapper/ # Redis storage impl (single + cluster + pipeline)
100+
├── okhttp-modules/ # Optional OkHttp HTTP client + Kerberos auth
101+
├── testing/ # Published test-support library (SplitClientForTest)
102+
└── pom.xml # Parent POM — manages versions and shared plugins
103+
```
104+
105+
## Important Packages
106+
107+
Based on commit activity and architectural importance:
108+
109+
| Package/Module | Role |
110+
|----------------|------|
111+
| `client/src/main/java/io/split/engine/` | Evaluation brain — the most algorithmically complex area |
112+
| `client/src/main/java/io/split/client/impressions/` | Impression tracking — wrong changes cause data loss |
113+
| `client/src/main/java/io/split/storages/` | Storage abstraction — Consumer/Producer interface hierarchy |
114+
| `client/src/main/java/io/split/telemetry/` | SDK observability — mirrors storage's producer/consumer pattern |
115+
| `pluggable-storage/` | Extension point for custom storage backends |
116+
| `redis-wrapper/` | Reference implementation of `CustomStorageWrapper` |
117+
118+
## Language-Specific Guidelines
119+
120+
This is a Java project. Invoke the `maf:java-conventions` skill for Java-specific patterns, build commands, and testing conventions.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

client/AGENTS.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# AGENTS.md — client/ module
2+
3+
## Purpose
4+
5+
The `client/` module is the **core artifact** of the Split Java SDK. It contains the public API surface, evaluation engine, storage abstraction, impression tracking, telemetry, and all synchronization logic. This module is what SDK consumers depend on.
6+
7+
## Key Files
8+
9+
| File | Purpose |
10+
|------|---------|
11+
| `src/main/java/io/split/client/SplitClient.java` | Public interface for feature flag evaluation |
12+
| `src/main/java/io/split/client/SplitManager.java` | Public interface for listing feature flags |
13+
| `src/main/java/io/split/client/SplitFactory.java` | Public factory interface |
14+
| `src/main/java/io/split/client/SplitClientImpl.java` | Primary `SplitClient` implementation |
15+
| `src/main/java/io/split/client/SplitFactoryImpl.java` | Wiring root / DI composition root for the entire SDK (~850 lines) |
16+
| `src/main/java/io/split/client/SplitClientConfig.java` | All SDK configuration options, immutable after construction via builder (~1280 lines) |
17+
| `src/main/java/io/split/client/LocalhostSplitManager.java` | Offline/localhost mode implementation |
18+
19+
## Naming Conventions
20+
21+
- Interface: `Foo`
22+
- Implementation: `FooImp` or `FooImpl` (both patterns used — prefer `FooImp` for consistency with existing code)
23+
- Private fields: `_fieldName` (underscore prefix)
24+
- Thread safety: internal caches use `ConcurrentHashMap` or atomic references
25+
26+
## Internal Package Map
27+
28+
```
29+
io.split/
30+
├── client/ # Public API + localhost implementations + HTTP fetchers
31+
│ ├── impressions/ # Impression tracking (see impressions/AGENTS.md)
32+
│ ├── dtos/ # Wire-format DTOs from Split API — NO business logic here
33+
│ ├── events/ # Track() call handling and event flushing
34+
│ ├── api/ # Public value objects (SplitView, etc.)
35+
│ └── jmx/ # JMX monitoring beans
36+
├── engine/ # Evaluation engine (see engine/AGENTS.md)
37+
├── storages/ # Storage abstraction (see storages/AGENTS.md)
38+
├── telemetry/ # SDK observability (see telemetry/AGENTS.md)
39+
├── inputValidation/ # Defensive validators — add validation here, not in SplitClientImpl
40+
├── grammar/ # Treatments.java — constants only
41+
├── integrations/ # ImpressionListener and third-party integration hooks
42+
└── service/ # HTTP client wrappers and Split API endpoints
43+
```
44+
45+
## DTOs vs Domain Objects (Critical Distinction)
46+
47+
- `client/dtos/` classes (e.g., `Split`, `Condition`, `Matcher`) are **wire-format** objects deserialized from the Split API.
48+
- `engine/experiments/` classes (e.g., `ParsedSplit`, `ParsedCondition`) are **domain objects** created by parsing DTOs.
49+
- **Never add business logic to DTOs.** Always parse into domain objects first.
50+
51+
## Testing
52+
53+
- **Run all tests**: `mvn -pl :client test` (from project root)
54+
- **Run single test**: `mvn -pl :client -Dtest=MyClassTest test`
55+
- **Note**: Integration tests require Redis on `localhost:6379`
56+
- **Test directory**: `src/test/java/io/split/`
57+
- **Test file pattern**: `*Test.java`
58+
59+
## Dependencies
60+
61+
- **Key external**: `com.google.guava`, `com.google.code.gson`, `org.slf4j`, `org.apache.httpcomponents`
62+
- **Internal modules**: `pluggable-storage` (for `CustomStorageWrapper` interface)
63+
64+
## DOs
65+
66+
- Add input validation in `inputValidation/` package, not in `SplitClientImpl`.
67+
- `SplitFactoryImpl` is the composition root — wire new dependencies there.
68+
- When modifying evaluation, check if telemetry recording is needed (see `telemetry/`).
69+
70+
## DON'Ts
71+
72+
- Don't add business logic to `dtos/` classes.
73+
- Don't modify `SplitClientConfig` fields after the builder `build()` call.
74+
- Don't bypass the `inputValidation/` layer for any public API method.

client/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# AGENTS.md — client/impressions/
2+
3+
## Purpose
4+
5+
Impression tracking subsystem. Every `getTreatment()` call produces an **impression** — an audit record of what feature flag was evaluated, for which user, and what treatment was returned. This package manages batching, deduplication, and flushing of impressions to the Split API.
6+
7+
**Warning:** Bugs here cause silent data loss — impressions won't be recorded, breaking Split's analytics and A/B testing.
8+
9+
## Three Impression Modes
10+
11+
Controlled by `SplitClientConfig.impressionsMode()`:
12+
13+
| Mode | Class | Behavior |
14+
|------|-------|----------|
15+
| `DEBUG` | `ProcessImpressionDebug` | Every impression sent (no dedup) |
16+
| `OPTIMIZED` (default) | `ProcessImpressionOptimized` | Bloom filter dedup — only first impression per key+flag+treatment sent per hour |
17+
| `NONE` | `ProcessImpressionNone` | No impressions sent; unique keys tracked via `UniqueKeysTracker` |
18+
19+
## Key Files
20+
21+
| File | Purpose |
22+
|------|---------|
23+
| `ImpressionsManagerImpl.java` | Scheduler — batches impressions and flushes periodically |
24+
| `strategy/ProcessImpressionDebug.java` | DEBUG mode strategy |
25+
| `strategy/ProcessImpressionOptimized.java` | OPTIMIZED mode strategy |
26+
| `strategy/ProcessImpressionNone.java` | NONE mode strategy |
27+
| `filters/` | Bloom filter-based deduplication for OPTIMIZED mode |
28+
| `UniqueKeysTracker.java` | Tracks unique (key, featureFlag) pairs for NONE mode |
29+
30+
## Data Flow
31+
32+
```
33+
SplitClientImpl.getTreatment()
34+
→ EvaluatorImp returns (treatment, label, changeNumber)
35+
→ ImpressionsManagerImpl.track(Impression)
36+
→ ActiveStrategy.process(Impression)
37+
→ [DEBUG] always add to queue
38+
→ [OPTIMIZED] check Bloom filter; add if not seen
39+
→ [NONE] track unique keys, don't queue impression
40+
→ Queue flushes to Split API periodically
41+
```
42+
43+
## Testing
44+
45+
- **Run tests**: `mvn -pl :client -Dtest="*Impression*" test`
46+
- Test file pattern: `src/test/java/io/split/client/impressions/`
47+
48+
## DOs
49+
50+
- When changing evaluation logic, verify impressions are still recorded with correct label and change number.
51+
- When adding a new impression field, update all three strategy classes.
52+
53+
## DON'Ts
54+
55+
- Don't add deduplication logic outside the `filters/` package.
56+
- Don't assume `ImpressionsMode` is always `OPTIMIZED` — all three modes must work.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# AGENTS.md — engine/
2+
3+
## Purpose
4+
5+
The evaluation engine and synchronization subsystem. This is the most algorithmically complex package in the SDK. It handles:
6+
1. **Evaluation** — resolving feature flag treatments from `ParsedSplit` domain objects
7+
2. **Matchers** — 29 matcher implementations for all condition types
8+
3. **SSE streaming** — push notifications from the Split API
9+
4. **Polling/streaming orchestration**`SyncManager` decides between streaming and polling
10+
11+
## Package Structure
12+
13+
```
14+
engine/
15+
├── evaluator/ # EvaluatorImp — the hot path for getTreatment()
16+
├── experiments/ # ParsedSplit domain model, SplitFetcher, SplitParser
17+
├── matchers/ # All matcher types (BetweenMatcher, Semver*, UserDefinedSegment, etc.)
18+
├── segments/ # Segment synchronization tasks
19+
├── sse/ # SSE streaming subsystem (38 classes)
20+
│ ├── client/ # EventSourceClientImp — manages SSE connection
21+
│ ├── workers/ # SplitUpdateWorker, SegmentUpdateWorker, etc.
22+
│ ├── dtos/ # SSE event payload DTOs
23+
│ └── ... # PushStatusTrackerImp, NotificationProcessorImp
24+
├── common/ # SyncManager orchestration
25+
│ ├── SyncManagerImp.java # Top-level: picks streaming vs. polling
26+
│ ├── SynchronizerImp.java # Polling mode synchronizer
27+
│ ├── PushManagerImp.java # Streaming mode manager
28+
│ └── ConsumerSynchronizer.java # Pluggable storage sync path
29+
├── splitter/ # Splitter.java — bucket assignment via MurmurHash
30+
└── metrics/ # (deprecated/noop metrics)
31+
```
32+
33+
## Evaluation Flow (Hot Path)
34+
35+
```
36+
SplitClientImpl.getTreatment()
37+
→ InputValidation
38+
→ EvaluatorImp.evaluateFeature()
39+
→ SplitCacheConsumer.get(featureName) → ParsedSplit
40+
→ Evaluates conditions in order (ParsedCondition[])
41+
→ Matcher.match(key, attributes)
42+
→ Splitter.getBucket(key, seed) → treatment
43+
→ ImpressionsManager.track(impression)
44+
→ TelemetryRuntimeProducer.recordLatency()
45+
```
46+
47+
## Streaming vs. Polling Duality
48+
49+
`SyncManagerImp` controls this at startup:
50+
- **Streaming mode** (default): `PushManagerImp` manages SSE connection. When an SSE update arrives, workers update storage directly and notify `SDKReadinessGates`.
51+
- **Polling mode** (fallback): `SynchronizerImp` schedules periodic HTTP fetches.
52+
- **Recovery**: If streaming fails, `SyncManagerImp` falls back to polling automatically.
53+
54+
## Key Distinction: DTOs vs Domain Objects
55+
56+
- `Split` (in `client/dtos/`) = wire format from the API
57+
- `ParsedSplit` (in `engine/experiments/`) = domain object used for evaluation
58+
- `SplitParser` converts `Split``ParsedSplit`
59+
- Never pass raw `Split` DTOs to the evaluator
60+
61+
## Testing
62+
63+
- **Run tests**: `mvn -pl :client -Dtest="*Evaluator*,*Matcher*,*Sse*" test`
64+
- **Key test classes**: `EvaluatorImpTest`, matcher-specific tests in `src/test/java/io/split/engine/`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

0 commit comments

Comments
 (0)