Design Notes for dxaws-dns

This document captures the design intent and architectural rules for the dxaws-dns primitive.

The module is intentionally small: it exists to provide a stable, composable contract for managing DNS record sets in AWS Route 53 using declarative convergence.


Architectural Position in dxaws

dxaws-dns is a primitive module.

That means:

  • It exposes stable Python contracts.

  • Its public interfaces are immutable and only extendable.

  • It contains no orchestration beyond converging a single DNS record.

  • It is designed to be composed by higher-level modules (e.g., website/CDN orchestration).

In the dxaws stack, the rough layering looks like:

  • dxaws-core: shared session, provider base classes, o11y

  • dxaws-dns: DNS record primitives (this module)

  • higher-level modules: orchestration (CloudFront, websites, multi-account)


Core Pattern: Declarative Convergence

The core workflow is:

  1. Normalize desired (complete inputs; canonicalize)

  2. Read current (from provider)

  3. Plan (diff desired vs current)

  4. Apply (execute the plan)

  5. Return a result with an outcome:

    • noop if no changes were required

    • applied if AWS was mutated

The contract goal is deterministic behavior:

  • Apply once → applied

  • Apply again → noop

  • Introduce drift → applied

  • Re-apply → noop

This is validated via AWS-backed acceptance tests.


Layering

Models (contracts)

Models define the stable API surface and must not leak AWS/boto3 shapes.

  • DnsRecordDesired

  • DnsRecordCurrent

  • DnsRecordPlan

  • DnsManagerResult

Contract notes:

  • zone_name is the human-facing identifier and is always required.

  • zone_id is optional at call sites and may be resolved by the system.

Manager (orchestration + normalization)

The manager owns:

  • Input normalization (canonicalization)

  • Workflow orchestration (current → plan → apply)

  • Emitting o11y events (when enabled)

Normalization rules live here because:

  • It keeps the planner deterministic.

  • It prevents duplication across call sites (CLI, tests, orchestrators).

Planner (pure diff)

The planner:

  • Takes (desired, current)

  • Produces a plan

  • Must be side-effect free (no AWS calls)

Executor (mutation)

The executor:

  • Takes a plan

  • Calls provider write operations

  • Must not perform input enrichment (e.g., resolving zone_id)

Provider (AWS isolation)

The provider is the only layer that should:

  • Touch boto3

  • Encode AWS/Route 53 quirks

The provider surface should stay small and explicit.


Canonicalization Rules

These are the canonicalization rules that make diffs stable and idempotency real.

Hosted zone identity

Callers provide:

  • zone_name (e.g., dev.dxaws.com)

The system may resolve:

  • zone_id via provider.resolve_zone_id(zone_name=...)

This keeps AWS implementation details out of call sites.

Record names

Record names are normalized to a fully-qualified name based on zone_name.

Example:

  • record_name="_test" + zone_name="dev.dxaws.com"

  • becomes "_test.dev.dxaws.com."

Record types

Record types are normalized to uppercase.

TXT values (quoting)

Route 53 returns TXT values wrapped in double quotes.

To keep diffs stable:

  • Desired TXT values are normalized to quoted form.

  • Provider write operations also normalize to quoted form.

This ensures that a second apply becomes noop.

CNAME targets

Route 53 stores DNS names with a trailing dot.

Callers should prefer canonical targets (e.g., example.com.). The planner operates on the normalized values.


Acceptance Tests as Contract Tests

Acceptance tests in this module are treated as contract tests.

They validate:

  • Create / read / delete lifecycle

  • Idempotency (noop on second apply)

  • TTL drift detection + correction

  • Multiple record types (TXT, A, CNAME)

Safety features:

  • Tests are opt-in (DXAWS_AWS_TESTS=1).

  • Tests require explicit zone confirmation (DXAWS_ACCEPTANCE_CONFIRM).

  • Tests print AWS identity before mutation.

  • Tests clean up after themselves and verify deletion.


Tradeoffs and Intentional Limitations

  • This module focuses on record sets, not zone provisioning.

  • It avoids complex routing policies (weighted, latency, geo) until the core contracts are proven.

  • It intentionally prefers explicit, composable APIs over convenience wrappers.

If/when the module expands, it should do so by extending interfaces rather than changing existing fields or semantics.


Extensibility Guidelines

When extending this module:

  • Prefer adding new optional fields to DnsRecordDesired over changing meaning.

  • Keep planner logic deterministic and side-effect free.

  • Put AWS quirks in the provider and canonicalization in the manager.

  • Add acceptance coverage for any new record type or behavior.