|
1 | | -# consumer-test |
| 1 | +# Examples |
2 | 2 |
|
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. |
6 | 5 |
|
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. |
11 | 8 |
|
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 |
13 | 45 |
|
14 | 46 | ```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): |
18 | 48 | make publish |
19 | 49 |
|
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: |
21 | 51 | echo "MARKETDATA_TOKEN=your-token-here" > examples/consumer-test/.env |
22 | 52 | ``` |
23 | 53 |
|
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): |
27 | 56 |
|
28 | 57 | ```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) |
37 | 61 | ``` |
38 | 62 |
|
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. |
40 | 66 |
|
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) |
46 | 68 |
|
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: |
49 | 73 |
|
50 | 74 | ```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 |
54 | 77 | ``` |
55 | 78 |
|
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