Description
Summary
In #3945 we added new zero-dependency functional utilities to read and parse environment variables. These new functions supersede the old class based environment service approach. We should refactor the Parameters package to use the new function based approach instead of the custom EnvironmentVariablesService
class.
Why is this needed?
The rationale for the introduction of these utilities as per #3945 is as follows:
[M]ost of the advantage to customers will come in the form of smaller utilities over time, since we'll be able to use these helpers across other Powertools for AWS utilities and replace the existing EnvironmentVariablesService class-based model which is extremely verbose and requires props drilling if you want to access env variables deep into an inheritance stack.
This change will help us to eventually remove the EnvironmentVariablesService
entirely from the code base. The Parameters package currently has its own extended version of the service that adds parameters-specific environment variable handling.
Which area does this relate to?
Parameters
Solution
Currently the Parameters package has its own EnvironmentVariablesService class that extends the common one:
import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons';
class EnvironmentVariablesService
extends CommonEnvironmentVariablesService
implements ConfigServiceInterface
{
private parametersMaxAgeVariable = 'POWERTOOLS_PARAMETERS_MAX_AGE';
private ssmDecryptVariable = 'POWERTOOLS_PARAMETERS_SSM_DECRYPT';
public getParametersMaxAge(): number | undefined {
const maxAge = this.get(this.parametersMaxAgeVariable);
if (maxAge.length === 0) return undefined;
const maxAgeAsNumber = Number.parseInt(maxAge, 10);
if (Number.isNaN(maxAgeAsNumber)) {
console.warn(`Invalid value for ${this.parametersMaxAgeVariable} environment variable: [${maxAge}], using default value of ${DEFAULT_MAX_AGE_SECS} seconds`);
} else {
return maxAgeAsNumber;
}
}
public getSSMDecrypt(): string {
return this.get(this.ssmDecryptVariable);
}
}
This service is used in the BaseProvider and passed to GetOptions and GetMultipleOptions classes primarily for reading the POWERTOOLS_PARAMETERS_MAX_AGE
environment variable.
Proposed refactor using new functional utilities (minimal changes approach):
Since the Parameters package has a different architecture with multiple provider classes extending BaseProvider, we'll take a minimal approach by replacing the service usage with direct functional utility calls:
import { getNumberFromEnv, getStringFromEnv } from '@aws-lambda-powertools/commons/utils/env';
// Replace EnvironmentVariablesService methods with utility functions
const getParametersMaxAge = (): number | undefined => {
try {
return getNumberFromEnv({
key: 'POWERTOOLS_PARAMETERS_MAX_AGE',
});
} catch (error) {
return undefined;
}
};
const getSSMDecrypt = (): string => {
return getStringFromEnv({
key: 'POWERTOOLS_PARAMETERS_SSM_DECRYPT',
defaultValue: '',
});
};
Update BaseProvider constructor:
// Remove envVarsService property and initialization
abstract class BaseProvider implements BaseProviderInterface {
// Remove: public envVarsService: EnvironmentVariablesService;
protected client: unknown;
protected store: Map<string, ExpirableValue>;
public constructor({
awsSdkV3Client,
clientConfig,
awsSdkV3ClientPrototype,
}: BaseProviderConstructorOptions) {
this.store = new Map();
// Remove: this.envVarsService = new EnvironmentVariablesService();
// ... rest of constructor
}
}
Update GetOptions class:
import { getParametersMaxAge } from '../utils/env.js'; // New utility file
class GetOptions implements GetOptionsInterface {
public constructor(
// Remove: envVarsService: EnvironmentVariablesService,
options: GetOptionsInterface = {}
) {
Object.assign(this, options);
if (options.maxAge === undefined) {
this.maxAge = getParametersMaxAge() ?? DEFAULT_MAX_AGE_SECS;
}
}
}
Update GetMultipleOptions class:
class GetMultipleOptions extends GetOptions implements GetMultipleOptionsInterface {
public constructor(
// Remove: envVarsService: EnvironmentVariablesService,
options: GetMultipleOptionsInterface = {}
) {
super(options); // Remove envVarsService parameter
// ... rest of constructor
}
}
Update BaseProvider method calls:
// Update get method
protected async _get(
name: string,
options?: GetOptionsInterface
): Promise<unknown | undefined> {
const configs = new GetOptions(options); // Remove envVarsService parameter
// ... rest of method
}
// Update getMultiple method
protected async _getMultiple(
path: string,
options?: GetMultipleOptionsInterface
): Promise<unknown> {
const configs = new GetMultipleOptions(options); // Remove envVarsService parameter
// ... rest of method
}
Update AppConfigProvider constructor:
``ts
import { getServiceName } from '@aws-lambda-powertools/commons/utils/env';
class AppConfigProvider extends BaseProvider {
public constructor(options: AppConfigProviderOptions) {
// ... rest of constructor
this.application = application ?? this.envVarsService.getServiceName();
// ... rest of constructor
}
}
``
Files to be modified:
- Remove
packages/parameters/src/config/EnvironmentVariablesService.ts
entirely - Create
packages/parameters/src/utils/env.ts
with utility functions - Update
packages/parameters/src/base/BaseProvider.ts
to remove envVarsService - Update
packages/parameters/src/base/GetOptions.ts
to use utility functions - Update
packages/parameters/src/base/GetMultipleOptions.ts
to use utility functions - Update
packages/parameters/src/appconfig/AppConfigProvider.ts
to use env utilities
After the refactor, make sure the unit tests are still passing - update the tests as needed.
Acknowledgment
- This request meets Powertools for AWS Lambda (TypeScript) Tenets
- Should this be considered in other Powertools for AWS Lambda languages? i.e. Python, Java, and .NET
Future readers
Please react with 👍 and your use case to help us understand customer demand.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status