Skip to main content

Modules

·731 words·4 mins

What is a dxaws module?
#

A dxaws module is a small, focused Python package that owns a single area of AWS functionality.

Examples include:

  • dxaws-core – shared AWS plumbing and primitives
  • dxaws-dns – DNS planning and Route 53 providers
  • dxaws-acm – ACM certificate workflows
  • dxaws-cloudfront – CloudFront distributions and edge configuration

Each module is:

  • independently installable (pip install -e)
  • independently testable (pytest inside the repo)
  • intentionally narrow in scope
  • has it’s own documentation

This keeps the system flexible and avoids the “giant framework” problem.


The multi-mono model
#

Although dxaws is developed inside a single workspace, it is not a monorepo in the traditional sense.

Instead, it uses a multi-mono model:

  • every module lives in its own repository directory
  • every module has its own pyproject.toml
  • dependencies between modules are explicit

The workspace tooling (Makefile, pytest config) exists only to orchestrate these repos, not to blur their boundaries.

If you removed a module from the workspace and cloned it elsewhere, it would continue to work on its own provided it has access to its dependencies like dxaws-core


Creating a new module
#

New modules are created using the workspace generator.

From the workspace root:

make new-module MODULE=dxaws-<name> DESC="Short description of the module"

For example:

make new-module MODULE=dxaws-acm DESC="ACM certificate workflows"

This will:

  • create a new repo under repos/
  • set up a src/-based Python package
  • configure testing, linting, and typing defaults
  • generate provider scaffolding and example tests

No Makefile changes are required.


Module layout
#

A typical dxaws module looks like this:

repos/dxaws-foo/
├─ pyproject.toml
├─ README.md
├─ src/
│  └─ dxaws_foo/
│     ├─ __init__.py
│     ├─ constants.py
│     ├─ exceptions.py
│     ├─ models.py
│     ├─ manager.py
│     └─ providers/
│        ├─ __init__.py
│        └─ aws.py
└─ tests/
   ├─ test_pkg_imports_dxaws_foo.py
   ├─ test_models.py
   └─ test_provider_base.py

This structure is consistent across all modules to make navigation and onboarding predictable.

Declarative Convergence
#

How each template file fits declarative convergence

  • models.py: Desired state + outputs. Think: Desired* (what we want), Current* (what AWS reports), Plan* (diff), Result* (post-apply outputs).
  • planner.py: Diff engine. Takes (desired, current) and produces a deterministic plan: create/update/delete/noop actions.
  • executor.py: Applies the plan. Executes steps in order via a provider and returns outputs.
  • module.py: Orchestrator. Loads current state, calls planner, calls executor, returns “converged result”. This is where you ensure idempotency and “single entrypoint”.
  • providers/base.py: Provider interface (protocol-ish) that planner/executor/module code relies on. Keeps unit tests clean.
  • providers/aws.py: Concrete boto3 implementation.
  • constants.py / exceptions.py: Shared knobs (defaults, tag keys, limits) and error types.

That gives you the canonical flow:

DesiredState -> (Provider.get_current) -> CurrentState -> Planner.plan -> Plan -> Executor.apply -> Result


Providers and managers
#

Modules are built around a simple separation of concerns:

  • Managers contain orchestration and decision-making logic
  • Providers interact with external systems (usually AWS)

Managers depend on a provider interface rather than a concrete AWS implementation. This keeps managers easy to test and reason about.

By default, AWS providers inherit from shared AWS plumbing in dxaws-core, ensuring consistent behavior across modules.


Working on a module
#

Installing a module
#

From the workspace root:

make install-one REPO=dxaws-foo

Or from inside the module itself:

pip install -e .[dev]

Editable installs are the source of truth for imports.


Running tests
#

To test a single module:

make test-one REPO=dxaws-foo

Or from inside the repo:

pytest

Workspace-level testing is intentionally repo-by-repo to avoid cross-module test collisions.


Linting and type checking
#

Most modules support:

ruff check .
mypy src

These tools are optional but recommended and are wired into the workspace Makefile where available.


Dependencies between modules
#

Modules may depend on other dxaws modules, but the dependency direction should be clear and intentional.

For example:

  • dxaws-dns depends on dxaws-core
  • dxaws-acm depends on dxaws-core (and may interact with dxaws-dns)
  • dxaws-cloudfront depends on dxaws-core and consumes outputs from dxaws-acm

Circular dependencies are avoided by design.


Design philosophy
#

dxaws modules are intentionally:

  • small – do one thing well
  • explicit – no hidden globals or path tricks
  • composable – modules are building blocks, not frameworks
  • boring – predictable code is easier to maintain than clever abstractions

If a module starts to feel large or ambiguous, that is usually a signal to split it.


Summary
#

  • dxaws modules are independent Python packages
  • the workspace provides orchestration, not coupling
  • new modules are created via make new-module
  • consistency is enforced by templates, not conventions alone

Understanding modules is the key to understanding dxaws as a whole.