Stream: general

Topic: How to compose mutually-dependent components?


view this post on Zulip godotdot (Feb 18 2025 at 05:05):

I'm trying to understand how I can compose together multiple components that use each others' exports?
In particular, I'm trying to create a plugin system, and would like creators of plugins to be able to create their own custom interfaces that could be used to allow plugins to interact with one another.

There would probably be a naming convention involved with this to facilitate the composition process, for example:

plugin1.wit

package plugin:plugin;

interface plugin1-exports-to-plugins {
    a-function-of-plugin1: func();
}

interface plugin2-exports-to-plugins {
  a-function-of-plugin2: func();
}

world plugin {
    import plugin2-exports-to-plugins;
    export plugin1-exports-to-plugins;
}

plugin2.wit

package plugin:plugin;

interface plugin1-exports-to-plugins {
    a-function-of-plugin1: func();
}

interface plugin2-exports-to-plugins {
  a-function-of-plugin2: func();
}

world plugin {
    import plugin1-exports-to-plugins;
    export plugin2-exports-to-plugins;
}

The main difference in the above example is just the imported and exported interfaces.
Each component imports the interface the other exports.
Unfortunately, it is not clear to me if this is actually possible - my interpretation of the wac language documentation has given me to impression that it would not be possible. This is because each component would need the exports of the other a the time they are instantiated, and as one of them has to be instantiated first it won't have the exports of the second instantiated component available.

If that is the case, an alternative approach I have thought of is to generate another component that would sit between the plugins and basically forward their interfaces to one another. The problem I am having with that is that while I have found ways to determine which interfaces are exported/imported by components, e.g. with wasm-tools component wit, I have not found ways to determine the contents of those interfaces, i.e. the functions or types exported or imported.

So I guess my questions are:
1 - Is it possible to make plugins that are mutually dependent, and if so how?
2 - Is there a way to determine the imported/exported types and functions of components?
3 - Is there a better approach to what I am trying to do?

view this post on Zulip Lann Martin (Feb 18 2025 at 13:39):

  1. There cannot be circular imports between components, as you have found.
  2. You can use wit_component::decode to parse a component into the equivalent WIT AST.
  3. It's a little hard to evaluate without more information but plumbing things together with a top-level component seems reasonable.

view this post on Zulip godotdot (Feb 18 2025 at 16:33):

Thank you for this information - decode looks like just what I need!

Regarding "3. It's a little hard to evaluate without more information but plumbing things together with a top-level component seems reasonable."

I'm trying to think this through, but I keep running into circular imports, possibly due to a misunderstanding of what you are suggesting.

I'm envisioning something like:

plugin1.wit:

package plugin:plugin;

interface plugin1-exports-to-plugins {
    a-function-of-plugin1: func();
}

interface plugin2-exports-to-plugins {
  a-function-of-plugin2: func();
}

world plugin {
    import plugin2-exports-to-plugins;

    export plugin1-exports-to-plugins;
}

plugin2.wit:

package plugin:plugin;

interface plugin1-exports-to-plugins {
    a-function-of-plugin1: func();
}

interface plugin2-exports-to-plugins {
  a-function-of-plugin2: func();
}

world plugin {
    import plugin1-exports-to-plugins;

    export plugin2-exports-to-plugins;
}

generated-component.wit (generated based off interfaces of selected/active plugins, in this case plugin1 and plugin2):

package plugin:plugin;

interface plugin1-exports-to-plugins {
    a-function-of-plugin1: func();
}

interface plugin2-exports-to-plugins {
  a-function-of-plugin2: func();
}

world plugin {
    import plugin1-exports-to-plugins;
    import plugin2-exports-to-plugins;

    export plugin1-exports-to-plugins;
    export plugin2-exports-to-plugins;
}

This is based on the idea that the generated component:
1 - Would be generated based on selected/active plugins.
2 - Would sit above the plugins and all interactions between plugins would go through this component.
3 - Would have implementations of the plugins' exports that would call those plugins' exports; these implementations would be exported for other plugins to import.

The issue is that:
1 - Generated-component would need to import the exports of the plugins so that it can forward calls to them
2 - Plugins would need to import generated-component so that they can call its exports, thus creating a circular import.


Last updated: Feb 28 2025 at 02:27 UTC