skip to content

Declarative Input Constraints

Declaring constraints on input models as annotations, schemas or rule sets, with nested, collection, group and conditional rules, and where they run. Asked because a late check protects nothing.

on this pageshow

questions

4

In a web framework, what does declaring input constraints on the request model change compared with checking values inside the handler?

level: juniorimportance: must knowfreq 68%

answer

  1. rules as data, not statements
  2. declared beside the field
  3. framework enforces, handler assumes
  4. runs after binding, before the handler
  5. inert unless validation is triggered

basics

~20 s

Declared constraints are metadata the framework reads and enforces on the bound model before the handler body runs, so the rule lives beside the field it describes and every endpoint binding that model inherits it.

solid answer

~50 s

A declared constraint is **data about a field**, not a statement executed by your code: a marker on the model field, an entry in a schema document describing the payload, or an entry in a rule set built beside the model. The framework reads that metadata after it has bound the request onto the model, runs the checks it implies, and only then enters the handler body — so the handler can assume the shape it was given is already legal. The gain is not terseness but *placement*: the rule sits with the field, one declaration covers every endpoint that binds the model, and the same metadata can drive generated API documentation and client-side checks. The cost is that nothing runs unless the framework's validation step is actually triggered for that binding, and rules that need stored state cannot be expressed this way at all.

go deeper

for a junior

Remember the shape of the answer: the rule is attached to the field as data, the framework checks it after binding the request, and the handler runs only on input that already passed.

for a middle

Explain the mechanics: metadata is read by a validation step, it sees typed values because binding already happened, and whether that step runs automatically or on an explicit marker differs between frameworks.

for a senior

Show that you know where the guarantee leaks — models built in code, handlers that read the raw request, and bindings nobody asked to validate — and how you keep those paths from existing.

for a principal

Frame it as contract ownership: one machine-readable description feeding enforcement, documentation and client stubs, weighed against the rules that genuinely cannot live at the boundary.

## Rules as data, not as statements Input validation can be written two ways. The **imperative** way puts the checks in the handler body: the handler receives a model (or raw values), tests each field, and returns an error itself. The **declarative** way attaches the rules to the input model as *metadata* and lets the framework apply them. Three declaration styles are common, and they differ in where the metadata physically lives rather than in what it means: | Style | Where the rule lives | Typical strength | |---|---|---| | Field markers on the model | Beside the field, in the model's own source | Shortest path from reading a field to knowing its rules | | Schema document | A separate artifact describing the payload shape | Shareable with clients; can be checked before binding | | Rule set object | A separate object composed next to the model | Rules are ordinary values: composable, conditionally built | All three express the same idea: **the model carries a description of what a legal instance looks like**, and some component other than your handler enforces it. ## Where the enforcement actually happens For a typical request the framework parses the body or parameters into a structure, binds that structure onto a typed input model, then runs the declared constraints over the **bound** model, and only afterwards calls the handler body. Two consequences follow and both are asked about: 1. Constraints see values **after** binding, so they check typed values, not raw text. A field that could not be converted to its declared type never reaches the constraint at all — it fails earlier. 2. The handler body is downstream of the check. That is the whole point: by the time your code runs, the input has already been rejected if it was illegal, so the body contains business logic and not a prelude of guard clauses. Frameworks differ in what triggers the step: some validate every bound input model automatically, others require the binding site or the route to be marked as validated, and a few treat validation as a separate component you place in the request pipeline. This matters more than it sounds — a correctly declared constraint on a model that nothing asks the framework to validate is inert. ## What the declarative form buys - **One rule, every endpoint.** Every route that binds the model gets the same rules; a handler cannot forget one, because no handler is writing them. - **Locality.** The rule is readable next to the field it constrains, so the model is self-documenting. - **Uniform failure handling.** Because the framework produced the failures, one place can turn them into the response body, rather than each handler inventing its own. - **Machine-readable metadata.** The same declarations can generate API documentation, client stubs, or a published schema, so the contract and the enforcement do not drift apart. - **Testability.** The rules can be exercised by validating model instances directly, without building a request or standing up a route. - **Change safety.** Tightening a rule is a one-line edit to the model rather than a search across handlers. ## What it does not buy A declaration is not a compile-time guarantee: a model instance created in ordinary code — in a test, in a mapper, in a handler that read the raw request itself — carries the metadata but nothing evaluates it. Nor can a declared constraint express a rule that needs stored state or an outside lookup; those depend on facts the boundary does not have and belong with the operation that can decide them transactionally. And the boundary rule set does not replace the domain's own invariants: the boundary is a **fail-fast filter** on shape and range, while the domain still enforces what it means for the operation to be legal. ## How to talk about it in an interview Say what the mechanism is (metadata read by the framework), where it runs (after binding, before the handler body), and what it is worth (one declaration per model, uniform enforcement, documentation from the same source). Then volunteer the limit — a declared rule only runs where the framework is asked to validate, and rules needing state do not fit — because naming the limit is what separates someone who has used the feature from someone who has reasoned about it.

  • If the rules are metadata, what makes them run at all?
    A validation step in the request path evaluates the metadata against the bound model. Some frameworks run it for every bound input model, others only where the binding site or route is marked. Constructing the same model in ordinary code evaluates nothing.
  • Why is a declared constraint easier to keep in step with published API documentation?
    The declaration is machine-readable metadata, so the documented contract and the enforced contract can be produced from one source. When rules live as statements inside handlers, the documentation is a second, hand-maintained copy that drifts.
  • Does declaring constraints on the input model mean the domain no longer needs its own checks?
    No. The boundary set filters shape, presence and range early and cheaply. The domain still owns rules that depend on stored state or on the meaning of the operation, and it is also reached by callers that never crossed the request boundary.

saying these in an interview costs you the question

  • Thinks a declared constraint runs whenever the object is constructed anywhere in the code
  • Believes the constraint is enforced by the type system at compile time
  • Assumes declaring rules on the model removes the need for domain invariants
  • Copies the same checks into every handler instead of onto the shared model
  • Claims the declaration protects a request even when nothing triggers validation
open as a page

In a web framework, why do constraints declared on a nested object or on list elements often not run during request validation?

level: middleimportance: must knowfreq 62%

basics

~20 s

Validation of a bound model stops at its own fields. Reaching a nested object or a collection's elements requires declaring that the validator descends into them; a rule on the container only checks the container.

open as a page

In a web framework, how can a value reach persistence unvalidated even though the input model declares a constraint on that field?

level: seniorimportance: should knowfreq 56%

basics

~20 s

Declared constraints only run where the framework binds that model and evaluates it. Reading the raw request, building the model in code, binding a laxer second model, or mutating a field after the check all reach the write unprotected.

open as a page

How do you decide which input rules belong in a request model's declarative constraint set and which must stay in domain logic?

level: principalimportance: should knowfreq 45%

basics

~20 s

Declare at the boundary what can be decided from the payload alone — presence, type-level shape, range, cardinality, cross-field agreement. Rules needing stored state, a lookup, or transactional certainty belong with the operation that can decide them atomically.

open as a page