Dependency Injection for NodeJS using Javascript decorators
Start with NodeSpring, it's very easy:
Installation:
$ npm install nodespring --save
The main file must be a class that inherits from ExpressApp:
import {ExpressApp} from 'nodespring'
export default class App extends ExpressApp {
constructor() {
super({
port: 5000,
classDir: __dirname,
implConfig: {
'/interfaces/DBService': './services/DBServiceImpl'
}
})
}
}
new App().start()
Using Express is optional, if you don't want to use it, you can create your own middleware extending from NodeSpringApp.
port: The port where your server is going to run
classDir: The directory where all your classes are placed
implConfig: If you have several implementations for the same interface, would be necessary to define, which one of them is going
to be used
A Controller is a Javascript class where you define the end-points you have in your API. Controllers are used to handle the HTTP requests and call the service layer.
This decorator expects a class to be used as a Controller
import {Controller} from 'nodespring'
@Controller
export default class MyController {
// Methods to handle the HTTP requests
}
When you create a Controller, an URL is automatically created: http://localhost:8080/MyController
If you want to change the name used in the URL, you can do:
@Controller({path: 'users'})
export default class MyController {
...
}
Now we can access this controller in this way: http://localhost:8080/users
Following, the decorators used to handle the common HTTP methods, they expect a method to be used as end-point and can be used only in a Controller class.
import {Controller, Post, Get} from 'nodespring'
@Controller
export default class MyController {
@Get
getMessage() {
return "Message from MyController"
}
@Post
getGreet(name) {
return "Hi " + name
}
}
Also if you want to return a JSON or any other kind of contentType, you can do:
@Post({contentType: 'application/json'})
getJSONObject(id, name) {
return {
id: id,
userName: name
}
}
Each method is an end-point:
http://localhost:8080/MyController/getMessage http://localhost:8080/MyController/getJSONObjectAlso, there are decorators to the other HTTP methods:
@Put, @Delete, @Update
A Service is a Javascript class where you define the methods to access a specific module of your application, let's understand
a module as a unit on your business logic, don't think on NodeJS modules, let's assume that a module is a set of NodeJS modules which
have a specific responsibility.
These modules usually need to communicate each other, but isn't a good practice to call directly a module from other one, it's preferable
to have a Service layer where this communication is defined.
This decoractor expects a class to be used as a Service, all the services are singleton, it means, only one instance
of this class is going to be created and shared through all the application.
Each time a Service is injected, the unique instance is used, that's why all the Services
must to be stateless.
import {Service} from 'nodespring'
@Service
export default class MyService {
// Methods to communicate with the module
}
The Dependency Injection (DI) is a common pattern used in several frameworks, if you're a Java programmer and have worked with frameworks like Spring,
you will miss some of those great features on NodeJS.
Yes, NodeJS is not Java, I'm not pretending to make it like that, but honestly, going through several DI frameworks for NodeJS, I found them too verbose and
compilcated to understad, so that's why, taking advantage of the decorators which are an experimental feature on Javascript, I've created
this to make things like DI easier.
This decoractor expects a class to be used as an Interface.
An Interface basically represents the shape of an object without specify its behavior, in this way, each time you need to use an object from
that class, you aren't going to deal with a specific implementation of it, but with its interface. The only thing you need to know about that
class is what it's offering, what are the methods you can use, the implementation details doesn't matter to other modules.
import {Interface} from 'nodespring'
@Interface
export default class DBService {
saveEntity(entity) {}
getEntityList(entityType) {}
}
As you notice, we are only specifying what are the methods that other modules will be able to use.
This decoractor expects a class to be used as an Implementation.
An Implementation is where you define the behaviour of a specific Interface, where all your third-party libraries are used.
Following, a dummy example what an implementation does:
import {Implements} from 'nodespring'
import DBService from './DBService'
// Assuming you're using mongoose to create your models
import EntityModel from './models/EntityModel'
@Implements(DBService)
export default class DBServiceMongoDB {
saveEntity(entity) {
new EntityModel({
id: '1',
name: 'entity'
}).save(() => {
console.log('saved sucessfully!')
})
}
deleteEntity(id) {
EntityModel.find({id: id}).remove(() => {
console.log('Removed!')
})
}
}
As you can see, all the details are here, if tomorrow we need to start using MySQL instead of MongoDB, the only thing we will need to do is to create another implementation of DBService and the other modules don't need to be aware about it because they inject using an interface and NodeSpring resolves which implementation has to be used.
This decorator expects an Interface as a parameter and it can be used only in class properties.
This is the magic part, we are going to inject our DBService in MyService and make use of it.
import {Service, Inject} from 'nodespring'
import DBService from './DBService'
@Service
export default class MyService {
@Inject(DBService)
dbService
saveEntity() {
let entity = {
id: 1,
name: 'entityName'
}
dbService.saveEntity(entity)
}
deleteEntity() {
let id = 1
dbService.deleteEntity(id)
}
}
Here, you can notice that MyService doesn't know I'm using MongoDB, also, I'm not creating the instance directly:
dbService = new DBServiceMongoDB()
Because in this way, I'd be creating a hard dependency between those classes and testing this class will be harder.
Instead, I'm injecting using the interface and NodeSpring will resolve at runtime the implementation that needs to be used.
This resolves several problems, the first one is that our code is now decoupled, which is great,
the other one is unit testing, I'll be able to mock all those interfaces providing fake implementations in order to test a specific
implementation with real isolation.
Finally, all of this ends with a code easier to maintain and be modified by other programmers.
There are some considerations about injecting dependencies:
@Implements(DBService, Scope.PROTOTYPE)In this way, a new instance of the implementation is created each time the interface DBService is injected.
This decorator expects a method to be executed once all the dependencies injected are resolved.
Although you can use the constructor in each Javascript class, it's important to know that it's possible
all the declared dependencies aren't resolved at the moment when the constructor is called, this is because all the dependencies are injected
setting them in the instance which is being created.
import {PostInject} from 'nodespring'
@PostInject
init() {
// All the dependencies were injected
}
One of the good parts of using Dependency Injection is how easy is to test our code, the injection will allow us to isolate our code without pain.
You can mock the injected dependencies in each test, it allows you to define the way that a certain object behaves, this is pretty similar what you can do with Mockito in Java ecosystem.
This decorator expects an class as a parameter, it's basically to tell to NodeSpring that it's a testing class in order to prepare all the necessary stuff to execute each test.
import {TestClass} from 'nodespring'
@TestClass
export default class MyServiceTest {
...
}
This decorator expects an Interface as a parameter and it can be used only in class properties.
Mocks an interface basically will assign a fake implementation that you can adjust during the test in order to isolate the code you're testing.
import {TestClass, Mock} from 'nodespring'
import DBService from './DBService'
@TestClass
export default class MyServiceTest {
@Mock(DBService)
dbServiceMock
...
}
This decorator expects an Interface as a parameter and it can be used only in class properties.
When you're testing, you have basically two things: the dependencies and the class you want to test.
Once, you have mocked all the dependencies, you will need to inject those fake implementations in the real object you're gonna test.
import {TestClass, Mock, InjectMocks} from 'nodespring'
import DBService from './DBService'
import MyService from './MyService'
@TestClass
export default class MyServiceTest {
@Mock(DBService)
dbServiceMock
@InjectMocks(MyService)
myService
...
}
This decorator expects a method as a parameter.
Sometimes, it's necessary to prepare objects or data before to run each test, but remember, those tests should be stateless, it means, you have to prepare all you need for each test in a separate way, they shouldn't depend on any other test.
This is essentially because you can have asynchronous operations inside of the class you're testing, it means, you cannot guarantee in which order the tests
are going to finish.
This method decorated with @Before is executed just before to call each test, so, if you have three tests, the before method is execute three times.
This is useful when you need to prepare something that is common to all the tests just before to start.
import {TestClass, Mock, InjectMocks} from 'nodespring'
import DBService from './DBService'
import MyService from './MyService'
@TestClass
export default class MyServiceTest {
@Mock(DBService)
dbServiceMock
@InjectMocks(MyService)
myService
@Before
initTest() {
// stuff before test
}
}
This decorator expects a method as a parameter.
Each method that is a test, must be decorated with this, otherwise, it isn't going to be called, unless you call it from a test method.
Additionally, each test method will receive an assert object, that is basically an instance of the great npm package:
assert.
In order to support asynchronous testing, there's an additional method that you can call when your test is completed: assert.done().
import {TestClass, Mock, InjectMocks} from 'nodespring'
import DBService from './DBService'
import MyService from './MyService'
@TestClass
export default class MyServiceTest {
@Mock(DBService)
dbServiceMock
@InjectMocks(MyService)
myService
@Before
initTest() {
// stuff before test
}
@Test
test1(assert) {
this.dbServiceMock.saveEntity = (entityType, entity) => {
// Simulating async behavior
setTimeout(() => {
// You can use all the methods in "assert" npm package
assert.equal(true, true)
// Call done() method to finish the current test like in NodeUnit
assert.done()
}, 5000)
}
}
}