10. Chapter review
The Chapter 8 prototype that crashed on a missing field, hung on a one-second network blip, and printed "Cannot get weather" for every kind of failure is now four focused layers on a tested foundation, with a test pyramid behind them. Each layer owns one concern; together they survive the things production throws at them.
Four layers on a shared foundation, five jobs:
- Foundation modules (
errors.py,json_helpers.py,validators.py): the toolkit imported from Chapters 9, 10, and 12, used by every layer above - API client (
weather_api_client.py): every outbound HTTP call wrapped with retry, timeout, and categorised error context - Data processing (
geocoding_processor.pyandweather_processor.py): raw payloads in, validated dataclasses out - Business logic (
orchestrator.py): sequencing, partial-success handling, and the singleWorkflowResultupper layers consume - Presentation (
display.py): three display strategies for the three workflow outcomes, plus the three-part error template from Chapter 9
Six skills you've locked in
- Layered architecture. Each layer holds one responsibility and one set of dependencies, which means each layer is independently testable and independently replaceable.
- Importing rather than re-implementing. The error categoriser, the safe-navigation helpers, and the validation pipeline came from earlier chapters as utility modules. The capstone composed them rather than rewriting them.
- Resilience as a default. The API client retries transient failures automatically with exponential backoff and jitter, so upper layers never branch on "did this fail because of the network or because of bad input?"
- Three-layer validation in production. Structure, content, and business rules each ran in sequence on every response, with non-fatal warnings accumulating instead of crashing the workflow.
- Graceful degradation. Partial success became a first-class outcome. The orchestrator returned
PARTIAL_SUCCESSwith the locations it found when the weather endpoint failed, so the user saw what worked rather than a wall of error text. - The test pyramid. Many unit tests at the base, fewer integration tests in the middle, a handful of end-to-end tests at the top. The shape isn't aesthetic; it's the cost gradient that lets you keep the suite fast enough to actually run.
Quick check
Pick a question. The answers test the architectural calls, not vocabulary.
1. The orchestrator gets a successful geocoding result but the weather call times out twice. What status should it return, and why?
PARTIAL_SUCCESS. The user's input was correct (geocoding confirmed it), so making them retype the city would be unhelpful and would probably fail in the same way. The display layer can show the resolved location and explain that current conditions are temporarily unavailable, which gives the user the right next action: try again in a moment, not retype London.
2. Why doesn't WeatherAPIClient subclass Chapter 7's APIClient?
Earlier in the book, Chapter 7 introduced a reusable APIClient base class: it put retry, timeout, and common HTTP handling behind a private _make_request method and returned (success, data, message) tuples. Chapter 9 then rebuilt that idea around explicit error categories. Chapter 13 needs the Chapter 9 shape because the orchestrator needs the failure category as a first-class field on the result so it can distinguish transient from not_found from user_input without parsing strings. Subclassing would either force string-matching on the message tuple or override _make_request wholesale. Building a sibling client whose APIResult carries categorised context is cleaner.
3. The processing layer raises on some failures and accumulates warnings on others. Where is the line drawn, and why?
The line follows whether downstream layers can act on the result. Layer 1 raises on a missing required section because nothing else can run without it. Layer 2 raises on a required field that fails type or range. A missing temperature isn't a degraded reading; it's a broken response. Optional fields (visibility, wind speed, wind direction) and Layer 3's cross-field oddities accumulate in validation_warnings and lower the quality score, which the orchestrator surfaces as warnings without changing the result status. The rule isn't "never raise"; it's "raise only when the next layer can't continue."
4. Chapter 11's graceful-degradation aggregation patterns weren't reused here. Why not?
Different shape of problem. Chapter 11 hit three independent news APIs, normalised three different response formats into one canonical model, and merged results while letting any single source fail. Chapter 13 makes sequential calls to one provider, where weather depends on geocoding having succeeded. There's no aggregation, no canonical model to design across sources, no inter-source deduplication. Reusing Chapter 11's pattern here would add coordination overhead the workflow doesn't need.
5. Why are the integration tests written against mocked HTTP rather than the real OpenWeatherMap API?
Speed, reliability, and control. Mocking lets these tests run in seconds, never fail because someone else's API is down, and exercise specific scenarios (a 503 response, a malformed payload, a slow response) that you can't reliably trigger against a live service. The real-network case still gets covered, but at the top of the pyramid, in the end-to-end tier, where its slowness is acceptable because there are only a few of them.
6. Why does the orchestrator never make an HTTP call directly?
Because then the orchestrator would be untestable without a network. By delegating every outbound request to the API client, the orchestrator's tests can replace the client with a mock and verify the sequencing, the partial-success logic, and the failure handling without touching the network. It's the same reason the orchestrator doesn't validate field types itself: single-responsibility per layer is what makes each layer independently testable.
7. The display layer borrows Chapter 9's three-part error template. What are the three parts, and why are all three needed?
What happened (a one-line description of the failure), what to do (a concrete next action the user can take), and an example (a worked instance of the suggested fix). Skip "what to do" and the user is informed but stuck. Skip "an example" and the suggestion is abstract. All three together turn a failure into something the user can act on without contacting support.
Looking forward
Part II closes here. Across these chapters you've gone from "make the request" to "design the system that survives the request failing in fifteen different ways." Part III moves into the next set of constraints: OAuth (Chapter 14), SQLite persistence (Chapter 15), and the Spotify integration (Chapter 16) that pays off Chapter 14's PKCE forward-reference. Beyond that come Flask web interfaces, testing, and deployment. Every chapter from here on assumes the patterns you've practised in Part II (error categorisation, retry, validation, layered architecture) are the floor, not the ceiling.