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

Almost any interface change in the Fluid repo results in IFluidObject typing incompatibility #2566

Closed
Tracked by #7920 ...
leeviana opened this issue Jun 16, 2020 · 18 comments
Assignees
Labels
Milestone

Comments

@leeviana
Copy link
Contributor

leeviana commented Jun 16, 2020

Describe the bug

Anything that references an IFluidObject type from different versions are pretty much always guaranteed to be typescript incompatible in a "major version" sort of way because of the number of "provides" contracts on IFluidObject . This means we can pretty much never have two different versions of packages on either side of an IFluidObject exchange, regardless of whether you're even using any interfaces that have actually changed. For instance, if somebody makes a random change to IFluidDataStoreContext, through a really deep dependency chain involving views and IHostRuntime, this essentially means a major bump in IFluidObject/core-interfaces.

To Reproduce

Make a change to almost any interface in the Fluid repo. Likely the IFluidObject interface in the version of the code published is not compatible with the version of code right before.

Expected behavior

IFluidObject interfaces should be somewhat durable across Framework changes, especially in interfaces that aren't being immediately used.

@leeviana leeviana added the bug Something isn't working label Jun 16, 2020
@ghost ghost added the triage label Jun 16, 2020
@leeviana
Copy link
Contributor Author

@anthony-murphy

@curtisman curtisman added this to the August 2020 milestone Aug 10, 2020
@curtisman curtisman removed the triage label Aug 10, 2020
@anthony-murphy anthony-murphy changed the title Almost any interface change in the Fluid repo results in IComponent typing incompatibility Almost any interface change in the Fluid repo results in IFluidObject typing incompatibility Aug 18, 2020
@markfields
Copy link
Member

We chatted about this issue today, and I wanted to jot down some notes.

We discussed the approach of removing IFluidObject from API signatures, and instead using it as-needed to cast through to get a type safe interface implementation. This will of course fix the TypeScript compilation errors, but we're still left with the runtime impact of interface changes across versions, so not much appears to be gained here actually.

The bulletproof way to deal with this is to never change any interface. Instead of modifying IFoo, you create IFoo2 with the changes, leaving IFoo intact. This way, within the Fluid ecosystem, if you ever find yourself with an object with a property IFoo you know its shape definitively.

But this is pretty onerous, so we're hopeful to find a more elegant solution.

@markfields
Copy link
Member

Update to my previous comment - even if we neutralize the type in API signatures, there's still a compile-time trap when a particular bit of code (e.g. a Container specification) imports two different module (e.g. 2 data stores) built/published against different versions of the runtime, with different definitions of IFluidObject.

I wonder if we can't just put a stake in the ground to say that when authoring a Container all included Data Stores and DDS's must be targeting the same version of the runtime? Is this just kicking the can until we get to dynamic loading? Does it even solve this issue?

@markfields
Copy link
Member

markfields commented Aug 19, 2020

Also, I think this issue would benefit from a concrete repro case and description of what specific errors come up. @leeviana if you can point to a PR or something that fixed this issue before, or any other collateral that would be great. On our side, @anthony-murphy and I discussed just crafting a toy example where some code imports two different modules, each with their own differing definitions of IFluidObject. Then we can not only see the errors but play around with different scenarios/tactics.

Speaking of tactics, a few we've discussed:

  • Audit interfaces being registered with IFluidObject to shed some that may be abusing the pattern. @anthony-murphy and I did a quick pass and I opened issue Remove certain "internal" interfaces from IFluidObject #3264 for some potential low-hanging fruit here. This would hopefully reduce the churn.
  • See if JavaScript's duck typing lets us be a bit loosey-goosey (all the water fowl!) in making changes to interfaces - e.g...
    • An extreme example would be to make everything on our interfaces optional via Partial<T>, and then even once you have an interface you need to query to see which members are actually there. I think this has a decent shot at working with the double import.
    • Using a discriminated union of versions of an interface, so the thing you get back only has the version field and anything common across all versions. Not sure how the double import would handle this... probably won't work.
  • Incorporate some kind of "strong name" semantics where interfaces have the friendly name concatenated with a guid, so you can use the guid to definitively address a particular interface, but you still get friendly names. We've discussed a similar scheme for naming factories. And just don't ever change the interface without renaming it / regenerating a guid.

@markfields
Copy link
Member

@curtisman, @anthony-murphy - These notes all came from discussions we had, anything else to add or clarify?

@leeviana
Copy link
Contributor Author

leeviana commented Aug 19, 2020

I don't think we have a specific PR because this particular issues prevents us from checking in any code :) I believe I created this issue during one of Kenneth's particularly convoluted FFX bumps where I suggested that in order to reduce regressions/catch them more quickly, we check in the component/container version bump first and then the host version updates second. However, all the contracts around IComponent were broken/unable to be reused around really random things ten layers deep into each interface and ending in something very much unrelated like an interface change in containerRuntime etc.

For example, scriptor is both a host and a component. So its component dependencies needed to be updated. However it could not use the IComponent output of the Loader.resolve() because the Loader's version of IComponent was incompatible with the component's version of IComponent because of aforementioned really random changes that had nothing to do with IComponent interfaces that were used.

@leeviana
Copy link
Contributor Author

leeviana commented Aug 19, 2020

You can probably repro this issue quite easily in the bohemia repo by picking any version bump and updating some dependencies in components/containers and not host/Loader code and seeing how it fails.

@markfields
Copy link
Member

markfields commented Aug 20, 2020

Ah of course, that makes sense. Thanks for spelling it out for me :)

@DLehenbauer
Copy link
Contributor

Found an example of 'IFluidDataStoreFactory' being incompatible with 'IFluidDataStoreFactory', where both packages are pulling v0.26.1 from npmjs:

ERROR in /workspaces/ps/danlehen/graphql/client/src/containerCode.ts
[tsl] ERROR in /workspaces/ps/danlehen/graphql/client/src/containerCode.ts(21,5)
      TS2345: Argument of type 'Map<string, Promise<Readonly<Partial<IProvideFluidDataStoreRegistry & IProvideFluidDataStoreFactory>>>>' is not assignable to parameter of type 'NamedFluidDataStoreRegistryEntries'.
  The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
    Type 'IteratorResult<[string, Promise<Readonly<Partial<IProvideFluidDataStoreRegistry & IProvideFluidDataStoreFactory>>>], any>' is not assignable to type 'IteratorResult<NamedFluidDataStoreRegistryEntry, any>'.
      Type 'IteratorYieldResult<[string, Promise<Readonly<Partial<IProvideFluidDataStoreRegistry & IProvideFluidDataStoreFactory>>>]>' is not assignable to type 'IteratorResult<NamedFluidDataStoreRegistryEntry, any>'.
        Type 'IteratorYieldResult<[string, Promise<Readonly<Partial<IProvideFluidDataStoreRegistry & IProvideFluidDataStoreFactory>>>]>' is not assignable to type 'IteratorYieldResult<NamedFluidDataStoreRegistryEntry>'.
          Type '[string, Promise<Readonly<Partial<IProvideFluidDataStoreRegistry & IProvideFluidDataStoreFactory>>>]' is not assignable to type 'NamedFluidDataStoreRegistryEntry'.
            Type 'Promise<Readonly<Partial<import("/workspaces/ps/danlehen/graphql/common/node_modules/@fluidframework/runtime-definitions/dist/dataStoreRegistry").IProvideFluidDataStoreRegistry & import("/workspaces/ps/danlehen/graphql/common/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IProvideFluidDataS...' is not assignable to type 'Promise<Readonly<Partial<import("/workspaces/ps/danlehen/graphql/client/node_modules/@fluidframework/runtime-definitions/dist/dataStoreRegistry").IProvideFluidDataStoreRegistry & import("/workspaces/ps/danlehen/graphql/client/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IProvideFluidDataS...'.
              Type 'Readonly<Partial<import("/workspaces/ps/danlehen/graphql/common/node_modules/@fluidframework/runtime-definitions/dist/dataStoreRegistry").IProvideFluidDataStoreRegistry & import("/workspaces/ps/danlehen/graphql/common/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IProvideFluidDataStoreFact...' is not assignable to type 'Readonly<Partial<import("/workspaces/ps/danlehen/graphql/client/node_modules/@fluidframework/runtime-definitions/dist/dataStoreRegistry").IProvideFluidDataStoreRegistry & import("/workspaces/ps/danlehen/graphql/client/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IProvideFluidDataStoreFact...'.
                Types of property 'IFluidDataStoreFactory' are incompatible.
                  Type 'import("/workspaces/ps/danlehen/graphql/common/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IFluidDataStoreFactory | undefined' is not assignable to type 'import("/workspaces/ps/danlehen/graphql/client/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IFluidDataStoreFactory | undefined'.
                    Type 'import("/workspaces/ps/danlehen/graphql/common/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IFluidDataStoreFactory' is not assignable to type 'import("/workspaces/ps/danlehen/graphql/client/node_modules/@fluidframework/runtime-definitions/dist/dataStoreFactory").IFluidDataStoreFactory'.

Fixed by casting to 'any' here:

export const GraphContainerRuntimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore(
    GraphInstantiationFactory.type,
    new Map([
        GraphInstantiationFactory.registryEntry as any,   // <-- Fixed by 'as any'
    ]),
);

@curtisman curtisman added api and removed bug Something isn't working labels Sep 22, 2020
@curtisman
Copy link
Member

curtisman commented Sep 22, 2020

@DLehenbauer what are the versions of @fluidframework/runtime-definitions in /workspaces/ps/danlehen/graphql/common vs /workspaces/ps/danlehen/graphql/client, The interface shouldn't change between 0.26.x release.

@leeviana
Copy link
Contributor Author

Yeah, unfortunately things like this have led to a lot of similar 'any' casting where we test different versions of host/loader etc. :(

@DLehenbauer
Copy link
Contributor

DLehenbauer commented Sep 22, 2020

@curtisman - It's '0.26.1' in both cases. I did a recursive diff of all the fluidframework packages like this:

diff -r /workspaces/ps/danlehen/graphql/common/node_modules/@fluidframework /workspaces/ps/danlehen/graphql/client/node_modules/@fluidframework

...and they are 100% identical (except for the "_where:" field generated by 'npm i' in package.json... which differs as expected).

Seems to be WebPack related ('client' compiles successfully w/tsc.)

anthony-murphy added a commit that referenced this issue Oct 1, 2020
Remove the unused interfaces on FluidObject

ISharedString
ISharedObject
IChannel
In addition to being unused these interfaces represent bad practices. A DataStore shouldn't externally expose it's dds for others to access directly. It should instead expose and API and control the data model of the dds.

While gernerally unused there is a coupling between our scribe tool and our shared text examples that leverages the ISharedString interface. To remove this interface from IFluidObject, which broadly exposes this bad practice, I've move it to using the request pattern. While still not ideal, this localizes the contract between the two rather than leaking this across the system.

Related #3264
Related #2566
@curtisman curtisman modified the milestones: October 2020, Next Oct 2, 2020
@ghost ghost added the status: stale label Jun 8, 2021
@ghost
Copy link

ghost commented Jun 8, 2021

This issue has been automatically marked as stale because it has had no activity for 180 days. It will be closed if no further activity occurs within 8 days of this comment. Thank you for your contributions to Fluid Framework!

@leeviana
Copy link
Contributor Author

leeviana commented Jun 8, 2021

This is still an issue, what is the standard here, just to leave a comment to prevent if from being closed?

@markfields
Copy link
Member

Still interested in this but about to go on leave so removing my assignment.

@Stevenic
Copy link

FYI... We ran into a similar issue with the Bot Framework SDK's. The lesson we learned is that you should always try to favor classes over interfaces as interfaces are essentially immutable and can almost never be updated. The advantage of classes is that you can easily extend them in future releases and provide default behaviors so that you don't break compatibility with previous versions.

@anthony-murphy
Copy link
Contributor

@Stevenic Thanks for this! curious if you had cases where consumer would be the implementor? did you end up going with abstract classes is these cases?

@anthony-murphy
Copy link
Contributor

will be fixed by #7613

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants