Skip to content

Commit 5c9c587

Browse files
Merge pull request #15 from MarketDataApp/15_improve_examples
docs(examples): rewrite the consumer examples as teaching material
2 parents 235f7d0 + 5c86678 commit 5c9c587

33 files changed

Lines changed: 1339 additions & 3258 deletions

Makefile

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ help: ## Show this help
1919
@echo ""
2020
@echo "Typical workflow:"
2121
@echo " make publish # publish SDK to mavenLocal"
22-
@echo " make mock-server # in another terminal"
23-
@echo " make demo-config # in a third terminal"
22+
@echo " make example-stocks # a resource example (live API, needs a token)"
23+
@echo " make mock-server # in another terminal, then:"
24+
@echo " make example-concurrency # a cross-cutting example (needs the mock server)"
2425

2526
# ---------------------------------------------------------------------------
2627
## --- SDK build ---
@@ -56,53 +57,66 @@ mock-server: ## Start the FastAPI mock server (blocks, Ctrl+C to stop)
5657
cd $(MOCK_DIR) && ./run.sh
5758

5859
# ---------------------------------------------------------------------------
59-
## --- Consumer demos (need `make publish` first) ---
60+
## --- Examples (need `make publish` first) ---
6061
# ---------------------------------------------------------------------------
62+
# Resource examples hit the LIVE API (need a token). The cross-cutting examples that show otherwise-
63+
# invisible behavior (concurrency, retry, errors) drive the mock server — run `make mock-server`
64+
# first for those. `make example-list` prints every runnable example with a one-line description.
6165

62-
.PHONY: demo-quickstart
63-
demo-quickstart: ## Idiomatic per-resource usage tour (live API; grows as resources land)
64-
cd $(CONSUMER_DIR) && ./gradlew runQuickstart
66+
.PHONY: example-list
67+
example-list: ## List every runnable example with a description
68+
cd $(CONSUMER_DIR) && ./gradlew tasks --group examples
6569

66-
.PHONY: demo-live
67-
demo-live: ## Live API smoke (needs MARKETDATA_TOKEN in examples/consumer-test/.env)
68-
cd $(CONSUMER_DIR) && ./gradlew runLive
70+
# --- resource examples (live API) ---
71+
.PHONY: example-utilities
72+
example-utilities: ## utilities: health, quota, request echo (live)
73+
cd $(CONSUMER_DIR) && ./gradlew runUtilities
6974

70-
.PHONY: demo-config
71-
demo-config: ## Demo mode, cascade, validation (needs mock-server)
72-
cd $(CONSUMER_DIR) && ./gradlew runDemoConfig
75+
.PHONY: example-stocks
76+
example-stocks: ## stocks: candles, quote, batch quotes (live)
77+
cd $(CONSUMER_DIR) && ./gradlew runStocks
7378

74-
.PHONY: demo-exceptions
75-
demo-exceptions: ## Every MarketDataException subtype (needs mock-server)
76-
cd $(CONSUMER_DIR) && ./gradlew runExceptions
79+
.PHONY: example-options
80+
example-options: ## options: lookup, expirations, chain, quote (live)
81+
cd $(CONSUMER_DIR) && ./gradlew runOptions
7782

78-
.PHONY: demo-retry
79-
demo-retry: ## Retry, Retry-After, preflight (needs mock-server)
80-
cd $(CONSUMER_DIR) && ./gradlew runRetry
83+
.PHONY: example-funds
84+
example-funds: ## funds: NAV candles (live)
85+
cd $(CONSUMER_DIR) && ./gradlew runFunds
8186

82-
.PHONY: demo-response
83-
demo-response: ## MarketDataResponse surface features (needs mock-server)
84-
cd $(CONSUMER_DIR) && ./gradlew runResponse
87+
.PHONY: example-markets
88+
example-markets: ## markets: open/closed calendar (live)
89+
cd $(CONSUMER_DIR) && ./gradlew runMarkets
8590

86-
.PHONY: demo-concurrency
87-
demo-concurrency: ## 50-permit semaphore (needs mock-server)
88-
cd $(CONSUMER_DIR) && ./gradlew runConcurrency
91+
.PHONY: example-kotlin
92+
example-kotlin: ## the same SDK from Kotlin: sync + async (live)
93+
cd $(CONSUMER_DIR) && ./gradlew runKotlinQuickstart
8994

90-
.PHONY: demo-options
91-
demo-options: ## Full options surface: every endpoint + all params, CSV facet, columns, Option A (needs mock-server)
92-
cd $(CONSUMER_DIR) && ./gradlew runOptions
95+
# --- cross-cutting examples ---
96+
.PHONY: example-sync-async
97+
example-sync-async: ## sync vs async, parallel fan-out (live)
98+
cd $(CONSUMER_DIR) && ./gradlew runSyncVsAsync
9399

94-
.PHONY: demo-stocks
95-
demo-stocks: ## Full stocks surface: every endpoint + all params, CSV facet, columns, Option A (needs mock-server)
96-
cd $(CONSUMER_DIR) && ./gradlew runStocks
100+
.PHONY: example-config
101+
example-config: ## constructors, cascade, redaction, validation (offline)
102+
cd $(CONSUMER_DIR) && ./gradlew runConfiguration
97103

98-
.PHONY: demo-markets
99-
demo-markets: ## Full markets surface: status + all params (open/closed calendar, null cells), CSV facet, columns, Option A (needs mock-server)
100-
cd $(CONSUMER_DIR) && ./gradlew runMarkets
104+
.PHONY: example-response
105+
example-response: ## response wrapper: data, metadata, formats, saveToFile (live)
106+
cd $(CONSUMER_DIR) && ./gradlew runResponseFormats
101107

102-
.PHONY: demo-funds
103-
demo-funds: ## Full funds surface: candles + all params (no volume/intraday/chunking), CSV facet, columns, Option A (needs mock-server)
104-
cd $(CONSUMER_DIR) && ./gradlew runFunds
108+
.PHONY: example-concurrency
109+
example-concurrency: ## fan-out async + 50-permit cap, observed (needs mock-server)
110+
cd $(CONSUMER_DIR) && ./gradlew runConcurrency
111+
112+
.PHONY: example-retry
113+
example-retry: ## automatic retry + backoff + Retry-After (needs mock-server)
114+
cd $(CONSUMER_DIR) && ./gradlew runRetry
115+
116+
.PHONY: example-errors
117+
example-errors: ## the sealed exception hierarchy and how to handle it (needs mock-server)
118+
cd $(CONSUMER_DIR) && ./gradlew runErrors
105119

106-
.PHONY: demos-all
107-
demos-all: ## Run every mock-server-based demo back-to-back (needs mock-server)
108-
cd $(CONSUMER_DIR) && ./gradlew runDemoConfig runExceptions runRetry runResponse runConcurrency runOptions runStocks runMarkets runFunds
120+
.PHONY: examples-mock
121+
examples-mock: ## Run the three mock-server examples back-to-back (needs mock-server)
122+
cd $(CONSUMER_DIR) && ./gradlew runConcurrency runRetry runErrors

examples/consumer-test/README.md

Lines changed: 61 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,81 @@
1-
# consumer-test
1+
# Examples
22

3-
A collection of small runnable apps that exercise every consumer-facing
4-
behavior of `marketdata-sdk-java`. Each app stands alone — pick the one that
5-
matches the scenario you want to see, run it, read the console output.
3+
Small, self-contained programs that show how to use `marketdata-sdk-java`. Each one is meant to be
4+
**read and copied** — pick the file that matches what you want to do, run it, look at the output.
65

7-
Lives under `examples/` rather than as a `src/test` source set on purpose: it
8-
consumes the SDK as an *external* artifact (via `mavenLocal`), so the demos
9-
exercise exactly the shape a published JAR exposes — no accidental package-
10-
private leaks, no access to internal seams.
6+
They consume the SDK as a published artifact (via `mavenLocal`), exactly as your own project would,
7+
so what you see is the real public API.
118

12-
## One-time setup
9+
## Layout
10+
11+
```
12+
src/main/java/com/marketdata/examples/
13+
resources/ one file per resource — the calls you'd write first, plus an "Advanced" file each
14+
common/ cross-cutting topics that apply to every resource (do these once, not per resource)
15+
src/main/kotlin/com/marketdata/examples/
16+
Quickstart.kt the same SDK, from Kotlin
17+
```
18+
19+
### Resource examples (live API)
20+
21+
| Example | Shows |
22+
|---|---|
23+
| `UtilitiesExample` | API health, your account quota, request echo |
24+
| `StocksExample` | candles, a quote, a multi-symbol batch quote |
25+
| `OptionsExample` | symbol lookup, expirations, a filtered chain, a contract quote |
26+
| `FundsExample` | mutual-fund NAV candles |
27+
| `MarketsExample` | the exchange open/closed calendar |
28+
| `…AdvancedExample` (one per resource) | universal params, date windows, column projection, CSV output, sealed chain filters |
29+
30+
### Cross-cutting examples (`common/`)
31+
32+
| Example | Shows | Needs |
33+
|---|---|---|
34+
| `SyncVsAsyncExample` | the sync call vs its async (`CompletableFuture`) variant, and a parallel fan-out | live API |
35+
| `ConfigurationExample` | constructors, the config cascade, token redaction, fail-fast validation | offline |
36+
| `ResponseFormatsExample` | the response wrapper: typed data, metadata, format predicates, raw body, `saveToFile` | live API |
37+
| `ConcurrentRequestsExample` | firing many requests at once and the SDK's 50-request concurrency cap | mock server |
38+
| `RetryAndBackoffExample` | automatic retry, exponential backoff, `Retry-After` | mock server |
39+
| `ErrorHandlingExample` | the sealed `MarketDataException` hierarchy and how to branch on it | mock server |
40+
41+
The cross-cutting examples cover these behaviors **once**, using whichever resource is handy as the
42+
vehicle — they're not repeated per resource.
43+
44+
## Running
1345

1446
```bash
15-
# 1. Publish the SDK to your local Maven cache. Run from the SDK root
16-
# (two directories up). The Makefile wraps it:
17-
cd ../..
47+
# 1. Publish the SDK to your local Maven cache (run once, from the SDK root, two dirs up):
1848
make publish
1949

20-
# 2. (For runLive only) put your token in this directory's .env:
50+
# 2. For the live examples, put your token where the SDK's cascade can find it:
2151
echo "MARKETDATA_TOKEN=your-token-here" > examples/consumer-test/.env
2252
```
2353

24-
## Running
25-
26-
From the SDK root, the easy path is `make` (see `make help` for the full list):
54+
Then run any example by its Gradle task (from this directory) or its `make` target (from the SDK
55+
root):
2756

2857
```bash
29-
make demo-quickstart # idiomatic per-resource tour — live API, no mock
30-
make demo-live # full plumbing smoke — live API, no mock
31-
make demo-config # config, validation, demo mode — needs mock server
32-
make demo-exceptions # every MarketDataException subtype — needs mock server
33-
make demo-retry # retry, Retry-After, preflight — needs mock server
34-
make demo-response # Response<T> features — needs mock server
35-
make demo-concurrency # 50-permit semaphore — needs mock server
36-
make demos-all # the five mock-server demos back-to-back
58+
./gradlew runStocks # or: make example-stocks
59+
./gradlew runSyncVsAsync # or: make example-sync-async
60+
./gradlew tasks --group examples # list them all (make example-list)
3761
```
3862

39-
Or directly from this directory, bypassing the Makefile:
63+
`UtilitiesExample`'s `status()` call, `SyncVsAsyncExample`, and `ResponseFormatsExample` use a public
64+
endpoint and run **without** a token. The other resource examples need one (and options/funds data
65+
needs the matching entitlements); without it they print a one-line hint instead of crashing.
4066

41-
```bash
42-
./gradlew tasks --group "consumer demos" # list all apps
43-
./gradlew runLive # same as `make demo-live`
44-
./gradlew runDemoConfig # etc.
45-
```
67+
## The mock server (for the three behavior examples)
4668

47-
Apps that say "needs mock server" require the mock running in another
48-
terminal. Easiest:
69+
`ConcurrentRequestsExample`, `RetryAndBackoffExample` and `ErrorHandlingExample` demonstrate things
70+
you can't see against the live API — the concurrency cap, deterministic retry timing, each error
71+
type on demand. They point the SDK at a local mock server whose responses are scripted up front.
72+
Start it in another terminal first:
4973

5074
```bash
51-
make mock-server # from the SDK root
52-
# or, equivalently:
53-
cd ../mock-server && ./run.sh
75+
make mock-server # from the SDK root
76+
# or: cd ../mock-server && ./run.sh
5477
```
5578

56-
Without it the demo fails fast with a clear "server not reachable" message.
57-
58-
## What each app shows
59-
60-
| App | Scenario | What you should see |
61-
|---|---|---|
62-
| **QuickstartApp** | Idiomatic per-resource usage. Designed to **grow** — each new SDK resource adds a section. Start here. | For each wired resource, one short snippet per typical call + console output of the typed data the consumer would actually use. Today: `utilities` (status / user / headers) and `options` (lookup / expirations / strikes / chain incl. sealed filters + `expiration=all` + `rho` / quote / quotes with `countback`). |
63-
| **LiveSmokeApp** | The happy path against the real API | client.toString redacted; sync + async parity on status/user/headers; parallel calls completing in ≈ slowest-single-call wall-time; final rate-limit snapshot populated |
64-
| **DemoAndConfigApp** | Construction-time behavior | demo-mode skip; §16 token redaction (short ≤8 → full; >8 → ******ABCD); cascade (explicit wins); IAE on invalid baseUrl / CRLF API key; validateOnStartup 200 vs 401 paths |
65-
| **ExceptionsApp** | Every sealed exception subtype | 401 → AuthenticationError; 400 → BadRequestError; 429 → RateLimitError (+ Retry-After); 500 → ServerError (no retry); 503×4 → ServerError after ≈7s; malformed JSON → ParseError; empty body → ParseError (§29 fix message); connection refused → NetworkError after retries; ADR-002 sealed routing via instanceof |
66-
| **RetryBehaviorApp** | The §9 retry contract | 503→503→200 recovers in ≈3s; Retry-After delta overrides exponential; Retry-After HTTP-date honored; pathological Retry-After (1 day) capped at 10 min — falls back to exponential; §10.3 preflight blocks the 2nd request when snapshot reports remaining=0 (0ms wall-time, 0 server-side requests) |
67-
| **ResponseFeaturesApp** | §13.5 Response<T> surface | isJson / isCsv / isHtml mutually exclusive; 404 + `{"s":"no_data"}` returns successfully with isNoData=true; rawBody() is a defensive copy (consumer mutations don't leak); saveToFile() writes verbatim; toString() omits data + redacts query (§16) |
68-
| **ConcurrencyApp** | §12 / ADR-007 50-permit semaphore | 60 parallel calls; server observes peak in-flight = exactly 50; total wall-time ≈ 2× per-call delay (two batches of 50+10) |
69-
70-
## Adding a section to QuickstartApp
71-
72-
`QuickstartApp` is the only app in this directory designed to be **extended** as
73-
the SDK grows. The other six prove a fixed contract; this one is the running
74-
catalog of "what each resource looks like in consumer code".
75-
76-
When a new resource lands on the SDK:
77-
78-
1. Open `src/main/java/com/marketdata/consumer/QuickstartApp.java`.
79-
2. Uncomment the matching placeholder line in `main(...)` (e.g.
80-
`// stocksExamples(client);`).
81-
3. Implement `xxxExamples(MarketDataClient client)` following the
82-
`utilitiesExamples` shape: one `Console.step` + one short SDK call + one
83-
`Console.ok` per typical use case. Catch `AuthenticationError` separately
84-
when the endpoint needs a token, so the demo stays runnable in demo mode.
85-
4. Keep each example to **3–5 lines of SDK code** — the goal is "what you'd
86-
copy-paste into your own app", not exhaustive coverage. Edge cases belong
87-
in the other demos.
88-
89-
## How the mock server fits
90-
91-
The mock server (FastAPI, `../mock-server/`) is what makes the
92-
non-live demos deterministic. Each demo POSTs a list of scripted responses
93-
to `/_admin/script`, then makes its real SDK call against the same host —
94-
the server's catch-all pops exactly the scripted response. Apps that need to
95-
see specific status codes, headers (Retry-After, x-api-ratelimit-*), or
96-
timing behavior depend on this scripting.
97-
98-
The control plane (`/_admin/*`) is hit with a plain `java.net.http.HttpClient`,
99-
not the SDK — keeping the SDK's surface uncluttered.
100-
101-
> Note: `MockServerControl` forces HTTP/1.1 because uvicorn's HTTP/2 upgrade
102-
> handling drops POST bodies during the negotiation. The SDK itself stays on
103-
> its ADR-004 HTTP/2 default — only the admin client downgrades.
104-
105-
## Caveats
106-
107-
- The local `.env` has a token that's been used during development. If the
108-
token is no longer valid against api.marketdata.app, `runLive` will show
109-
`AuthenticationError` on calls that need it (status/ stays public).
110-
- "Demo mode" (no token at all) is hard to see if you have a token in your
111-
env or `.env` — the cascade picks it up. The demo detects this and prints
112-
a skip note rather than constructing a misleading client.
79+
Without it those three examples fail fast with a clear "mock server not reachable" message. The mock
80+
is a teaching aid (`com.marketdata.examples.util.MockServer`) — it is **not** part of the SDK and you
81+
never need it in your own code.

0 commit comments

Comments
 (0)