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

New Component Creation Paradigm #777

Closed
skylerjokiel opened this issue Dec 13, 2019 · 2 comments
Closed

New Component Creation Paradigm #777

skylerjokiel opened this issue Dec 13, 2019 · 2 comments
Assignees
Labels
area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct
Milestone

Comments

@skylerjokiel
Copy link
Contributor

skylerjokiel commented Dec 13, 2019

This proposal is to shift the Component creation paradigm from creating through the ContainerRuntime to creation through individual component factories. By moving component creation into factories we provide more control to the component developers when it comes to the creation of their objects.

It provides a cleaner developer experience and mirrors the current pattern of SharedObject creation.

In this new world IComponentFactory will have a createComponent function that will be used for the create new flow. This function will call into the create flow on the ContainerRuntime but will need to provide the instantiate function. The initialize function has the same signature as the existing instantiateComponent with: (context: IComponentContext) => void. The Component Factory developer can choose to simply use the existing instantiateComponent function or they can provide an alternate function on the create call. This is powerful because create can be further separated from standard instantiate.

In the generic case the code would look something like this:

public instantiateComponent(context: IComponentContext): void {
    //...
}

public async createComponent(context: IComponentContext): Promise<IComponent> {
    const cr = await context.createComponent(this.registryName, (c) => { this.instantiateComponent(c); });
    const response = await cr.request({url: "/"});
    if (response.status !== 200 || response.mimeType !== "fluid/component") {
        throw new Error("Failed to create component");
    }

    return response.value as IComponent;
}

In the specialized case we can take in props that are used only in the create flow:

public async createClickerComponent(
    props: IClickerWithInitialValueProps,
    context: IComponentContext): Promise<ClickerWithInitialValue> {
        const cr = await context.hostRuntime.createComponentDirect(
            this.registryName, this.createInstantiation(props));
        const response = await cr.request({url: "/"});
        if (response.status !== 200 || response.mimeType !== "fluid/component") {
            throw new Error("Failed to create component");
        }

        return response.value as ClickerWithInitialValue;
    }

private createInstantiation(props: IClickerWithInitialValueProps) {
    return (context: IComponentContext) => {
        // Create a new runtime for our component
        // The runtime is what Fluid uses to create DDS' and route to your component
        ComponentRuntime.load(
            context,
            this.sharedObjectRegistry,
            (runtime: ComponentRuntime) => {
                const clicker = new ClickerWithInitialValue(runtime, context, props);
                const clickerP = clicker.initialize();

                runtime.registerRequestHandler(async (request: IRequest) => {
                    await clickerP;
                    return clicker.request(request);
                });
            },
        );
    };
}

In the above example we have strongly typed props passed through the entire creation flow.

Aqueduct update

With the new pattern Component developers will be responsible for writing creation code. We will offer patterns and helpers through the Aqueduct to make this easier to consume. SharedComponentFactory will come with a default createComponent that will emulate the existing flow provided by calling through the instantiateComponent flow. Developers can overwrite or extend the SharedComponentFactory to implement their own strongly typed create with props flow (See below for more)

The this.context.hostRuntime.createComponent(...) pattern will still be supported in the short term but quickly deprecated.

To improve the developer experience we will be cleaning up two other existing parts:

  1. Adding type to IComponentFactory. This will allow us to have a cleaner registry pattern. We'll need to support backwards compatibility with existing named components.
  2. Merging createComponent and createSubComponent.

Backwards compatibility

Since our components can be dynamically loaded we need to ensure that Components on older versions can still be loaded with the new patterns. Since this change will keep existing loading flows using the instantiateRuntime call flow for loading existing components we only need to worry about the component creation flow.

We can either provide guidance for how to create an old component or expose a helper function that will wrap the instantiateComponent function in the create call the same way SharedComponentFactory will.

Key Problem Spaces

Container Developers define ComponentFactory Mapping

Currently the Container developer is responsible for defining the string used in the Registry for finding the corresponding Factory. This is a problem because if we do something like MyComponentFactory.create(this.context); then the Factory needs to know the name of the registry entry. So, the actual syntax would look something like MyComponentFactory.create(this.context, "registry-package-name"); which is pretty ugly because you are defining that name of the thing you are calling.
It's especially ugly in the dynamic case where you do something like:

const factory = (await this.context.hostRuntime.IComponentRegistry.get("clicker-pkg-name")).IComponentFactory;
if (factory) {
const component = await factory.create(this.context, "clicker-pkg-name");
}

In the above scenario we needed to provide the package name to get the registry entry and the package name to create because it doesn't know it's own name.

To resolve this we can follow the same pattern as Distributed Data Structures and expose a type on the IComponentFactory. With this pattern instead of the Container developer being responsible to provide the type it will be on the ComponentFactory to provide it's type. The most obvious problem with this is ensuring uniqueness of types. For this we should provide guidance to name types after package names. Since package names should be unique in themselves and public ones are enforced by npmjs then we are leveraging existing. If you are exporting multiple components from the same package you can postfix the component name with a /{component name} (Ex. @fluid-example/vltava/tabs)

Since Registries can consist of Factories or other Registries we should additionally expose type on IComponentRegistry so they can follow the same pattern.

Q - Should there be an IComponentType base interface? or is it okay if they all just have a type?

Component Creation without Props

Current Pattern

const componentRuntime = await this.context.hostRuntime.createComponent(pkg, id);
componentRuntime.attach();

const response = await componentRuntime.request({ url: "/" });
if (response.status === 200 && response.mimeType === "fluid/component") {
  const clicker = response.value;
    this.root.set("unique-clicker-id", clicker.handle);
}

New Pattern

const clicker = await ClickerFactory.createComponent(this.context);
this.root.set("unique-clicker-id", clicker.handle);

Component Creation with Props

Because we know the factory we can have specific create functions with strong typing.

const clicker = await ClickerWithInitialValueFactory.createClickerComponent({initialValue: 100}, this.context);
this.root.set("unique-clicker-id", clicker.handle);

Dynamic case

All Components should be able to create without initial state. For the dynamic case we can't presume what we are loading so we can't pass in state. We use the registry to get
the ComponentFactory since we don't have it and we call create on it.

const factory = await this.context.hostRuntime.IComponentRegistry.get(ClickerName);
if (factory) {
  const component = await factory.createComponent(this.context);
  this.root.set("unique-clicker-id", component.handle);
}
@skylerjokiel skylerjokiel added the area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct label Dec 13, 2019
@skylerjokiel skylerjokiel self-assigned this Dec 13, 2019
@curtisman curtisman modified the milestones: Build 2020, February 2020 Feb 2, 2020
@skylerjokiel
Copy link
Contributor Author

Initial Draft PR #538

@skylerjokiel
Copy link
Contributor Author

This works has been split into more manageable tasks and has been mostly completed. Closing.

jason-ha pushed a commit that referenced this issue Jan 23, 2025
jason-ha pushed a commit that referenced this issue Jan 23, 2025
* version update for brainstorm and collaborative text area (#764)

* version update for brainstorm and collaborative text area

* remove azure-client from collaborative-text-area example, update brainstorm dependencies to use internal instead of dev

* Node demo and dice roller demo update to tinylicious 2.0 itnernal (#765)

* update dice roller to 2.0-internal tinylicious

* update node demo to tinylicious 2.0 internal

* V2.0 internal updates (#766)

* react-demo and react-starter-template

* audience demo and angular demo

* update FluidExamples 2.0.0-internal to latest internal version (#783)

* V2.0 internal update (#792)

* remove teams-fluid-hello-world example (#767)

* build: Misc. cleanup (#768)

* docs: Misc. README cleanup

* build: Update lockfile

* build: Update lockfile

* docs: Misc README cleanup

* docs: Update READMEs

* docs: Update README

* build: Update lockfile

* docs: Update README

* improvement: useMemo

* docs: Update README

* style: Sort package.json

* build: Update lockfile

* refactor: Update start flow to match other packages

* build: Update lockfile

* docs: Fix typo

* build: Update start flow to match other packages

* docs: Update README

* ci: Remove obsolete template

* fix: config

* style: Prettier

* style: Sort scripts

* style: Sort scripts

* revert: lockfile update

Seems to have broken tests

* brainstorm demo fix for remote azure example (#769)

* Add schedule to build apps daily (#771)

Add a schedule to automatically run builds on a daily basis

AB#4694

* docs: Clarify app usage instructions (#774)

Update app running instructions to be more consistent and explicit.

* tools: Update test infra to generate test reports locally and in CI (#775)

CI was set up to publish generated test reports, but none were being generated. This PR updates our testing logic to ensure test reports are correctly generated by test runs.

E.g.
![image](https://github.com/microsoft/FluidExamples/assets/54606601/4642657e-6e69-4f10-bba2-10ef5bd80a9c)

* Increase timeout for react-starter-template tests (#776)

This PR increases the timeout for react-starter-template tests, since they were consistently timing out in automated test pipelines.

* Bump @types/node to v16 (#777)

* Increase Timeouts (#780)

* Update root deps (#778)

Make syncpack a dev dep, update it and regenerate lock file.

* Update audience-demo (#782)

Update deps and improve readme for audience-demo.

Also updates CI from node 14 to node 16 to support ??= syntax.

These updates might help with the node update (#779 ), but with the unrelated non-deterministic CI errors and no useful error output, that's just a guess.

* Misc Updates and cleanup (#785)

Pull in updates from 61415de except for updating the node version, then npm install in all the examples.

This mainly updates a lot of deps to help enable node 18, and fixes many minor issues (updates to address deprecation warning from the new versions, fix alphabetical ordering, mode dev deps to dev deps etc.)

* Revert "Misc Updates and cleanup (#785)" (#786)

This reverts commit 11030fb.

* Un-revert dependency updates (#787)

Updates from #785, except for the build.yml change (which broke test running), and adding update to one jest version which caused a failing test.

* Update build.yml (#788)

Remaining changes from #785, cleaning up the build.yml. Includes a fix from that version which fixes test running.

This does not include the Node version update: that will follow in a separate change.

* Use node 18 on CI (#779)

Update to a supported version of NodeJs for CI.

* Use node 20 (#789)

Use and recommend (via nvm) Node 20.
New projects starting now should probably use Node 20, since it will be active LST and thus the best choice for production in just over a month.

Also, the Fluid project, as library authors, is granted the window of a Node release being "Current" (before it goes LTS) to ensure our libraries work: running these examples on the "current" release, Node 20, can help with that.

Support for node 18 will be kept (in engines and @types/node) for those who wish to use it, but it will not be tested by CI.

* Only support Node 18 and newer (#790)

Require at least Node 18.

This removes support for older unsupported major versions of NodeJs.

* version bump for brainstorm and collaborative text-area

* version bump for dice roller and audience demo

* version bump for react and node demo

* version bump react starter template

* angular demo bump

---------

Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com>
Co-authored-by: Scott Norton <scottnorton@microsoft.com>
Co-authored-by: Sonali Deshpande <48232592+sonalideshpandemsft@users.noreply.github.com>
Co-authored-by: Craig Macomber (Microsoft) <42876482+CraigMacomber@users.noreply.github.com>

* Add v2.0_internal to yml file (#795)

* Bump to internal 7.1.0 (#796)

Also switches to test-runtime-utils in place of test-client-utils and bumps build-common to 2.0.2.

* bump to 2.0.0-internal.7.3.0 (#798)

Bump internal branch 2.0 packages to 2.0.0-internal.7.2.0

* bump package dependencies to 2.0.0-internal.7.2.2 (#800)

Co-authored-by: Michael Zhen <michaelzhen@WIN-1V7LUTHS292.redmond.corp.microsoft.com>

* bump FF package dependencies to 2.0.0-internal.7.3.0 (#803)

* Update dependencies to v8 internal (#804)

* Delete most demos

* Finish setting up directories

* Basic content swap

* Formatting

* Content swap, start prettier change

* Format

* Copyright headers

* Get basic tests working

* More permissive test timeouts

* Few more updates

* Update to rc

---------

Co-authored-by: Michael Zhen <112977307+zhenmichael@users.noreply.github.com>
Co-authored-by: Rishhi Balakrishnan <107130183+RishhiB@users.noreply.github.com>
Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com>
Co-authored-by: Scott Norton <scottnorton@microsoft.com>
Co-authored-by: Sonali Deshpande <48232592+sonalideshpandemsft@users.noreply.github.com>
Co-authored-by: Craig Macomber (Microsoft) <42876482+CraigMacomber@users.noreply.github.com>
Co-authored-by: Michael Zhen <michaelzhen@WIN-1V7LUTHS292.redmond.corp.microsoft.com>
Co-authored-by: nmsimons <nick@dreamlarge.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct
Projects
None yet
Development

No branches or pull requests

4 participants