-
Notifications
You must be signed in to change notification settings - Fork 4k
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(core): generalization of dependencies #1583
Conversation
CONSTRUCTS NOW TAKE DEPENDENCIES ON OTHER CONSTRUCTS. Before, only `Resource`s could take dependencies, and they would depend on `IDependable`. Instead, we generalize the concept of dependencies from construct trees to other construct trees. The result will be that any resource in the depending tree will depend on all resources in the depended tree. Lazy dependency objects have been removed; they are are now calculated in the prepare() phase. DEPENDENCIES ARE CROSS-STACK AWARE If you take a dependency on a construct in another stack, the dependency does not get rendered in the template, but is instead added as a dependency between stacks. BREAKING CHANGE: `resource.addDependency()` has been moved onto `ConstructNode`. You now write `resource.node.addDependency()`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks nice :) Discussed some concerns about possible impact on change set execution performance (due to inability to parallelize stuff that could have been, only because DependsOn
was over-eager), but I feel it's an okay tradeoff (don't expect this mechanism to be used "often" between large trees). Would nee ncie if we could find a strategy to make this "customizable" by the depended-on construct (so it could scope down to omit the resources that are not interesting to depend on, or where the dependencies are already implied transitively).
*/ | ||
protected readonly defaultPort: string; | ||
public readonly loadBalancerConstructs = new Array<cdk.IConstruct>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should prefix that name with an _
since the user won't need to interact with this directly. Also, probably should be called something like loadBalancerAttachments
or something.
Note: for APIGW, the set of dependencies has increased; we can make those explicit again. Also, we should probably find a good way of naming sets of constructs that are only exposed in order to take dependencies on them. OR find a way to expose those via types. The alternative is we reintroduce |
const ret = new Set<IConstruct>(); | ||
let node: ConstructNode | undefined = this; | ||
while (node !== undefined) { | ||
addSet(ret, node.dependencies); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would have been easier to read if you had just in-lined the loop here.
} | ||
}, | ||
|
||
'cross-stack construct dependencies are not rendered but turned into stack dependencies'(test: Test) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smooth!
@@ -182,5 +168,7 @@ class LatestDeploymentResource extends CfnDeployment { | |||
|
|||
this.lazyLogicalId = this.originalLogicalId + md5.digest("hex"); | |||
} | |||
|
|||
super.prepare(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my note about maybe moving resource dependency calculation to the stack. If you decide to leave Rhiannon here, then add a wee comment on the super call to hint why it Ian needed
* so we add an ordering dependency on the Target Group being associated with | ||
* a Load Balancer. | ||
*/ | ||
public dependOnLoadBalancerAttachment(targetGroup: elbv2.ITargetGroup) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“AddTargetGroup” maybe?
this.natGatewayByAZ[sub.availabilityZone] = sub.addNatGateway(); | ||
const gateway = sub.addNatGateway(); | ||
this.natGatewayByAZ[sub.availabilityZone] = gateway.natGatewayId; | ||
this.natDependencies.push(gateway); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn’t it make more sense to just expose this as “natGateways” ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe. But I don't want to overpromise; they were there to be able to take a dependency on, nothing more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a legitimate API to expose the NAT gateways and internet gateways within the VPC, no?
*/ | ||
public readonly dependencyElements: IDependable[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just expose the internet gateway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's actually not the gateway, it's the attachment of the gateway to the VPC.
@@ -33,14 +33,24 @@ export class Stack extends Construct { | |||
* @returns The Stack object (throws if the node is not part of a Stack-rooted tree) | |||
*/ | |||
public static find(scope: IConstruct): Stack { | |||
const curr = Stack.tryFind(scope); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s an unsafe breaking change... maybe we can’t safely
Make it without checking all call sites...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a breaking change? find()
used to throw and still throws, tryFind()
does not throw.
/** | ||
* Return all dependencies registered on this node and its ancestors. | ||
*/ | ||
public myDependencies(): IConstruct[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder the api can be more explicit and/or rich to indicate that it returns both explicit and implicit dependencies (maybe “findDependencies(includeImpicit = true)”
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can, but there's no use for it (at least today). Future proofing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just don't like the fact that it returns implicit dependencies without that reflecting in the API. It could be findImplicitAndExplicitDependencies()
but findDependencies({ implicit: true })
feels a bit less ugly.
...I am not sure about the "implicit" terminology. Maybe "inherited" or "derived" or something like that...
protected prepare() { | ||
super.prepare(); | ||
|
||
// As an optimization, do the stack dependencies first on the parents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make more sense perhaps to do this globally at the stack instead of distribute to each resource?
I'm having one more issue with this PR at the moment. Representing a "bundle of dependencies"Relevant in the following cases:
Right now, I'm representing this as an I'm thinking of reintroducing interface IDependable {
readonly dependencyElements: IConstruct[];
}
interface Construct extends IDependable {
public readonly dependencyElements = [this];
}
class DependableSet implements IDependable {
public add(element: IConstruct) { ... }
} This will allow naive use in most cases: // OLD (IConstruct[])
construct.node.addDependency(otherConstruct);
construct.node.addDependency(...targetGroup.loadBalancerDependencies);
// NEW (IDependable)
construct.node.addDependency(otherConstruct);
construct.node.addDependency(targetGroup.loadBalancerAttached); For the VPC case, instead of exposing both the IGW dependency objects and the NAT
|
@rix0rrr I like the |
Just one question: why do we need |
It would be an |
This blocks #1633 |
Constructs can now take a dependency on any other construct. Before, only `Resource`s could take dependencies, and they would depend on `IDependable` which had to be implemented explicitly. In this change we generalize the concept of dependencies from construct trees to other construct trees; all constructs now take dependencies and also implement `IDependable`. The semantics are that any resource in the depending tree will depend on all resources in the depended tree. Dependencies are cross-stack aware If you take a dependency on a construct in another stack, the dependency does not get rendered in the template, but is instead added as a dependency between stacks. Fixes aws#1568, fixes aws#95. BREAKING CHANGE: `resource.addDependency()` has been moved onto `ConstructNode`. You now write `resource.node.addDependency()`. VPC's `internetDependency` has been moved to the subnets as `internetConnectivityEstablished`. Target Group's `loadBalancerAssociationDependencies` has been renamed to `loadBalancerAttached`.
CONSTRUCTS NOW TAKE DEPENDENCIES ON OTHER CONSTRUCTS.
Before, only
Resource
s could take dependencies, and they would dependon
IDependable
. Instead, we generalize the concept of dependenciesfrom construct trees to other construct trees. The result will be that
any resource in the depending tree will depend on all resources in the
depended tree.
Lazy dependency objects have been removed; they are are now calculated
in the prepare() phase.
DEPENDENCIES ARE CROSS-STACK AWARE
If you take a dependency on a construct in another stack, the dependency
does not get rendered in the template, but is instead added as a
dependency between stacks.
BREAKING CHANGE:
resource.addDependency()
has been moved ontoConstructNode
. You now writeresource.node.addDependency()
.Fixes #1568, fixes #95.
Pull Request Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license.