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

Use of "private" properties prevents multiple versions of a module in the same closure #979

Closed
eladb opened this issue Oct 21, 2018 · 16 comments · Fixed by #1091
Closed
Assignees
Labels
bug This issue is a bug.

Comments

@eladb
Copy link
Contributor

eladb commented Oct 21, 2018

Due the fact that when a class has private/protected members, TypeScript uses nominal type checking instead of structural type checking, different versions of the same class would not be compatible.

See details and example in microsoft/TypeScript#28128.

Bottom line, it appears that using "private" members is not practical for class libraries since it means classes cannot be used across versions and it is a common use case for ecosystems to have multiple versions of the same class in an application's closure.

To ensure that this cannot happen with the CDK and AWS Construct Library, I am proposing that jsii will not allow the use of TypeScript's private modifier. Instead it will recommend that users prefix a member with an underscore _, which will be hidden from jsii languages and is a common practice in JavaScript to denote private members.

The next step to enable multiple major versions is to allow jsii-runtime to load multiple versions into a module's closure. At the moment it is prohibited (aws/jsii#61).

@eladb eladb added the bug This issue is a bug. label Oct 21, 2018
@rix0rrr
Copy link
Contributor

rix0rrr commented Oct 22, 2018

I imagine it's just the typechecker that's affected, so shouldn't have any impact on jsii.

But this is a supposition :).

@eladb
Copy link
Contributor Author

eladb commented Oct 23, 2018

