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 primitivesdxaws-dns– DNS planning and Route 53 providersdxaws-acm– ACM certificate workflowsdxaws-cloudfront– CloudFront distributions and edge configuration
Each module is:
- independently installable (
pip install -e) - independently testable (
pytestinside 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.pyThis 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-fooOr 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-fooOr from inside the repo:
pytestWorkspace-level testing is intentionally repo-by-repo to avoid cross-module test collisions.
Linting and type checking#
Most modules support:
ruff check .
mypy srcThese 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-dnsdepends ondxaws-coredxaws-acmdepends ondxaws-core(and may interact withdxaws-dns)dxaws-cloudfrontdepends ondxaws-coreand consumes outputs fromdxaws-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.