From 2ba5ad25f65b7c75556bc33fdb9e8934ed71c799 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 30 May 2019 10:50:30 +0200 Subject: [PATCH] fix(core): hide `dependencyRoots` from public API (#2668) `IDependable` is now a marker interface, and signals that there is a `DependableTrait` implementation which can be retrieved which contains the actual implementation of `dependencyRoots` for this instance. Fixes #2348. --- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 24 +++--- .../@aws-cdk/aws-lambda/lib/function-base.ts | 1 - packages/@aws-cdk/cdk/lib/construct.ts | 18 ++--- packages/@aws-cdk/cdk/lib/dependency.ts | 73 ++++++++++++++++--- 4 files changed, 83 insertions(+), 33 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 32cabbf516343..4d070293caff2 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1302,23 +1302,25 @@ function describeSelection(placement: SubnetSelection): string { class CompositeDependable implements IDependable { private readonly dependables = new Array(); + constructor() { + const self = this; + cdk.DependableTrait.implement(this, { + get dependencyRoots() { + const ret = []; + for (const dep of self.dependables) { + ret.push(...cdk.DependableTrait.get(dep).dependencyRoots); + } + return ret; + } + }); + } + /** * Add a construct to the dependency roots */ public add(dep: IDependable) { this.dependables.push(dep); } - - /** - * Retrieve the current set of dependency roots - */ - public get dependencyRoots(): IConstruct[] { - const ret = []; - for (const dep of this.dependables) { - ret.push(...dep.dependencyRoots); - } - return ret; - } } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index c60a2fccb97e3..fdd779bd310f6 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -238,7 +238,6 @@ export abstract class FunctionBase extends Resource implements IFunction { action: 'lambda:InvokeFunction', }); }, - dependencyRoots: [], node: this.node, }, }); diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index 900df346bdb09..8bbfc2f4ec0de 100644 --- a/packages/@aws-cdk/cdk/lib/construct.ts +++ b/packages/@aws-cdk/cdk/lib/construct.ts @@ -1,7 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { IAspect } from './aspect'; import { CLOUDFORMATION_TOKEN_RESOLVER, CloudFormationLang } from './cloudformation-lang'; -import { IDependable } from './dependency'; +import { DependableTrait, IDependable } from './dependency'; import { resolve } from './resolve'; import { createStackTrace } from './stack-trace'; import { Token } from './token'; @@ -537,7 +537,7 @@ export class ConstructNode { for (const source of this.findAll()) { for (const dependable of source.node.dependencies) { - for (const target of dependable.dependencyRoots) { + for (const target of DependableTrait.get(dependable).dependencyRoots) { let foundTargets = found.get(source); if (!foundTargets) { found.set(source, foundTargets = new Set()); } @@ -594,14 +594,6 @@ export class Construct implements IConstruct { */ public readonly node: ConstructNode; - /** - * The set of constructs that form the root of this dependable - * - * All resources under all returned constructs are included in the ordering - * dependency. - */ - public readonly dependencyRoots: IConstruct[] = [this]; - /** * Creates a new construct node. * @@ -612,6 +604,12 @@ export class Construct implements IConstruct { */ constructor(scope: Construct, id: string) { this.node = new ConstructNode(this, scope, id); + + // Implement IDependable privately + const self = this; + DependableTrait.implement(this, { + get dependencyRoots() { return [self]; }, + }); } /** diff --git a/packages/@aws-cdk/cdk/lib/dependency.ts b/packages/@aws-cdk/cdk/lib/dependency.ts index 30a2137ae4590..a62ce86f49285 100644 --- a/packages/@aws-cdk/cdk/lib/dependency.ts +++ b/packages/@aws-cdk/cdk/lib/dependency.ts @@ -1,7 +1,10 @@ import { IConstruct } from "./construct"; /** - * A set of constructs that can be depended upon + * Trait marker for classes that can be depended upon + * + * The presence of this interface indicates that an object has + * an `IDependableTrait` implementation. * * This interface can be used to take an (ordering) dependency on a set of * constructs. An ordering dependency implies that the resources represented by @@ -9,13 +12,7 @@ import { IConstruct } from "./construct"; * deployed. */ export interface IDependable { - /** - * The set of constructs that form the root of this dependable - * - * All resources under all returned constructs are included in the ordering - * dependency. - */ - readonly dependencyRoots: IConstruct[]; + // Empty, this interface is a trait marker } /** @@ -27,17 +24,71 @@ export interface IDependable { export class ConcreteDependable implements IDependable { private readonly _dependencyRoots = new Array(); + constructor() { + const self = this; + DependableTrait.implement(this, { + get dependencyRoots() { return self._dependencyRoots; }, + }); + } + /** * Add a construct to the dependency roots */ public add(construct: IConstruct) { this._dependencyRoots.push(construct); } +} +/** + * Trait for IDependable + * + * Traits are interfaces that are privately implemented by objects. Instead of + * showing up in the public interface of a class, they need to be queried + * explicitly. This is used to implement certain framework features that are + * not intended to be used by Construct consumers, and so should be hidden + * from accidental use. + * + * @example + * + * // Usage + * const roots = DependableTrait.get(construct).dependencyRoots; + * + * // Definition + * DependableTrait.implement(construct, { + * get dependencyRoots() { return []; } + * }); + */ +export abstract class DependableTrait { /** - * Retrieve the current set of dependency roots + * Register `instance` to have the given DependableTrait + * + * Should be called in the class constructor. */ - public get dependencyRoots(): IConstruct[] { - return this._dependencyRoots; + public static implement(instance: IDependable, trait: DependableTrait) { + // I would also like to reference classes (to cut down on the list of objects + // we need to manage), but we can't do that either since jsii doesn't have the + // concept of a class reference. + DependableTrait.traitMap.set(instance, trait); } + + /** + * Return the matching DependableTrait for the given class instance. + */ + public static get(instance: IDependable): DependableTrait { + const ret = DependableTrait.traitMap.get(instance); + if (!ret) { + throw new Error(`${instance} does not implement DependableTrait`); + } + return ret; + } + + private static traitMap = new WeakMap(); + + /** + * The set of constructs that form the root of this dependable + * + * All resources under all returned constructs are included in the ordering + * dependency. + */ + public abstract readonly dependencyRoots: IConstruct[]; } \ No newline at end of file