-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
v0.0.1 - Initial library with examples
- Completed the basic service container - Can perform Constructor, Setter, and Property Injection - Can recursively resolve other services as arguments - Will throw exception on resolving a circular dependency - Unit test coverage at 100% - TODO: Tags and scoped services
- Loading branch information
Brandon Eum
authored and
Brandon Eum
committed
Jul 5, 2013
1 parent
b636c08
commit 94d1a18
Showing
21 changed files
with
2,240 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/nbproject | ||
lib-cov | ||
coverage.html | ||
node_modules | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
The MIT License | ||
|
||
Copyright (c) 2011, 2012 Brandon Eum and contributors | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
unittest: | ||
export NODE_TEST_ENV=test && \ | ||
./node_modules/mocha/bin/mocha --reporter spec --recursive | ||
|
||
coverage: | ||
./node_modules/jscoverage/bin/jscoverage lib lib-cov && \ | ||
export NODE_TEST_ENV=coverage && \ | ||
./node_modules/mocha/bin/mocha --reporter html-cov --recursive > coverage.html | ||
|
||
.PHONY: test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,160 @@ | ||
service-container | ||
================= | ||
# Service Container | ||
|
||
|
||
A Symfony2-style dependency injection library for Node.js | ||
|
||
# Purpose | ||
|
||
|
||
Inspired by the [Symfony2 Service Container](http://fabien.potencier.org/article/13/introduction-to-the-symfony-service-container) | ||
|
||
A service container increases the usability of classes by allowing a configuration | ||
file to specify which classes should be used to construct a class. The container | ||
will construct an instance of the class for you using the parameters that you | ||
specify, allowing you to specify different classes to use in different situations | ||
or different environments. | ||
|
||
For example, if you have a class that makes HTTP requests. In your production | ||
environment you would want this to be a real http client. In your unit test | ||
environment, there is significant complication and overhead in using a real HTTP | ||
client, so you would probably want to use a mock class instead that delivers canned | ||
and predictable responses. | ||
|
||
With a service container, you can reconfigure your class through a configuration | ||
file without actually touching your code. | ||
|
||
The Symfony 2 Guide Book provides a great [explanation](http://symfony.com/doc/current/book/service_container.html) | ||
|
||
|
||
# Installation | ||
|
||
|
||
npm install service-container | ||
|
||
Or add to your package.json dependencies. | ||
|
||
|
||
# Usage | ||
|
||
There are two pieces to using the service container, the container itself and the | ||
`services.json` configuration files. | ||
|
||
The `services.json` file specifies [3 types of dependency injection](http://symfony.com/doc/current/components/dependency_injection/types.html): | ||
* Constructor Injection | ||
* Setter Injection | ||
* Property Injection | ||
|
||
Constructor injection allows you to specify JSON literal parameters as arguments | ||
for your class instances or it will allow you to specify another service as an | ||
argument (be careful about circular dependencies!). | ||
|
||
Setter injection will attempt to call the method that you specify with the arguments | ||
(either parameters or other services) that you define. This is useful for adding | ||
dependency injection for existing libraries that do not conform to Constructor | ||
Injection. | ||
|
||
Property injection will directly attempt to set the property on the object with | ||
the argument that you specify, either another service or a JSON literal parameter. | ||
|
||
|
||
## Initializing and using the container | ||
|
||
Create a container like: | ||
|
||
```javascript | ||
var ServiceContainer = require('service-container'); | ||
|
||
// Create an instance of the container with the environment option set | ||
// This will include both services.json and services_[ENV].json files to override | ||
// any environment-specific parameters | ||
var options = { | ||
env: 'test' | ||
}; | ||
var container = ServiceContainer(__dirname, options); | ||
``` | ||
|
||
Get an instance of a service like: | ||
|
||
```javascript | ||
// Get an example mailer service | ||
var mailer = container.get('mailer'); | ||
mailer.sendMail('Hello'); | ||
|
||
// Get a parameter | ||
var maxcount = container.getParameter('mailer.maxcount'); | ||
``` | ||
|
||
The section below goes over how to configure and construct services. | ||
|
||
## services.json | ||
|
||
When initializing the container, you'll pass it the root directory for it to search | ||
for the services.json files. The Builder will recursively search through directories | ||
below the root directory to search for services.json files. If the `env` option | ||
is specified then files named services_[ENV].json will also be parsed. Allowing | ||
you to override any parameters setup earlier. | ||
|
||
The hierarchy of services and parameters are as follows: | ||
* services.json files at lower folder levels are overridden by those at higher levels | ||
* services.json files are overriden by services_[ENV].json files | ||
|
||
This allows you to override the specifics of a module from the application level | ||
and allows you to override any parameter based on whether this is your dev, test, | ||
qa or production environment. | ||
|
||
```javascript | ||
{ | ||
"parameters": { | ||
"dependency_service.file":"./CoolClass", // File paths are relative to the root passed into the builder | ||
"dependency2_service.file":"./OtherClass", | ||
"my_service.file":"MyClass", | ||
"my_service.example_param":"yes", // Parameter names are arbitrary | ||
"my_service.obj_param":{isExample: true} // Can use literals as parameters | ||
}, | ||
"services": { | ||
"dependency_service": { | ||
"class":"%depency_service.file%", | ||
"arguments":[] // No Arguments | ||
}, | ||
"dependency2_service": { | ||
"class":"%depency2_service.file%", | ||
"arguments":[] | ||
}, | ||
"my_service": { | ||
"class":"%my_service.file%" // Parameters use % symbols, services use @ | ||
"arguments": ["@dependency_service", "%my_service.example_param%", "@?optional_service"] // Optional services have @? at the beginning | ||
"calls": [ | ||
["setSecondDependency", ["@dependency2_service"]] // Method calls have the method name and an array of arguments | ||
], | ||
"properties": { | ||
"myServiceProperty":"%my_service.obj_param%" | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Examples | ||
|
||
Check out the `example` directory to see some of the more common use cases for a | ||
service container. | ||
|
||
Included: | ||
* Basic usage | ||
* Overriding/Using mocks with the environment option | ||
* Using an argument as a service | ||
|
||
|
||
##TODO | ||
* tags | ||
* scoped services | ||
|
||
## Run the Tests | ||
|
||
make test | ||
|
||
## Create Unit Test Coverage | ||
|
||
make coverage | ||
|
||
Open the file `coverage.html` that gets generated in the root directory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/******************************************************************************* | ||
* | ||
* INDEX.JS | ||
* | ||
* Author: Brandon Eum | ||
* Date: July 2013 | ||
* | ||
******************************************************************************/ | ||
|
||
/** | ||
* Construct a Builder object | ||
*/ | ||
module.exports = (function () { | ||
var fs, Builder, Container, Definition; | ||
fs = require('fs'); | ||
Builder = require('./lib/Builder'); | ||
Container = require('./lib/Container'); | ||
Definition = require('./lib/Definition'); | ||
|
||
// Create an instance of the builder with the proper dependencies injected | ||
var builder = new Builder(fs, require, Container, Definition); | ||
|
||
// Return the initialized builder | ||
return builder; | ||
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* Example class that logs messages to the console | ||
*/ | ||
var Messenger = function Messenger(msg) { | ||
this.message = msg || ''; | ||
}; | ||
|
||
Messenger.prototype.setMessage = function (msg) { | ||
this.message = msg; | ||
}; | ||
|
||
Messenger.prototype.printMessage = function () { | ||
console.log(this.message); | ||
}; | ||
|
||
module.exports = Messenger; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* Example class takes the example messenger classes and prints all messages | ||
* to the console | ||
*/ | ||
var MessengerManager = function MessengerManager() { | ||
this.messengers = []; | ||
}; | ||
|
||
MessengerManager.prototype.addMessenger = function (messenger) { | ||
this.messengers.push(messenger); | ||
}; | ||
|
||
MessengerManager.prototype.printAllMessages = function () { | ||
var i; | ||
for (i in this.messengers) { | ||
this.messengers[i].printMessage(); | ||
} | ||
}; | ||
|
||
module.exports = MessengerManager; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* Example mock class | ||
* | ||
* You can achieve similar things with sinon.js for unit testing purposes | ||
*/ | ||
var MockMessenger = function MockMessenger(msg) { | ||
this.message = msg || ''; | ||
}; | ||
|
||
MockMessenger.prototype.setMessage = function (msg) { | ||
this.message = msg; | ||
}; | ||
|
||
MockMessenger.prototype.printMessage = function () { | ||
console.log('This is a test: ' + this.message); | ||
}; | ||
|
||
module.exports = MockMessenger; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/** | ||
* An example of how to override the parameters based on environment | ||
* | ||
* In the simpleExample we used the same class multiple times with different | ||
* parameters to demonstrate the power of the service container. This time the | ||
* demo is about overriding the messages and the service class using a separate | ||
* services_test.json file. | ||
* | ||
*/ | ||
|
||
var ServiceContainer, container, helloMessenger, worldMessenger, exclamationMessenger; | ||
|
||
// Create a service container with this directory as the root - this will load | ||
// the services.json file from this directory and create service definitions | ||
// Passing the options as the second parameter with the env specified as "test" | ||
// will force the container to load services_test.json files | ||
ServiceContainer = require('../ServiceContainer'); | ||
container = ServiceContainer.buildContainer(__dirname, {env: 'test'}); | ||
|
||
// Grab instances of the services - the underlying class will be different | ||
helloMessenger = container.get('hello_messenger'); | ||
worldMessenger = container.get('world_messenger'); | ||
exclamationMessenger = container.get('exclamation_messenger'); | ||
|
||
|
||
// Using a different class, the printMessage function is overridden and appears | ||
// differently | ||
// This is great for injecting mock classes (with a mock library like sinon.js) | ||
// to unit test specific JS Objects | ||
helloMessenger.printMessage(); // Print "This is a test: Hello" | ||
worldMessenger.printMessage(); // Print "This is a test: world" | ||
exclamationMessenger.printMessage(); // Print "This is a test: !" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* An example of how to use another service as an argument | ||
* | ||
* In the services.json file for the messenger_manager service, the "addMessenger" | ||
* method is called with the 3 services we saw in the simpleExample.js. | ||
* | ||
* By using Setter Injection through the calls array, we can add an arbitrary | ||
* number of messenger services controlled through the configuration file and | ||
* not through code. | ||
*/ | ||
|
||
var ServiceContainer, container, helloMessenger, worldMessenger, exclamationMessenger; | ||
|
||
// Create a service container with this directory as the root - this will load | ||
// the services.json file from this directory and create service definitions | ||
ServiceContainer = require('../ServiceContainer'); | ||
container = ServiceContainer.buildContainer(__dirname); | ||
|
||
// Grab an instance of the messenger manager service | ||
messengerManager = container.get('messenger_manager'); | ||
|
||
// Loop through all of the services that were added through setter injection and | ||
// print their messages | ||
messengerManager.printAllMessages(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"parameters": { | ||
"messenger.file":"./Messenger", | ||
"messenger_manager.file":"./MessengerManager", | ||
"hello_messenger.message":"Hello ", | ||
"world_messenger.message":"World", | ||
"exclamation_messenger.message":"!" | ||
}, | ||
"services": { | ||
"hello_messenger": { | ||
"class":"%messenger.file%", | ||
"arguments":["%hello_messenger.message%"] | ||
}, | ||
"world_messenger": { | ||
"class":"%messenger.file%", | ||
"arguments":[], | ||
"calls": [ | ||
["setMessage", ["%world_messenger.message%"]] | ||
] | ||
}, | ||
"exclamation_messenger": { | ||
"class":"%messenger.file%", | ||
"properties": { | ||
"message":"%exclamation_messenger.message%" | ||
} | ||
}, | ||
"messenger_manager": { | ||
"class":"%messenger_manager.file%", | ||
"calls": [ | ||
["addMessenger", ["@hello_messenger"]], | ||
["addMessenger", ["@world_messenger"]], | ||
["addMessenger", ["@exclamation_messenger"]] | ||
] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"parameters": { | ||
"messenger.file":"./MockMessenger" | ||
} | ||
} |
Oops, something went wrong.