Skip to content

Pydantic in adapters only

Context and Problem Statement

Pydantic provides powerful validation, serialization, and settings management. Should we use it in the domain layer for our data models, or restrict it to specific layers?

Decision Drivers

  • Domain models must remain pure and dependency-free (see ADR-0002)
  • External data (JWT claims, HTTP requests, environment variables) requires robust validation
  • Adapters need OpenAPI schema generation and structured error messages
  • Mixing validation frameworks in the domain creates coupling to external libraries

Considered Options

  • Pydantic everywhere (domain + adapters)
  • Pydantic in adapters only, pure dataclasses in domain
  • No Pydantic at all — manual validation everywhere

Decision Outcome

Chosen option: "Pydantic in adapters only", because it keeps the domain pure while leveraging Pydantic's strengths at system boundaries where external data enters the system.

Where Pydantic belongs

Layer Use case Why
adapters/ Parsing JWT claims from dict to domain TokenClaims Rich validation of incoming external data
adapters/ Request/Response DTOs for HTTP APIs Automatic OpenAPI schemas, user input validation
adapters/ IdP configuration (Cognito endpoints, JWKS URIs) pydantic-settings for env vars, .env, secrets
ports/ Configuration schema for PolicyRule from YAML/JSON Structural validation with clear error messages

The mapping pattern

Adapters receive raw data (dict, JSON, env vars), validate it with a Pydantic BaseModel, and then map it to a domain dataclass. Services only ever receive domain objects.

external data → Pydantic BaseModel (adapter) → domain dataclass (service)

What NOT to do

  • DO NOT use Pydantic in domain/ — domain models remain pure dataclasses
  • DO NOT expose Pydantic models to services — services receive domain objects
  • DO NOT use BaseModel where a simple dataclass without complex validation suffices

Consequences

  • Good, because domain layer has zero external dependencies
  • Good, because Pydantic validation catches malformed external data at the boundary
  • Good, because OpenAPI schemas are generated automatically from adapter DTOs
  • Neutral, because it requires explicit mapping between Pydantic models and domain dataclasses
  • Bad, because the mapping layer adds boilerplate code

Confirmation

  • domain/ must never import pydantic — enforced by import linting
  • Code review ensures new Pydantic models are placed in adapters/ or ports/, never in domain/ or services/

More Information

Suggested dependencies: pydantic for adapter models, pydantic-settings for proxy configuration.