Package: polycons
PolyconsA framework for building polymorphicconstructs.
Think of polycons like dependency injection for constructs.polycons can be used with any CDK framework, includingAWS CDK,cdktf, andcdk8s.🚀 Getting startedPolycons can be used just like ordinary constructs:import{Dog}from"@acme/shared-polycons";import{Construct}from"constructs";classPetsextendsConstruct{constructor(scope:Construct,id:string){super(scope,id);//thisisapolycon!newDog(this,"Dog",{treats:5});//likeordinaryconstructs,polyconscanhavemethods,properties,etc.dog.giveBone();}}ThisPetsconstruct contains aDogfrom a library of polycons.
The dog could have multiple implementations -- aLabrador, aTerrier, or your own implementation.To use polycons in an application, you need to register a factory that specifies how to turn polycons into concrete constructs.
In the example below, aPetFactoryis used, which has been defined to resolve eachDogin the construct tree into aLabrador.
By registering it to the rootAppconstruct, eachDogin the construct tree will be created as aLabrador.import{App}from"<cdk-framework>";import{PetFactory}from"@acme/shared-polycons";import{Polycons}from"polycons";constapp=newApp();Polycons.register(app,newPetFactory());newPets(app,"MyPets");Check out the usage guide for more details about how to create your own polycons and polycon factories.📖 DocumentationClickhereto visit the polycons API reference.🏭 Polycon factoriesA polycon factory is a class that implements theIPolyconFactoryinterface, which has a singleresolve()method.
This method accepts atypeand a list of construct arguments, and returns a concrete construct. For example:import{DOG_TYPE,CAT_TYPE,Labrador,Kitten}from"@acme/shared-polycons";classPetFactoryimplementsIPolyconFactory{publicresolve(type:string,scope:Construct,id:string,...args:any[]):Construct{switch(type){caseDOG_TYPE:returnnewLabrador(scope,id,...args);caseCAT_TYPE:returnnewKitten(scope,id,...args);default:thrownewError(`Type"${type}"notimplemented.`);}}}In the above example,DOG_TYPEandCAT_TYPEare unique string constants associated with the respective polycons.By customizing theresolve()method, it's possible to change construct IDs, override properties, or even make factories that call other factories.✍️ Creating polyconsYou can define a new polycon by creating a class that returns a new Polycon instance in its constructor.
Each polycon must be associated with a unique identifying string.import{Constructs}from"constructs";import{Polycons}from"polycons";exportinterfaceDogProps{readonlyname?:string;readonlytreats?:number;}//makesureyourpolyconhasagloballyuniquename!exportconstDOG_TYPE="@acme/shared-polycons.Dog";exportclassDogextendsConstruct{constructor(scope:Construct,id:string,props:DogProps){super(nullasany,id);//(1)returnPolycons.newInstance(DOG_TYPE,scope,id,props)asDog;}}TheDogclass definition serves as an empty shell, or placeholder -- only when a user callsnew Dog(), a real construct will be returned.In the constructor ofDog, a null value MUST be passed as the first argument tosuper()(1).
This is because actually two constructs are made by the constructor, and the first one should be thrown away (and not be added to the construct tree).Concrete implementations of a polycon can be written like ordinary constructs:exportclassLabradorextendsConstruct{publicreadonlyname:string;privatereadonlytreats:number;constructor(scope:Construct,id:string,props:DogProps){super(scope,id);this.name=props.name;this.treats=props.treats;}publictoString(){return`Labradorwith${this.treats}treats.`;}}🤝 Sharing behaviorOftentimes, you may want all polycons to share some properties or methods.You can achieve this by defining a base class, and having the polycon extend the base class:exportinterfaceDogProps{readonlyname?:string;readonlytreats?:number;}exportconstDOG_TYPE="@acme/shared-polycons.Dog";//Thisisthepolycon.exportclassDogextendsDogBase{constructor(scope:Construct,id:string,props:DogProps){super(nullasany,id,props);//[1]returnPolycons.newInstance(DOG_TYPE,scope,id,props)asDog;}publictoString(){thrownewError("Method not implemented");//[2]}}//Thisisthebaseclass.exportabstractclassDogBaseextendsConstruct{publicreadonlyspecies="Canis familiaris";publicreadonlytreats:number;constructor(scope:Construct,id:string,props:DogProps){super(scope,id);//[3]if(!scope){this.treats=0;return;}this.treats=props.treats;}publicabstracttoString():string;}Please take note:In the constructor of the polycon (Dog), a null value MUST be passed as the first argument tosuper().Since theDogclass is just an empty shell, and does not get returned to the user, any methods required by theabstractbase class can be left unimplemented.In the constructor of the base class (DogBase), the constructor should have no side effects or mutations when an emptyscopeis passed (otherwise, side effects may occur multiple times). In the example above, we set dummy values when the scope is empty (this.treats = 0;) and return early.When a polycon has a base class, every polycon implementation should extend it instead of extendingConstruct:exportclassLabradorextendsDogBase{publicreadonlyname:string;constructor(scope:Construct,id:string,props:DogProps){super(scope,id,props);this.name=props.name;}publictoString(){return`Labradorwith${this.treats}treats.`;}}✋ ContributingWe welcome community contributions and pull requests. SeeCONTRIBUTING.mdfor information on how to set up a development environment and submit code on GitHub.⚖️ LicenseThis library is licensed under the Apache-2.0 license.