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

(ecs): small refactor for PortMapping ease of change #24170

Closed
1 of 2 tasks
bun913 opened this issue Feb 14, 2023 · 2 comments · Fixed by #24227
Closed
1 of 2 tasks

(ecs): small refactor for PortMapping ease of change #24170

bun913 opened this issue Feb 14, 2023 · 2 comments · Fixed by #24227
Labels
effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2

Comments

@bun913
Copy link
Contributor

bun913 commented Feb 14, 2023

Describe the feature

Sorry if I have the wrong category for the Issue or the wrong place for the discussion!

While looking at Issue #23509, I was surprised by the following addPortMappings conditional branch.

public addPortMappings(...portMappings: PortMapping[]) {
this.portMappings.push(...portMappings.map(pm => {
if (this.taskDefinition.networkMode === NetworkMode.AWS_VPC || this.taskDefinition.networkMode === NetworkMode.HOST) {
if (pm.containerPort !== pm.hostPort && pm.hostPort !== undefined) {
throw new Error(`Host port (${pm.hostPort}) must be left out or equal to container port ${pm.containerPort} for network mode ${this.taskDefinition.networkMode}`);
}
}
// No empty strings as port mapping names.
if (pm.name === '') {
throw new Error('Port mapping name cannot be an empty string.');
}
// Service connect logic.
if (pm.name || pm.appProtocol) {
// Service connect only supports Awsvpc and Bridge network modes.
if (![NetworkMode.BRIDGE, NetworkMode.AWS_VPC].includes(this.taskDefinition.networkMode)) {
throw new Error(`Service connect related port mapping fields 'name' and 'appProtocol' are not supported for network mode ${this.taskDefinition.networkMode}`);
}
// Name is not set but App Protocol is; this config is meaningless and we should throw.
if (!pm.name) {
throw new Error('Service connect-related port mapping field \'appProtocol\' cannot be set without \'name\'');
}
if (this._namedPorts.has(pm.name)) {
throw new Error(`Port mapping name '${pm.name}' already exists on this container`);
}
this._namedPorts.set(pm.name, pm);
}
if (this.taskDefinition.networkMode === NetworkMode.BRIDGE) {
if (pm.hostPort === undefined) {
pm = {
...pm,
hostPort: 0,
};
}
}
return pm;
}));
}

The ContainerDefinition class is responsible for many of the responsibilities, and many if statements are added to verify PortMapping's sanity.

I feel that by writing down if statements like this, we lose the ease of modification.

Therefore, I would like to refactor it so that many contributors can easily modify it here in a way that does not affect the existing system!

Use Case

ECS is a very widely used service, and I believe it is a service that will continue to see many bug fixes and feature additions.

I think that every time we add more properties in the future, we will make it harder to make changes by making miscellaneous changes!

I want to make it easy for many developers to contribute to this project!

Proposed Solution

I think ContainerDefinition has too many responsibility.

An if statement is written in addPortMappings that assumes all contexts.

public addPortMappings(...portMappings: PortMapping[]) {
this.portMappings.push(...portMappings.map(pm => {
if (this.taskDefinition.networkMode === NetworkMode.AWS_VPC || this.taskDefinition.networkMode === NetworkMode.HOST) {
if (pm.containerPort !== pm.hostPort && pm.hostPort !== undefined) {
throw new Error(`Host port (${pm.hostPort}) must be left out or equal to container port ${pm.containerPort} for network mode ${this.taskDefinition.networkMode}`);
}
}
// No empty strings as port mapping names.
if (pm.name === '') {
throw new Error('Port mapping name cannot be an empty string.');
}
// Service connect logic.
if (pm.name || pm.appProtocol) {
// Service connect only supports Awsvpc and Bridge network modes.
if (![NetworkMode.BRIDGE, NetworkMode.AWS_VPC].includes(this.taskDefinition.networkMode)) {
throw new Error(`Service connect related port mapping fields 'name' and 'appProtocol' are not supported for network mode ${this.taskDefinition.networkMode}`);
}
// Name is not set but App Protocol is; this config is meaningless and we should throw.
if (!pm.name) {
throw new Error('Service connect-related port mapping field \'appProtocol\' cannot be set without \'name\'');
}
if (this._namedPorts.has(pm.name)) {
throw new Error(`Port mapping name '${pm.name}' already exists on this container`);
}
this._namedPorts.set(pm.name, pm);
}
if (this.taskDefinition.networkMode === NetworkMode.BRIDGE) {
if (pm.hostPort === undefined) {
pm = {
...pm,
hostPort: 0,
};
}
}
return pm;
}));
}

