Configuration
When service logic differs between deployment environments, those differences are captured in configuration. This configuration takes two forms: environment variables and dependency injection.
Environment Variables
When data differs between environments, the business logic fetches the data at runtime from the environment, specifically OS environment variables via os.environ. To enforce type safety, completeness, and documentation, these environment variables are accessed via a Config object that is injected (see below). Config is a Pydantic model that requires the environment variables be specified fully and correctly.
The Config object can be constructed by reading directly from environment variables, or by loading local Pulumi YAML files, or both. The Pulumi YAML is the source of truth for environment variables, since it is used to deploy the service to the cloud.
- When deployed to cloud environments, only environment variables should be used.
- When running locally, it is convenient to simply load the variables instead of figuring out how to export them from Pulumi first, so this is the default behaviour.
- Environment variables can be set manually to override the YAML config.
- In particular, the
ENVIRONMENTenvironment variable is used to select which Pulumi stack config to load from. The default islocal, which generally resembles thedevenvironment.
Dependency Injection
(See Patterns for more details on the DI pattern)
When behaviour differs between environments, we have environment-specific implementations of interfaces that are wired into business logic via dependency injection, specifically with the Injector library.
The behaviour of the system is thus determined by the set of environment-specific implementations of interfaces that are selected (aka bound) when building the Injector (e.g. gcp_injector.py). Each environment has its own build target that constructs an Injector specific to that environment, and that target is used to build the artefact that is deployed.