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:
- Normalize desired (complete inputs; canonicalize)
- Read current (from provider)
- Plan (diff desired vs current)
- Apply (execute the plan)
- Return a result with an outcome:
noopif no changes were requiredappliedif 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.
DnsRecordDesiredDnsRecordCurrentDnsRecordPlanDnsManagerResult
Contract notes:
zone_nameis the human-facing identifier and is always required.zone_idis 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_idviaprovider.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 (
noopon 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
DnsRecordDesiredover 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.