Skip to content

Commit

Permalink
chore: refactor static setConnectionProvider method (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
sophia-bq authored Nov 22, 2024
1 parent 0ab384b commit 3f8f6ca
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 133 deletions.
3 changes: 2 additions & 1 deletion common/lib/aws_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ConnectionProviderManager } from "./connection_provider_manager";
import { DefaultTelemetryFactory } from "./utils/telemetry/default_telemetry_factory";
import { TelemetryFactory } from "./utils/telemetry/telemetry_factory";
import { DriverDialect } from "./driver_dialect/driver_dialect";
import { WrapperProperties } from "./wrapper_property";

export abstract class AwsClient extends EventEmitter {
private _defaultPort: number = -1;
Expand Down Expand Up @@ -63,7 +64,7 @@ export abstract class AwsClient extends EventEmitter {
this.pluginManager = new PluginManager(
container,
this.properties,
new ConnectionProviderManager(new DriverConnectionProvider(), null),
new ConnectionProviderManager(new DriverConnectionProvider(), WrapperProperties.CONNECTION_PROVIDER.get(this.properties)),
this.telemetryFactory
);
}
Expand Down
39 changes: 1 addition & 38 deletions common/lib/connection_provider_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
import { ConnectionProvider } from "./connection_provider";
import { HostRole } from "./host_role";
import { HostInfo } from "./host_info";
import { CanReleaseResources } from "./can_release_resources";

export class ConnectionProviderManager {
private static connProvider: ConnectionProvider | null = null;
private readonly defaultProvider: ConnectionProvider;
private readonly effectiveProvider: ConnectionProvider | null;

Expand All @@ -29,19 +27,11 @@ export class ConnectionProviderManager {
this.effectiveProvider = effectiveProvider;
}

static setConnectionProvider(connProvider: ConnectionProvider) {
ConnectionProviderManager.connProvider = connProvider;
}

getConnectionProvider(hostInfo: HostInfo | null, props: Map<string, any>): ConnectionProvider {
if (hostInfo === null) {
return this.defaultProvider;
}

if (ConnectionProviderManager.connProvider?.acceptsUrl(hostInfo, props)) {
return ConnectionProviderManager.connProvider;
}

if (this.effectiveProvider && this.effectiveProvider.acceptsUrl(hostInfo, props)) {
return this.effectiveProvider;
}
Expand All @@ -50,22 +40,11 @@ export class ConnectionProviderManager {
}

acceptsStrategy(role: HostRole, strategy: string) {
return (
ConnectionProviderManager.connProvider?.acceptsStrategy(role, strategy) ||
this.effectiveProvider?.acceptsStrategy(role, strategy) ||
this.defaultProvider.acceptsStrategy(role, strategy)
);
return this.effectiveProvider?.acceptsStrategy(role, strategy) || this.defaultProvider.acceptsStrategy(role, strategy);
}

getHostInfoByStrategy(hosts: HostInfo[], role: HostRole, strategy: string, props: Map<string, any>) {
let host;
if (ConnectionProviderManager.connProvider?.acceptsStrategy(role, strategy)) {
try {
host = ConnectionProviderManager.connProvider.getHostInfoByStrategy(hosts, role, strategy, props);
} catch {
// Ignore and try with other providers.
}
}

if (this.effectiveProvider?.acceptsStrategy(role, strategy)) {
try {
Expand All @@ -81,20 +60,4 @@ export class ConnectionProviderManager {

return host;
}

static async releaseResources(): Promise<any> {
if (ConnectionProviderManager.connProvider !== null) {
if (this.implementsCanReleaseResources(ConnectionProviderManager.connProvider)) {
await ConnectionProviderManager.connProvider.releaseResources();
}
}
}

private static implementsCanReleaseResources(connectionProvider: any): connectionProvider is CanReleaseResources {
return connectionProvider.releaseResources !== undefined;
}

static resetProvider() {
this.connProvider = null;
}
}
7 changes: 7 additions & 0 deletions common/lib/wrapper_property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
limitations under the License.
*/

import { ConnectionProvider } from "./connection_provider";

export class WrapperProperty<T> {
name: string;
description: string;
Expand Down Expand Up @@ -63,6 +65,11 @@ export class WrapperProperties {
static readonly HOST = new WrapperProperty<string>("host", "Database host", null);

static readonly DIALECT = new WrapperProperty<string>("dialect", "A unique identifier for the supported database dialect.", null);
static readonly CONNECTION_PROVIDER = new WrapperProperty<ConnectionProvider>(
"connectionProvider",
"The connection provider used to create connections.",
null
);

static readonly INTERNAL_QUERY_TIMEOUT = new WrapperProperty<number>(
"mysqlQueryTimeout",
Expand Down
4 changes: 2 additions & 2 deletions docs/development-guide/Pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ For information on how to subscribe to these pipelines, please see the documenta

## Connect Pipeline

The connect pipeline performs any additional setup or post connection steps required to establish a connection. By default, the connect pipeline will establish connections using the `DriverConnectionProvider` class. If you would like to use a non-default `ConnectionProvider` to create connections, you can do so by calling `ConnectionProviderManager.setConnectionProvider(new CustomConnectionProvider())`.
The connect pipeline performs any additional setup or post connection steps required to establish a connection. By default, the connect pipeline will establish connections using the `DriverConnectionProvider` class. If you would like to use a non-default `ConnectionProvider` to create connections, you can do so by setting the connection property `connectionProvider: new CustomConnectionProvider()`.

The wrapper provides a custom connection provider called `InternalPooledConnectionProvider`. This provider creates pooled clients and are intended to be used with the Read/Write Splitting plugin. To learn more about this provider, see the internal connection pool section in [UsingTheReadWriteSplittingPlugin.md](https://github.com/aws/aws-advanced-nodejs-wrapper/blob/main/docs/using-the-nodejs-wrapper/using-plugins/UsingTheReadWriteSplittingPlugin.md).

Expand All @@ -32,7 +32,7 @@ An example would be the IAM connection plugin. The IAM connection plugin generat

## Force Connect Pipeline

The force connect pipeline is similar to the connect pipeline except that it will use the default `DriverConnectionProvider` class to establish connections regardless of whether a non-default `ConnectionProvider` has been requested via `ConnectionProviderManager.setConnectionProvider(new CustomConnectionProvider())`. For most plugins, the connect and force connect implementation will be equivalent.
The force connect pipeline is similar to the connect pipeline except that it will use the default `DriverConnectionProvider` class to establish connections regardless of whether a non-default `ConnectionProvider` has been requested via the `connectionProvider` connection property. For most plugins, the connect and force connect implementation will be equivalent.

## Execute Pipeline

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The Read/Write Splitting Plugin is not currently supported for non-Aurora cluste
## Internal Connection Pooling

> [!WARNING]
> If internal connection pools are enabled, database passwords may not be verified with every connection request. The initial connection request for each database instance in the cluster will verify the password, but subsequent requests may return a cached pool connection without re-verifying the password. This behavior is inherent to the nature of connection pools in general and not a bug with the wrapper. `await ConnectionProviderManager.releaseResources()` can be called to close all pools and remove all cached pool connections. See [Internal Connection Pool Password Warning Example for Postgres](../../../examples/aws_driver_example/aws_interal_connection_pool_password_warning_postgres_example.ts) and [Internal Connection Pool Password Warning Example for MySQL](../../../examples/aws_driver_example/aws_internal_connection_pool_password_warning_mysql_example.ts)
> If internal connection pools are enabled, database passwords may not be verified with every connection request. The initial connection request for each database instance in the cluster will verify the password, but subsequent requests may return a cached pool connection without re-verifying the password. This behavior is inherent to the nature of connection pools in general and not a bug with the wrapper. `await <name-of-ConnectionProvider>.releaseResources()` can be called to close all pools and remove all cached pool connections. See [Internal Connection Pool Password Warning Example for Postgres](../../../examples/aws_driver_example/aws_interal_connection_pool_password_warning_postgres_example.ts) and [Internal Connection Pool Password Warning Example for MySQL](../../../examples/aws_driver_example/aws_internal_connection_pool_password_warning_mysql_example.ts)
Whenever `await setReadOnly(true)` is first called on a `AwsClient` object, the read/write plugin will internally open a new physical connection to a reader. After this first call, the physical reader connection will be cached for the given `AwsClient`. Future calls to `setReadOnly` on the same `AwsClient` object will not require opening a new physical connection. However, calling `await setReadOnly(true)` for the first time on a new `AwsClient` object will require the plugin to establish another new physical connection to a reader. If your application frequently calls `setReadOnly`, you can enable internal connection pooling to improve performance. When enabled, the wrapper driver will maintain an internal connection pool for each instance in the cluster. This allows the read/write splitting plugin to reuse connections that were established by `setReadOnly` calls on previous `AwsClient` objects.

Expand Down Expand Up @@ -91,22 +91,22 @@ const myPoolKeyFunc: InternalPoolMapping = {
};
const poolConfig = new AwsPoolConfig({ maxConnections: 10, idleTimeoutMillis: 10000 });
const provider = new InternalPooledConnectionProvider(poolConfig, myPoolKeyFunc);
ConnectionProviderManager.setConnectionProvider(provider);
props.set("connectionProvider", provider);
```

> [!WARNING]
> If you do not include the username in your InternalPoolMapping function, connection pools may be shared between different users. As a result, an initial connection established with a privileged user may be returned to a connection request with a lower-privilege user without re-verifying credentials. This behavior is inherent to the nature of connection pools in general and not a bug with the driver. `await ConnectionProviderManager.releaseResources()` can be called to close all pools and remove all cached pool connections.
> If you do not include the username in your InternalPoolMapping function, connection pools may be shared between different users. As a result, an initial connection established with a privileged user may be returned to a connection request with a lower-privilege user without re-verifying credentials. This behavior is inherent to the nature of connection pools in general and not a bug with the driver. `await provider.releaseResources()` can be called to close all pools and remove all cached pool connections.
2. Call `ConnectionProviderManager.setConnectionProvider()`, passing in the `InternalPoolConnectionProvider` you created in Step 1.
2. Set the `connectionProvider` connection property, passing in the `InternalPoolConnectionProvider` you created in Step 1.

3. By default, the read/write plugin randomly selects a reader instance the first time that `await setReadOnly(true)` is called. If you would like the plugin to select a reader based on a different selection strategy, please see the [Reader Selection](#reader-selection) section for more information.

4. Continue as normal: create connections and use them as needed.

5. When you are finished using all connections, call `await ConnectionProviderManager.releaseResources()`.
5. When you are finished using all connections, call `await provider.releaseResources()`.

> [!IMPORTANT]
> You must call `await ConnectionProviderManager.releaseResources()` to close the internal connection pools when you are finished using all connections. Unless `await ConnectionProviderManager.releaseResources()` is called, the wrapper driver will keep the pools open so that they can be shared between connections.
> You must call `await provider.releaseResources()` to close the internal connection pools when you are finished using all connections. Unless `await ConnectionProviderManager.releaseResources()` is called, the wrapper driver will keep the pools open so that they can be shared between connections.
### Reader Selection

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@ const wrongPassword = "wrong_password";
const database = "database";
const port = 5432;

/**
* Configure read-write splitting to use internal connection pools (the pool config and mapping
* parameters are optional, see UsingTheReadWriteSplittingPlugin.md for more info).
*/
const provider = new InternalPooledConnectionProvider();

const client = new AwsPGClient({
host: postgresHost,
port: port,
user: username,
password: correctPassword,
database: database,
plugins: "readWriteSplitting"
plugins: "readWriteSplitting",
connectionProvider: provider
});

/**
* Configure read-write splitting to use internal connection pools (the pool config and mapping
* parameters are optional, see UsingTheReadWriteSplittingPlugin.md for more info).
*/
const provider = new InternalPooledConnectionProvider();
ConnectionProviderManager.setConnectionProvider(provider);

// Create an internal connection pool with the correct password
try {
await client.connect();
Expand Down Expand Up @@ -72,7 +72,7 @@ try {
await newClient.end();
}
// Closes all pools and removes all cached pool connections.
await ConnectionProviderManager.releaseResources();
await provider.releaseResources();

const newClient2 = new AwsPGClient({
host: postgresHost,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@ const wrongPassword = "wrong_password";
const database = "database";
const port = 3306;

/**
* Configure read-write splitting to use internal connection pools (the pool config and mapping
* parameters are optional, see UsingTheReadWriteSplittingPlugin.md for more info).
*/
const provider = new InternalPooledConnectionProvider();

const client = new AwsMySQLClient({
host: mysqlHost,
port: port,
user: username,
password: correctPassword,
database: database,
plugins: "readWriteSplitting"
plugins: "readWriteSplitting",
connectionProvider: provider
});

/**
* Configure read-write splitting to use internal connection pools (the pool config and mapping
* parameters are optional, see UsingTheReadWriteSplittingPlugin.md for more info).
*/
const provider = new InternalPooledConnectionProvider();
ConnectionProviderManager.setConnectionProvider(provider);

// Create an internal connection pool with the correct password
try {
await client.connect();
Expand Down Expand Up @@ -72,7 +72,7 @@ try {
await newClient.end();
}
// Closes all pools and removes all cached pool connections.
await ConnectionProviderManager.releaseResources();
await provider.releaseResources();

const newClient2 = new AwsMySQLClient({
host: mysqlHost,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,6 @@ const password = "employees";
const database = "database";
const port = 3306;

const client = new AwsMySQLClient({
// Configure connection parameters. Enable readWriteSplitting, failover, and efm plugins.
host: mysqlHost,
port: port,
user: username,
password: password,
database: database,
plugins: "readWriteSplitting,failover,efm",

// Optional: PoolKey property value used in internal connection pools
dbUser: "john_smith"
});

/**
* Optional method: only use if configured to use internal connection pools.
* The configuration in these methods are only examples - you can configure as you need in your own code.
Expand All @@ -59,7 +46,20 @@ const myKeyFunc: InternalPoolMapping = {
*/
const poolConfig = new AwsPoolConfig({ maxConnections: 10, maxIdleConnections: 10, idleTimeoutMillis: 10000 });
const provider = new InternalPooledConnectionProvider(poolConfig, myKeyFunc);
ConnectionProviderManager.setConnectionProvider(provider);

const client = new AwsMySQLClient({
// Configure connection parameters. Enable readWriteSplitting, failover, and efm plugins.
host: mysqlHost,
port: port,
user: username,
password: password,
database: database,
plugins: "readWriteSplitting,failover,efm",

// Optional: PoolKey property value and connection provider used in internal connection pools.
connectionProvider: provider,
dbUser: "john_smith"
});

// Setup Step: Open connection and create tables - uncomment this section to create table and test values.
/* try {
Expand Down Expand Up @@ -108,7 +108,7 @@ try {
await client.end();

// If configured to use internal connection pools, close them here.
await ConnectionProviderManager.releaseResources();
await provider.releaseResources();
}

async function setInitialSessionSettings(client: AwsMySQLClient) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,6 @@ const password = "employees";
const database = "database";
const port = 5432;

const client = new AwsPGClient({
// Configure connection parameters. Enable readWriteSplitting, failover, and efm plugins.
host: postgresHost,
port: port,
user: username,
password: password,
database: database,
plugins: "readWriteSplitting,failover,efm",

// Optional: PoolKey property value used in internal connection pools.
dbUser: "john_smith"
});

/**
* Optional methods: only required if configured to use internal connection pools.
* The configuration in these methods are only examples - you can configure as you needed in your own code.
Expand All @@ -59,7 +46,20 @@ const myPoolKeyFunc: InternalPoolMapping = {
*/
const poolConfig = new AwsPoolConfig({ maxConnections: 10, maxIdleConnections: 10, idleTimeoutMillis: 10000, allowExitOnIdle: true });
const provider = new InternalPooledConnectionProvider(poolConfig, myPoolKeyFunc);
ConnectionProviderManager.setConnectionProvider(provider);

const client = new AwsPGClient({
// Configure connection parameters. Enable readWriteSplitting, failover, and efm plugins.
host: postgresHost,
port: port,
user: username,
password: password,
database: database,
plugins: "readWriteSplitting,failover,efm",

// Optional: PoolKey property value and connection provider used in internal connection pools.
connectionProvider: provider,
dbUser: "john_smith"
});

// Setup Step: Open connection and create tables - uncomment this section to create table and test values.
/* try {
Expand Down Expand Up @@ -108,7 +108,7 @@ try {
await client.end();

// If configured to use internal connection pools, close them here.
await ConnectionProviderManager.releaseResources();
await provider.releaseResources();
}

async function setInitialSessionSettings(client: AwsPGClient) {
Expand Down
Loading

0 comments on commit 3f8f6ca

Please sign in to comment.