@rix0rrr you are right, but the current implementation of the jsii-runtime has a limitation (aws/jsii#61) that won't allow multiple versions of the same module to be loaded into the runtime, so in a sense those are related.

@eladb
Copy link
Contributor Author

eladb commented Oct 23, 2018

Related: microsoft/TypeScript#8346

@eladb
Copy link
Contributor Author

eladb commented Oct 23, 2018

Here's a repro:

repro.zip

@eladb
Copy link
Contributor Author

eladb commented Oct 23, 2018

Another discussion on the topic: microsoft/TypeScript#7755

Basically, private members do not participate in structural typing intentionally in order to protect from overriding private implementation.

As much as I can see the purist reasoning behind it (and it seems like the TypeScript team doesn't intend to change things), we need a solution that will allow multiple versions of @aws-cdk/cdk to work together. This is especially tricky around base classes like Construct and Resource.

To be pragmatic about this, I'd like to propose that for these base classes, we will not use private members, but rather just use _ prefix to indicate those members are private. This is not an uncommon convention in JavaScript and jsii will hide these member from native languages.

@rix0rrr @RomainMuller I wonder what you guys think about this.

@rix0rrr
Copy link
Contributor

rix0rrr commented Oct 23, 2018

I HATE EVERYTHING ABOUT IT.

But seriously.

I'd like to see automation around this, and preferably I wouldn't have to defile my code to work around these kinds of issues. I would like to see automatic translation of private xxx() to public _xxx() at the compilation phase. Either by postprocessing on the generated .js and .d.ts files, or by writing a compiler plugin.

@eladb
Copy link
Contributor Author

eladb commented Oct 23, 2018

I agree. It’s a huge limitation of private members in typescript, which I am not 100% sure they realize. I’ll chime in on one of these conversations to present our use case and ask for guidance.

In the meantime, we could either block the usage of private members at the Jsii level (tell you to use underscore and explain why) or just solve this locally for cdk.Construct and other core base classes, so that we could start versioning modules separately.

@eladb eladb self-assigned this Oct 30, 2018
@eladb eladb changed the title Potential issue with multiple versions of @aws-cdk/cdk Potential issue with multiple versions of CDK modules Oct 30, 2018
@eladb eladb changed the title Potential issue with multiple versions of CDK modules Use of "private" properties prevents multiple versions of a module in the same closure Oct 31, 2018
@RomainMuller
Copy link
Contributor

I share @rix0rrr's philosophical issue with this. It is uncanny to me that I cannot use privates as pure implementation details. However, Javascript is what it is, and from that perspective I guess we have to preserve usability of the CDK.

@eladb
Copy link
Contributor Author

eladb commented Oct 31, 2018

This is actually a Typescript thing. Whether you use the CDK or not, if you wanted to use TypeScript to vend a class library, you wouldn't want to use "private" properties. I know, it's weird.

@yandy-r
Copy link

yandy-r commented Oct 31, 2018

I don't like it, but a limitation of the language is a limitation. Based on the issue MS/Typescript rep adding interfaces for all types seems like a major headache. I'm not sure how my OCD will handle the _ everywhere, but improving functionality and practicality right now to get real use cases to me is more important than a few semantics.

I do agree with @rix0rrr that maybe in the future some logic and automation around this would be useful, though for now, your proposal seems fair.

@christophercurrie
Copy link

What does embracing the recommendation of using interfaces look like? The CDK could still provide base classes, but arguments themselves would always take the interface. It's a huge pain for the library to rename all the arguments, but it appears the cleanest from both the end user and the cross-language perspective.

@eladb
Copy link
Contributor Author

eladb commented Oct 31, 2018

The implication is that we will have to define an interface for every exported class in every module and somehow enforce that only those interfaces are used to reference these classes... doesn’t seem realistic

@eladb
Copy link
Contributor Author

eladb commented Oct 31, 2018

@huijbers proposed that the only reason typescript actually exposes private properties in type declarations is to allow objects to reference private properties of other objects of the same type. This was also mentioned in one of the typescript discussions (microsoft/TypeScript#7755 (comment)), another reason was to ensure that derived classes do not override the private property by accident.

@huijbers proposed to modify the typescrip compiler and add a mode which won’t private propert declarations, and perhaps can even enforce accessing private properties that are not via “this”.

What do you guys think? Maybe we can propose this as a contribution to typescript?

@christophercurrie
Copy link

Can you provide a link to that proposal? I can see it being valuable for some use cases.

A related question would be, what are private members being used for, and is there a way to eliminate them altogether, perhaps by making more types immutable?

@eladb
Copy link
Contributor Author

eladb commented Nov 5, 2018

In a conversation with @rix0rrr, he suggested that perhaps the correct modeling for dependencies that are exposed outside of a module's API is to use peerDependencies instead of normal dependencies, and I tend to agree.

For jsii code (CDK, AWS Construct Library, etc), we could enforce this at the jsii level: if a module's public API includes types from external modules, jsii can require that those are defined as peerDependencies instead of normal dependencies.

The problem is that we cannot enforce this for modules written by 3rd parties. We can recommend, guide and also make sure our templates are using peerDependencies for things like @aws-cdk/cdk (which by definition is exposed in the API), but we can't really enforce it as far as I know.

@eladb
Copy link
Contributor Author

eladb commented Nov 6, 2018

Indeed peerDependencies seem like the correct mechanism, but we from this article it seems like we must specify any of these dependencies both as "peerDependencies" and normal "dependencies". This will ensure that if a peer is not installed, npm (starting from version 3) will install it for you. If you only specify dependencies as peers, it means npm will require that you install all peers manually, and that's not sustainable.

eladb pushed a commit that referenced this issue Nov 6, 2018
Adds "peerDependencies" to all modules for all dependencies that
include types that are used in the module's public API.

Also, add "@aws-cdk/cdk" to "peerDependencies" for the typescript 
library cdk-init template.

This will be enforced by jsii when aws/jsii#1090 is introduced.
Prerequisite for #272
Fixes #979
eladb pushed a commit that referenced this issue Nov 6, 2018
Adds "peerDependencies" to all modules for all dependencies that
include types that are used in the module's public API.

Also, add "@aws-cdk/cdk" to "peerDependencies" for the typescript
library cdk-init template.

This will be enforced by jsii when aws/jsii#292 is introduced.
Prerequisite for #272
Fixes #979
eladb pushed a commit that referenced this issue Nov 6, 2018
Adds "peerDependencies" to all modules for all dependencies that
include types that are used in the module's public API.

Also, add "@aws-cdk/cdk" to "peerDependencies" for the typescript
library cdk-init template.

This will be enforced by jsii when aws/jsii#292 is introduced.
Prerequisite for #272
Fixes #979
eladb pushed a commit to aws/jsii that referenced this issue Nov 6, 2018
If a jsii module exposes a type from a dependency in it's public API,
jsii now enforces that this dependency is also defined as a "peer"
instead of a normal dependency.

This tells npm that if a user of this module already
installed a compatible verison of this dependency in their
closure, npm will pick the one installed by the user (as a peer)
instead of fetching another version.

jsii will also flag these dependencies as "peer" in the jsii spec. At the
moment, this won't have implications on generated language packages,
but in environments that have support for peer dependencies, we should
make sure the module's metadata reflects this idea as well.

A utility called "jsii-fix-peers" is included. It will inspect .jsii
and package.json and will add "peerDependencies" to package.json
for all dependencies that have types in the public API.

Related to aws/aws-cdk#979
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants