Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rds): unify ParameterGroup and ClusterParameterGroup #8959

Merged
merged 4 commits into from
Jul 16, 2020

Conversation

skinny85
Copy link
Contributor

@skinny85 skinny85 commented Jul 9, 2020

The classes ParameterGroup and ClusterParameterGroup were basically identical;
they only differed in the L1 that was created in their scope.
Unify the classes by adding a bind()-like protocol between ParameterGroup and cluster/instance,
which allows us to only have a single class instead of 2.

Fixes #8932

BREAKING CHANGE: the class ClusterParameterGroup has been removed -
use ParameterGroup instead


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@skinny85 skinny85 requested review from njlynch and shivlaks July 9, 2020 02:01
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Jul 9, 2020
@skinny85 skinny85 self-assigned this Jul 9, 2020
@skinny85 skinny85 added the pr-linter/exempt-readme The PR linter will not require README changes label Jul 9, 2020
packages/@aws-cdk/aws-rds/lib/cluster.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-rds/lib/cluster.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-rds/lib/cluster.ts Show resolved Hide resolved
*/
protected parameters?: { [key: string]: string } = {};
bindToCluster(options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question: What's the advantage of requiring an empty interface as a parameter to the call? You wouldn't be able to add a new required member to that interface without it being a breaking change. I get it's future-proofing, but doesn't seem to add any more backwards compatibility than just omitting the options for now and making an optional options parameter in the future when needed. Am I missing a non-obvious advantage to this approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mainly for other languages that we support that don't have optional parameters (Java comes to mind).

I don't think making the options optional in the bindToCluster() method is a good idea - makes it very inconvenient to actually implement it (you have to constantly check for null, which is even worse in languages like Java).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK on the null checks. You're still going to have to either (a) make a breaking change or (b) introduce an optional member to the options (which will need a null check). I guess it's the over/under on that degree of awkwardness vs the awkwardness of creating an empty object that (currently) serves no purpose. Again, I'm thinking of the poor Java dev who has to write new ParameterGroupClusterBindOptions().

However, I don't have super-strong feelings either way, just wanting to understand your perspective on the general principle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK on the null checks. You're still going to have to either (a) make a breaking change or (b) introduce an optional member to the options (which will need a null check).

Right. But making it optional would require 2 null checks! Which is twice as irritating 🙂 (Even with an optional parameter, you can't add a required property to ParameterGroupClusterBindOptions without breaking backwards compatibility).

I guess it's the over/under on that degree of awkwardness vs the awkwardness of creating an empty object that (currently) serves no purpose. Again, I'm thinking of the poor Java dev who has to write new ParameterGroupClusterBindOptions().

But here's the beauty of this - a Java dev will never have to create a ParameterGroupClusterBindOptions! Notice that bindToCluster and bindToInstance are only called in Cluster and Instance. So while you might conceivably want to implement bindToCluster() / bindToInstance(), you won't have to call it unless you're doing something really, really crazy (at which point creating an empty ParameterGroupClusterBindOptions is the least of your problems 😉).

Does this make more sense now in how is this approached here?


public bindToCluster(_options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig {
if (!this.clusterCfnGroup) {
const parentScope = this.instanceCfnGroup ?? this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This maybe deserves a comment. Why parent the scope of one param group onto the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because I want both of the Cfn* resources to have the ID 'Resource', but if one of the bind*() methods was already called, that would result in a duplicate identifier in the this scope. Hence this trick.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

¯_(ツ)_/¯ I'd add a comment to that effect, unless this is a super-common pattern I haven't seen before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also thinking - instead of changing the parent, I could change the ID.

So, this would become:

  public bindToCluster(_options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig {
    if (!this.clusterCfnGroup) {
      const resourceId = this.instanceCfnGroup ? 'SecondResource' : 'Resource';
      this.clusterCfnGroup = new CfnDBClusterParameterGroup(this, resourceId, {
        description: this.description || `Cluster parameter group for ${this.family}`,
        family: this.family,
        parameters: Lazy.anyValue({ produce: () => this.parameters }),
      });

Is that more clear @njlynch ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to just do this?

public bindToCluster(...) {
  this.clusterCfnGroup = new CfnDBClusterParameterGroup(this, 'ClusterParamGroup', {
  ...
  });
}
public bindToInstance(...) {
  this.clusterCfnGroup = new CfnDBClusterParameterGroup(this, 'InstanceParamGroup', {
  ...
  });
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i find them both equally readable, but a comment will be handy for posterity as I think it won't be intuitive to a new contributor why we do this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to just do this?

public bindToCluster(...) {
  this.clusterCfnGroup = new CfnDBClusterParameterGroup(this, 'ClusterParamGroup', {
  ...
  });
}
public bindToInstance(...) {
  this.clusterCfnGroup = new CfnDBClusterParameterGroup(this, 'InstanceParamGroup', {
  ...
  });
}

Actually, yes.

Since the existing classes use the Resource IDs, changing them now to 'ClusterParamGroup'/'InstanceParamGroup' would cause a replacement of them in customer's CloudFormation Stacks after they upgrade their version of CDK, which is quite a disruptive change.

Also, the name 'Resource' (and 'Default') is treated a little bit especially - it's the return value from the this.node.defaultChild method (which is often used with escape hatches).

@skinny85 skinny85 requested a review from njlynch July 9, 2020 19:10
@skinny85
Copy link
Contributor Author

skinny85 commented Jul 9, 2020

Rebased on top of #8686 , which was merged.

@skinny85 skinny85 added the pr/do-not-merge This PR should not be merged at this time. label Jul 10, 2020
packages/@aws-cdk/aws-rds/lib/cluster.ts Show resolved Hide resolved
*/
protected parameters?: { [key: string]: string } = {};
bindToCluster(options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK on the null checks. You're still going to have to either (a) make a breaking change or (b) introduce an optional member to the options (which will need a null check). I guess it's the over/under on that degree of awkwardness vs the awkwardness of creating an empty object that (currently) serves no purpose. Again, I'm thinking of the poor Java dev who has to write new ParameterGroupClusterBindOptions().

However, I don't have super-strong feelings either way, just wanting to understand your perspective on the general principle.


public bindToCluster(_options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig {
if (!this.clusterCfnGroup) {
const parentScope = this.instanceCfnGroup ?? this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

¯_(ツ)_/¯ I'd add a comment to that effect, unless this is a super-common pattern I haven't seen before.

Copy link
Contributor

@shivlaks shivlaks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good simplification! added some minor comments

* @param key The key of the parameter to be added
* @param value The value of the parameter to be added
*/
public addParameter(key: string, value: string): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the use case where the return type would be used?
at a cursory glance, most of the addXyZ methods return void or a non-primitive type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but if you're going to return void, you might as well return boolean (I don't see any downsides in making it return boolean compared to void, seems like pure upside to me).


public bindToCluster(_options: ParameterGroupClusterBindOptions): ParameterGroupClusterConfig {
if (!this.clusterCfnGroup) {
const parentScope = this.instanceCfnGroup ?? this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i find them both equally readable, but a comment will be handy for posterity as I think it won't be intuitive to a new contributor why we do this

@skinny85
Copy link
Contributor Author

@njlynch @shivlaks thanks for the review! I wanted to run one more thing by you.

As you see, the most important parameter when creating the ParameterGroup is the family. In general, that is a property of the database engine (either cluster, or instance): parameterGroupFamily. I'm wondering whether it makes more sense for ParameterGroup to take an IEngine in its constructor, if that's the case, similarly to how OptionGroup takes an InstanceEngine today:

readonly engine: IInstanceEngine;

Thoughts on this idea?

@njlynch
Copy link
Contributor

njlynch commented Jul 13, 2020

In general, that is a property of the database engine (either cluster, or instance): parameterGroupFamily. I'm wondering whether it makes more sense for ParameterGroup to take an IInstanceEngine in its constructor

I'm not familiar enough with the RDS API to see how this works. IInstanceEngine doesn't expose the family today, and the various implementations have a mapping of version to families, rather than just a single family, where version is optional. If you accepted an IInstanceEngine instead of the family, you'd need the various engines to be able to select a "default" family in the case where no version was specified. Is that correct?

@skinny85
Copy link
Contributor Author

skinny85 commented Jul 13, 2020

In general, that is a property of the database engine (either cluster, or instance): parameterGroupFamily. I'm wondering whether it makes more sense for ParameterGroup to take an IInstanceEngine in its constructor

I'm not familiar enough with the RDS API to see how this works. IInstanceEngine doesn't expose the family today

Yes, we of course, we would surface parameterGroupFamily in IInstanceEngine for this (actually, thinking about it, we should do it even in this current form of this PR! Because otherwise, it's difficult to know what to pass for family when creating a ParameterGroup even today, and with this, you could use something like DatabaseInstanceEngine.MARIADB.parameterGroupFamily, which makes a lot of sense).

, and the various implementations have a mapping of version to families, rather than just a single family, where version is optional. If you accepted an IInstanceEngine instead of the family, you'd need the various engines to be able to select a "default" family in the case where no version was specified. Is that correct?

Yes, but this the situation that we have today already. For example, if you don't specify a version for the Aurora PostgreSQL Engine, it will default to the parameter group family aurora-postgresql11.

@skinny85 skinny85 force-pushed the feat/rds-paramaeter-group branch 2 times, most recently from cac12ed to 64b1ba2 Compare July 14, 2020 02:07
@skinny85
Copy link
Contributor Author

I've made the change to make ParameterGroup take an IEngine in its construction properties instead of just a parameterGroupFamily. @njlynch @shivlaks would appreciate a look - let me know how you like it!

Copy link
Contributor

@shivlaks shivlaks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the simplification to IEngine
had a couple small comments / questions around them

packages/@aws-cdk/aws-rds/lib/engine.ts Show resolved Hide resolved
packages/@aws-cdk/aws-rds/lib/engine.ts Outdated Show resolved Hide resolved
* When `engineVersion` is specified, return the parameter group family corresponding to that engine version.
* Return undefined if no parameter group family is defined for this engine or for the requested `engineVersion`.
*/
export function calculateParameterGroupFamily(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a hard time reconciling this with #9016. In the other PR, parameterGroupFamilies look to be completely removed; will this function be deleted then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. In that PR, the major version is set explicitly for a specific version (instead of being calculated).

* Return undefined if no parameter group family is defined for this engine or for the requested `engineVersion`.
*/
export function calculateParameterGroupFamily(
parameterGroupFamilies: ParameterGroupFamilyMapping[] | undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(For my own edification): Is there any logical difference between a required parameter that can be undefined vs an optional parameter, especially in cases where all parameters are the same (e.g., no issues with optional parameters needing to be the last ones)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a tiny difference: for a required parameter that can be undefined, you have to always explicitly pass it (even if just the undefined value), while an optional parameter can be skipped from the call altogether.

return undefined;
}
if (engineVersion !== undefined) {
const family = parameterGroupFamilies.find(x => engineVersion.startsWith(x.engineMajorVersion));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this guaranteed to only match one version? If not, is ordering guaranteed? Below, you sort the families by version, which implies this list isn't (guaranteed) sorted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's guaranteed to match one version, provided the engineMajorVersion are unique, and not subsets of each other, but this is the case with the currently used major versions (and it makes sense, because it would be weird if 11 and 11.2 were both major versions ;p).

The classes ParameterGroup and ClusterParameterGroup were basically identical;
they only differed in the L1 that was created in their scope.
Unify the classes by adding a bind()-like protocol between ParameterGroup and cluster/instance,
which allows us to only have a single class instead of 2.

Fixes aws#8932

BREAKING CHANGE: the class ClusterParameterGroup has been removed -
  use ParameterGroup instead
@skinny85 skinny85 removed the pr/do-not-merge This PR should not be merged at this time. label Jul 16, 2020
@mergify
Copy link
Contributor

mergify bot commented Jul 16, 2020

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 545c5cd
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify
Copy link
Contributor

mergify bot commented Jul 16, 2020

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot merged commit 17b690b into aws:master Jul 16, 2020
@skinny85 skinny85 deleted the feat/rds-paramaeter-group branch July 16, 2020 18:33
curtiseppel pushed a commit to curtiseppel/aws-cdk that referenced this pull request Aug 11, 2020
The classes ParameterGroup and ClusterParameterGroup were basically identical;
they only differed in the L1 that was created in their scope.
Unify the classes by adding a bind()-like protocol between ParameterGroup and cluster/instance,
which allows us to only have a single class instead of 2.

Fixes aws#8932

BREAKING CHANGE: the class ClusterParameterGroup has been removed -
  use ParameterGroup instead

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS. pr-linter/exempt-readme The PR linter will not require README changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Finalize the design of ParameterGroup in RDS
4 participants