[WIP] Notes on provider structure - HelpfulHuman/TupperwareJS GitHub Wiki

This article is to help evaluate different possible provider configurations.

Open Attach Method

This is the method currently employed by Tupperware. Inspired by BroadwayJS, this method relies on the user specifying an attach() method that is called upon attaching a provider to the application. attach() then has access to the Tupperware instance and the instance options.

Pros

  • Lots of flexibility
  • Complete access to the Tupperware instance
  • Options can be passed down for single provider

Cons

  • Not an actual DI container system
  • Hard to identify what is provided by the module
  • Have to manually specify a detach() method to undo provider's actions
var provider = {
  name: 'namespace.providername',
  requires: ['makesureiexist'],
  attach: function (app, options) {
    app.foo = function () {
      return 'foo';
    };
  },
  detach: function (app, options) {
    delete app.foo;
  }
};

app.attach(provider, {});
console.log( app.foo() ); // Outputs 'foo'

Single Factory Provider

This approach allows for a single factory to have strict dependencies injected directly into its factory method. The dependencies are declared in the inject array which allows dependencies to be explicitly defined without affecting the attach method's argument names.

Calling the make() method on the Tupperware instance will also allow you to call the factory outside of a provider scope. Worth noting that within the factory method this refers to the Tupperware instance.

Pros

  • Specifies exactly what component is provided by the provider
  • Dependencies can be defined and injected directly into the factory
  • No detach() method necessary as the provider can be removed by name
  • Allows for specific namespaced factories to be injected (so multiple foo classes could be attached and injected separately)

Cons

  • A separate provider is required for every factory or service attached

  • The specific provider factory would be required for injection array which prevents faux-polymorphic or generic components

  • Would require a separate injectable options component if options could be passed to the factory during make()

var provider = {
  name: 'namespace.foo',
  inject: ['namespace.bar'],
  attach: function (bar) {
    return function (foo) {
      return foo + bar;
    };
  }
};

app.attach(provider);
console.log( app.make('namespace.foo') ); // Outputs foo + bar result

Multiple Factory Provider

Very similar to the "Single Factory Provider" except that the inject array (and explicit injections) is no longer used. The required providers array is back and providers can extend Tupperware with multiple factories/services again.

For dependency injection using this approach, automatic annotation will be used and to capture the factories argument names. The argument names will be make() and inject other factories' values into the requested factory.

Using this approach, the provides keys will be used as the containers' names, so in our example below app.make('foo') will be available instead of app.make('namespace.foo'). This is similar to the approach Angular uses of components grouped by modules (or providers in our case).

Pros

  • Can define multiple factories/services
  • Dependency injection is resolved automatically on factories

Cons

  • Can't explicitly define injected dependencies so only one type of foo could exist
var provider = {
  name: 'namespace.foo',
  requires: ['namespace.bar'],
  provides: {
    foo: function (bar) {
      // app.make('bar') will be called and injected
      return function (foo) {
        return foo + bar;
      };
    }
  }
};

app.attach(provider);
console.log( app.make('foo') ); // Outputs foo + bar result