For example, this nested if statement is also commented as Sevice Connect logick, but now I don't know what the decision logic is for.

// Service connect logic.
if (pm.name || pm.appProtocol) {
// Service connect only supports Awsvpc and Bridge network modes.
if (![NetworkMode.BRIDGE, NetworkMode.AWS_VPC].includes(this.taskDefinition.networkMode)) {
throw new Error(`Service connect related port mapping fields 'name' and 'appProtocol' are not supported for network mode ${this.taskDefinition.networkMode}`);
}
// Name is not set but App Protocol is; this config is meaningless and we should throw.
if (!pm.name) {
throw new Error('Service connect-related port mapping field \'appProtocol\' cannot be set without \'name\'');
}
if (this._namedPorts.has(pm.name)) {
throw new Error(`Port mapping name '${pm.name}' already exists on this container`);
}
this._namedPorts.set(pm.name, pm);
}
if (this.taskDefinition.networkMode === NetworkMode.BRIDGE) {
if (pm.hostPort === undefined) {
pm = {
...pm,
hostPort: 0,
};
}
}
return pm;
}));

So, I suggest creating a PortMap or similarly named class (I think it would be a Value Object) like below.

export class PortMap {
  readonly portmap: PortMapping;
  readonly namedPorts: Map<string, PortMapping>;
  readonly networkMode: NetworkMode;

  constructor(pm: PortMapping, np: Map<string, PortMapping>, nwm: NetworkMode) {
    this.portmap = pm;
    this.namedPorts = np;
    this.networkMode = nwm;
    this.hasRequiredProp();
    this.canServiceConnect();
  }

  private hasRequiredProp() {
    if ( this.portmap.name == '') {
      throw new Error('Port mapping name cannot be an empty string.');
    }
  }
  private canServiceConnect() {
    // validation
  }
  private addNamedPort() {
    // return new NamedPort(Don'n change curret taskdef propertiy)
  }
}

I thought it would make for a more prospective code by taking the responsibility for checking the sanity of PortMapping related properties out of the picture!

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

CDK version used

2.64.0

Environment details (OS name and version, etc.)

macOS Monterey

@bun913 bun913 added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Feb 14, 2023
@peterwoodworth peterwoodworth changed the title ecs:small refactor for PortMapping ease of change (ecs): small refactor for PortMapping ease of change Feb 14, 2023
@peterwoodworth peterwoodworth removed their assignment Feb 14, 2023
@github-actions github-actions bot added the @aws-cdk/aws-ecs Related to Amazon Elastic Container label Feb 14, 2023
@peterwoodworth peterwoodworth added p2 effort/medium Medium work item – several days of effort and removed @aws-cdk/aws-ecs Related to Amazon Elastic Container needs-triage This issue or PR still needs to be triaged. labels Feb 14, 2023
@peterwoodworth
Copy link
Contributor

Thanks for providing this feedback and your suggestions on how to improve this code after your previous experience of attempting to change it. @madeline-k you have some good context around this library, what do you think about this refactoring suggestion?

@mergify mergify bot closed this as completed in #24227 Mar 1, 2023
mergify bot pushed a commit that referenced this issue Mar 1, 2023
This PR...

- Refactor ecs.ContainerDefintion.addPortMapping method
- The addPortMapping method had many if statements and it was difficult to understand what was being determined.
- It was also difficult to make changes.
- Therefore, I divided the classes by interest to improve visibility.

Closes #24170 .

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@github-actions
Copy link

github-actions bot commented Mar 1, 2023

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

homakk pushed a commit to homakk/aws-cdk that referenced this issue Mar 28, 2023
This PR...

- Refactor ecs.ContainerDefintion.addPortMapping method
- The addPortMapping method had many if statements and it was difficult to understand what was being determined.
- It was also difficult to make changes.
- Therefore, I divided the classes by interest to improve visibility.

Closes aws#24170 .

----

*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
effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants