8. Chapter review

Six skills go on the shelf this chapter, listed below. The quiz pressure-tests the architectural calls, not the syntax: the design judgements that actually matter in review.

What you can do now

  • Tell defensive programming and validation apart in a codebase you are reading for the first time, and know which one is wrong to reach for in a given situation.
  • Design a three-layer validation pipeline against any API response, starting from "the shape is right" and finishing at "the combination makes sense."
  • Write a JSON Schema that expresses the structural and content rules declaratively, and run it through jsonschema.validate with error messages that name the failed constraint.
  • Combine schemas with hand-written business-rule functions in the hybrid pattern, keeping the division of labour clear: schema for the mechanics, Python for the domain.
  • Place validation at the right architectural boundary so each layer gains a specific contract and downstream code can trust the data without repeating checks.
  • Choose fail-fast or fail-graceful per field based on how load-bearing the field is, and write good error messages that name the rule, include the value, and identify the layer.

Chapter quiz

Seven questions that test the architectural judgement, not the syntax. Attempt each one before expanding the answer.

Question 1: defensive programming versus validation

You are reviewing a pull request that adds try_fields(item, ["title", "name", "heading"]) to a normalizer. The PR description says: "adds validation for missing title fields." Should you approve?

Show answer

No. try_fields is defensive programming: it prevents a crash when the expected field name is missing by trying alternatives. It does not reject bad data, it accommodates variation. Validation is the discipline of rejecting data that does not meet a rule: an empty string, a -999 placeholder, an email without an @. The two are complementary and both should be present, but they are not interchangeable and relabelling one as the other is how gaps ship.

Question 2: layer ordering

Why must structural validation run before content validation, and why must content validation run before business-rule validation?

Show answer

Every layer depends on the previous one having succeeded. Content validation starts with if temp < -100, which assumes temp is a number, a structural claim. Business-rule validation compares apparent_temp to temp, which assumes both fields exist and are numeric, a content claim. Run the layers out of order and you either crash (business rules hitting missing keys) or produce meaningless errors (content checks reporting range failures on strings).

Question 3: schema versus manual for a new validator

Your team is about to add a validator for a new payment-processing response with twelve fields and three cross-field invariants. Which approach do you reach for, and why?

Show answer

The hybrid approach. Twelve fields with individual type and range rules is exactly the shape JSON Schema handles well -- twelve near-identical manual validators are the repetition schemas were designed to eliminate. Three cross-field invariants are likely to be clearer and easier to test as hand-written Python. Start with the schema, add the business-rule function, chain them in a fail-fast pipeline. Pure manual would be a wall of repetitive validator code; pure schema would make the three invariants harder to understand and maintain.

Question 4: placement

Where does business-rule validation belong: in the API client, the service layer, or the application layer? Give a one-sentence justification.

Show answer

In the service layer. The API client does not know or care about domain constraints (snow at 15°C is a business fact, not an API-contract fact), and the application layer already assumes domain constraints hold before it displays or stores. Structural and content validation belong in the API client; business rules belong in the service layer; display logic belongs in the application layer.

Question 5: fail-fast versus fail-graceful

The Weather Dashboard fetches current temperature and a weather-code icon. The response arrives with an out-of-range temperature and an unknown weather code. What happens to each, and why are the two fields handled differently?

Show answer

Temperature is load-bearing (the whole UI renders against it), so an invalid temperature is fail-fast: raise or return a hard error, refuse to proceed. The weather-code icon is an enhancement (the dashboard is useful without it), so an invalid code is fail-graceful: log a warning, set the field to None, let the UI fall back to a default icon. The rule is criticality-per-field: essential data fails hard, enhancements degrade.

Question 6: what a schema cannot do

Name three kinds of rule that JSON Schema can handle well, and three that are usually clearer outside the schema. For each one outside the schema, say what you would use instead.

Show answer

Schema handles well: type checking ("temperature must be a number"), range validation ("humidity between 0 and 100"), required fields ("temperature_2m must be present"). Also string patterns via regex, enumerated values, and nested-object structure. Schema handles poorly or awkwardly: domain comparisons (snow codes at warm temperatures), rules that need outside context (whether a payment amount exceeds a user's credit limit), and array-by-array comparisons (whether temp_min[i] <= temp_max[i] for every day). JSON Schema has conditional keywords for some relationship rules, but these three are clearer as hand-written Python validator functions chained after the schema in a hybrid pipeline.

Question 7: cross-chapter integration

How do the defensive helpers from Chapter 10 (safe_get, try_fields) relate to the validation layers in this chapter? Where do they run, and what do they replace or complement?

Show answer

They are complementary, not alternatives. safe_get and try_fields prevent crashes when data structures are missing or nested deeply -- they are defensive programming, the "do not blow up on surprises" discipline. Validators in this chapter reject data that is present but wrong -- they are the "the data must satisfy a rule" discipline. In a production codebase you use both: the normalizer reads with safe_get to avoid KeyError, and the validator checks the extracted values against rules before the data flows downstream. Defensive programming protects your code; validation protects your output.

Looking forward

Chapter 13 is the capstone that brings error handling (Chapter 9), defensive programming (Chapter 10), normalisation (Chapter 11), and validation (this chapter) onto one production-grade Weather Dashboard. You will coordinate responses from multiple APIs simultaneously, each with its own failure modes and data-quality issues, and you will see how the four disciplines compose when they are all present at once: error handling for the request, defensive programming for the parse, validation for the output contract, and normalisation for the unified downstream shape.

The mental model after this chapter: reliable API integration is two complementary halves stacked. Error handling and defensive programming keep the request alive and the parse crash-free. Validation and normalisation keep the data usable and the contracts stable. Together, they are the difference between an application that runs and an application that produces output someone can depend on.