diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index c771e05d9e8af..21437c3551a78 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 { Token } from './token'; import { makeUniqueId } from './uniqueid'; @@ -521,7 +521,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()); } @@ -578,14 +578,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. * @@ -596,6 +588,11 @@ export class Construct implements IConstruct { */ constructor(scope: Construct, id: string) { this.node = new ConstructNode(this, scope, id); + + 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..b9407b7d32198 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,61 @@ 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. + */ +export abstract class DependableTrait { + /** + * Register `instance` to have the given DependableTrait + * + * Should be called in the class constructor. + */ + 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); + } /** - * Retrieve the current set of dependency roots + * Return the matching DependableTrait for the given class instance. */ - public get dependencyRoots(): IConstruct[] { - return this._dependencyRoots; + 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