KickApp is an application architecture framework for organizing the logic into runnable services.
Split your logical components into services contained in a top-level Applicaton object,
define service dependencies and launch them all with a single app.start()
!
Key features:
- Structured application architecture approach
- Service workflow:
init()
,start()
,stop()
- Service dependencies resolution
- Can deal with hierarchical services
- Promise-based: using the q package
- Unit-tested
- KickApp
- Table of Contents
- Core Components
- Full Example
- Promise-Haters
- Application as a Service
- Bundled Services
A Service is an arbitrary constructor function or an object that implements the IService
interface:
- Its constructor function gets an
Application
as the first argument. More arguments can be provided with theApplication.addService()
method. - It can optionally have the
init()
method: here you can perform some preparation steps - It must have the
start()
andstop()
methods which bring your service up and down respectively.
Consider an example:
/** Database service
* @param {Application} app
* The parent Application
* @param {String} url
* Connection string
* @constructor
* @implements {IService}
*/
var DbService = function(app, url){
this.app = app;
this.url = url;
this.client = undefined;
};
DbService.prototype.init = function(){
this.client = new DatabaseClient(this.url); // init some imaginary client
};
DbService.prototype.start = function(){
return this.client.connect(); // assuming it's promise-based
};
DbService.prototype.stop = function(){
return this.client.disconnect();
};
Having such a service, you can:
- Add it to an Application
- Define service dependencies
- Launch them all in the correct order
If your service is simple, you don't have to implement the constructor and everything. Use an object instead, which is also a valid service:
var DbService = {
app: undefined,
client: new DatabaseClient('db://localhost/'),
start: function(){
return this.client.connect();
},
stop: function(){
return this.client.disconnect();
},
};
It will automatically get the app
property when added to the Application.
An Application
is the container for your services.
You define an application by creating an instance of it, providing a constructor function as an argument:
var kickapp = require('kickapp');
var App = new kickapp.Application(function(configFile){
this.config = require(configFile); // init the configuraton
}, 'app/config.js');
By design, an Application
does not have custom start/stop behavior: instead, it wraps services.
Add a service to the application.
name: String
: Name of the service. Use any reasonable string.serviceConstructor: Function
: Constructor function for an object that implements theIService
interface....args
: Variadic arguments for the service constructor
Returns: an instance of ServiceWrapper
(see below).
This function can also be called inside the Application constructor:
var kickapp = require('kickapp');
var App = new kickapp.Application(function(configFile){
// Load the configuration
this.config = require(configFile);
// Add services
this.addService('db', require('./services/db.js'), this.config.db );
this.addService('web', require('./services/web.js'), this.config.web )
.dependsOn('db'); // Define a dependency on other services
}, 'app/config.js');
Add dependencies for the recently added Service.
When a Service depends on other services, they will be started before starting this one.
Dependencies can be given either as arguments, or as a single array argument.
Get the Service object by name:
app.get('db').client; // get the Service property
Get the ServiceWrapper
object by name.
See: ServiceWrapper
Get the list of service names added to this Application object.
Check whether the Application is running.
An Application is running if all its services are running.
Service methods are run in the following fashion:
- Service constructors are called immediately whilst you add your services to the Application
init()
,start()
,stop()
methods are called when the corresponding Application method is called. Unlike the constructor, these honor the service dependencies.
Call init()
on all services, honoring the dependencies. Returns a promise.
Note that IService.init
is optional.
Call start()
on all services, honoring the dependencies. Returns a promise.
If some services were not yet initialized with init()
, Application does that.
Call stop()
on all services, honoring the dependencies in reverse order. Returns a promise.
Application
is an EventEmitter
which fires the following events:
- 'init': All services have been initialized
- 'start': All services have been started
- 'stop': All services have been stopped
Each Service is internally wrapped in ServiceWrapper
which controls the service state.
ServiceWrapper
is anEventEmitter
which fires the 'init', 'start', 'stop' events of the service- Contains metainformation about the Service: service, initialized, running, dependencies
Usually, you won't need it. See the source code: ServiceWrapper
.
var kickapp = require('kickapp');
// Application
var App = new kickapp.Application(function(configFile){ // Application constructor
// Load the configuration
this.config = require(configFile);
// Services
this.addService(
'db', // Service name
require('./services/db.js'), // Service constructor with init()/start()/stop() methods
this.config.db // Arguments for the Service constructor
);
this.addService('web', require('./services/web.js'), this.config.web )
.dependsOn('db'); // Dependencies on other services
}, 'app/config.js'); // Arguments for the Application constructor
// Launch it
App.start()
.then(function(){
console.log('Application initialized and started!');
})
.catch(function(err){
console.error('Application.start failed:', err.stack);
});
// Stop the services properly when the application exitsq
process.on('SIGINT', process.exit);
process.on('exit', function(){
App.stop()
.catch(function(err){
console.error('Application.stop failed:', err.stack);
});
});
If you dislike promises, you can always get back to the old good NodeJS-style callbacks:
var App = new kickapp.Application(function(){
});
App.start().nodeify(function(err){
if (err)
console.error('Application.start failed:', err.stack);
else
console.log('Application initialized and started!');
});
The Application
object can be used as a service as it implements the IService
interface.
This allows creating reusable components from sustainable application parts and add them to other applications as a service:
var app = new kickapp.Application(function(){
// ... init some services
});
var top = new kickapp.Application(function(){
this.addService('app', app); // added as a service
});
KickApp comes with some handy bundled services.
NetService is a helper to wrap net.Server
, http.Server
, https.Server
, tls.Server
, dgram.createSocket
network servers in a KickApp service. It also supports static configuration with generalized interface.
NetService accepts two arguments:
-
config: Object
: Server configuration object:-
config.lib: String
: The server type to create. Supported values:'net'
(TCP socket),'tls'
(TLS socket),'http'
(HTTP server),'https'
(HTTPS server),'udp4'
(UDP IPv4 socket),'udp6'
(UDP IPv6 socket). -
config.listen: Array
: Array of arguments for thelisten()
function.For
'net'
,'http'
,'https'
: port, [hostname], [backlog]For
'tls'
: port, [host]For
'udp4'
,'udp6'
: port, [address] -
config.options: Object
: Options for thecreateServer()
function. See NodeJS Manual.Note: for
'tls'
and'https'
certificates & stuff, you can optionally specify filenames for the following keys:'pfx', 'key', 'cert', 'ca', 'crl'
.
-
-
accept: Function
: Method used to accept the incoming connections.For `'net', 'tls'`: `function(sock: net.Socket)` For `'http'`, `'https'`: `function(req: http.ClientRequest, res: http.ServerResponse)` For `'udp4'`, `'udp6'`: `function(msg: String|Buffer, rinfo: Object)`
Wielding the service, you can start/stop it an get error handling:
var app = new kickapp.Application(function(){
this.addService('net', kickapp.services.NetService,
{ lib: 'net', listen: [6001, 'localhost'] },
function(sock){
sock.end('hi');
}
);
this.addService('http', kickapp.services.NetService,
{ lib: 'http', listen: [6080, 'localhost'] },
function(req, res){
res.end('hi');
}
);
});
NetService has the following properties:
config
: Server configuration objectserver
: The created server