Skip to content
This repository has been archived by the owner on Sep 13, 2019. It is now read-only.
ibasoni edited this page Feb 13, 2016 · 11 revisions

Getting started

Define a module:

/core/say.js

module.exports = {
    sayHello: function() {
        console.log("Hello scattered world!");
    }
}

Add the particle descriptor:

/core/particle.json

{
    "name": "helloComponent"
}

Initialize the Scatter container and register the new particle directory:

/app.js

var scatter = new Scatter();
scatter.registerParticle([
  __dirname + '/core'
]);

//Load and use the module
scatter.load('hello').then(function(mod) {
    mod.sayHello();
});

(Usually you will never need to manually load a module, modules are normally wired together using dependency injection )

Module resolver

In Scatter, you don't need to manually register each module with the DI container (although you can), modules are automatically resolved from the particle directories specified during the container creation.

Module Naming

Each module is named after it's relative path from its particle directory + the name of the file (without the .js extension). For example if we add a particle from the directory:

/project/lib/

and then define a module:

/project/lib/foo/bar.js

The module will be available in the DI container with the name:

foo/bar

Particles and subparticles

A Particle in Scatter is a container for a set of modules. To define a particle directory it is necessary to create a particle.json file in the particle directory itself. The json file must contain at least a name property, for example:

myparticledir/particle.json:

{
    "name": "<particle name>"
}

If particle.json is not found, the directory will not be added as particle to the Scatter DI container.

A particle might define multiple subparticles by specifying the subparticles property (containing relative paths to subparticles directories), for example:

myparticledir/particle.json:

{
    "name": "<particle name>",
    "subparticles": [
        "subDir1", "subDir2"
    ]
}

Note: Each Subparticle dir must define its own particle.json file. When specifying subparticles the "parent" particle directory is not registered in the DI container, only subparticles will be.

Importing modules from the node_modules directory

You can automatically register all the Scatter particles in the node_modules directory by using the method scatter.setNodeModulesDir. This will also allow you to require standard npm modules from the DI container with the syntax npm!<module name>

Dependency Injection

Dependency injection is achieved by defining a __module descriptor in your module. With it you can control how the module is instantiated, but more importantly, how it's wired with other modules.

The most intuitive type of dependency injection in Scatter is achieved by using factories.

module.exports = function(person) {
    return {
        sayHello: function() {
            console.log("Hello " + person.name + "!");
        }
    };
};
module.exports.__module = {
    args: ['models/person']
};

You can also use a constructor:

function Hello(person) {
    this.person = person;
};
Hello.prototype = {};
Hello.prototype.sayHello: function() {
    console.log("Hello " + person.name + "!");
}

module.exports = Hello;
module.exports.__module = {
    args: ['models/person']
};

You can even inject properties directly into the module instance (injected after the module is instantiated)

var self = module.exports = {
    sayHello: function() {
        console.log("Hello " + self.person.name + "!");
    }
};
module.exports.__module = {
    properties: {
        person: 'models/person'
    }
};

Module lifecycle

A module in Scatter has 3 states:

  1. Resolved - The module is known to Scatter, it's kept in its internal data structures, but it's not ready yet to use.
  2. Instantiated - The module is instantiated (e.g. the factory or constructor is invoked). At this point the module instance exists but it's not fully usable yet (the properties are not injected and initialize is not invoked).
  3. Initialized - The module is initialized, the initialize method was already invoked and all the dependencies are injected and initialized as well.

All modules are by default injected in an initialized state. Sometimes though you might have loops between dependencies, in this case you should know how the module lifecycle works to find workarounds.

For example, you will have a deadlock if you have two modules which try to inject each other at instantiation time (using args with factory/constructor). To go around this, just inject one of the two modules with the properties command being sure you require an instance only of that module using the dependency delayinit!<module name>, otherwise you will have a deadlock at initialization time.

The same technique can be applied with deadlocks at initialization time (injected with properties or initialize).

PS: Don't worry, even with delayinit!<module name> the module will be fully initialized as soon as the main application cycle starts.

Example

/core/foo.js:

module.exports = function(bar) {
    //bar is just the module instance, you can assign it,
    //but be careful when using it at this point

    var self = {
      doSomething: function() {
        console.log(bar.name);
      }
    };
    return self;
}
module.exports.__module = {
    args: ['delayinit!bar']
}

/core/bar.js:

module.exports = function() {
    var self = {
      name: 'bar',
      useFoo: function() {
        self.foo.doSomething();
      }
    };
    return self;
}
module.exports.__module = {
    properties: {foo: 'foo'}
}

Services

One of the most powerful features of Scatter is the services framework. You can use it to implement extension points, hooks or emit events.

To define a service, create a function in your module then declare it in your __module descriptor, using the provides property. The service name is in the form <namespace>/<method>, where <namespace> must be the module namespace (or a parent namespace) and method is the service identifier.

To use a service inject a dependency in the format svc!<namespace>/<method>, then simply invoke the resulting dependency as if it was a normal function. By default the services will be invoked in series (sequence), but Scatter also allows you to invoke the service using other modes: any() and pipeline(). To explicitly specify the mode, pass it as an option to the dependency: svc|any!<namespace>/<service name>

Here is an example of how you can use it to register some routes in an express application.

/components/home/routes/home.js:

var self = module.exports = {
    home: function(req, res) {
        ...
    },
    register: function(express) {
        express.get('/', self.home);
    }
};
self.__module = {
    provides: ['routes/register']
}

/components/aPlugin/routes/person.js:

var self = module.exports = {
    view: function(req, res) {
        ...
    },
    register: function(express) {
        express.get('/person', self.view);
    }
};
self.__module = {
    provides: ['routes/register']
}

Now somewhere else in your project you can register all your routes at once using the register service:

/components/core/expressApp.js:

...
var express = require('express');

module.exports = function(registerRoutes) {
    var self = {
        initializeApp: function() {
            ...

            return registerRoutes(self.express);
        }
    }
    return self;
};
module.exports.__module = {
    args: ['svc!routes/register'],
    provides: ['initializeApp']
}

Then the app entry point:

/app.js:

var scatter = new Scatter();
scatter.registerParticle(__dirname + '/components/*');

scatter.load('svc|sequence!initializeApp').then(function(initializeApp) {
    return initializeApp();
}).then(function() {
    console.log('App initialized');
});

Notice how you can require a service exactly in the same way you require a module! The service becomes a dependency!

Another cool thing, is that the three modules do not know of the existence of each other, they are totally decoupled.

If you need a particular order of execution between your services, you can easily define it by specifying it in the __module descriptor, for example:

/components/aPlugin/routes/person.js:

...
module.exports.__module = {
    ...
    provides: {
        register: {
            after: "routes/home"
        }
    }
}

Extend and Override Modules

The real power of Scatter resides in the fact that every module can be overridden or extended by another particle. This way it is possible to change the behavior of any module in any particle!

To declare that a particle is going to override the modules of another one it is necessary to add the property overrides into the particle.json descriptor, for example:

/components/EnhancedUser/particle.json

{
    "name": "EnhancedUser",
    "overrides": ["BasicUser"]
}

Now as let's see how it's possible to extend an hypothetical User module:

/components/BasicUser/User.js

var self = module.exports = {
    username: "Mario",
    hello: function() {
        console.log("Hello " + self.username);
    }
}

Then to extend it we ca just do the following:

/components/EnhancedUser/User.js

module.exports = function(User) {
    User.username = "Luigi";
    return User;
}
module.exports.__module = {
    args: ['User']
}

The module modifies the module User by changing its username to Luigi. This is just a basic change but thanks to the flexibility of JavaScript we can transform the parent module in many different ways, or even return a totally different object.

Notice the dependency User that is injected into the factory. Since we specified that the particle EnhancedUser overrides the particle BasicUser, Scatter knows how to resolve the User module from the dependency tree.

We can now initialize Scatter and load the User module:

/app.js:

var scatter = new Scatter();
scatter.registerParticle(__dirname + '/components/*');

scatter.load('User').then(function(user) {
    user.hello();
});

What the code above will print?

Clone this wiki locally