Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 195 lines (152 sloc) 8.101 kb
a6a6069 added cake tasks
ezku authored
1 # Coffee-Injector in short
2
3 Coffee-Injector is a very small dependency injection container intended
4 to fit both Javascript as a language and node.js as an environment where
5 asynchronous execution is the rule rather than the exception. It is
57b7219 doc update
ezku authored
6 written in [CoffeeScript][cs] (a terrific language that compiles into
7 Javascript), takes advantage of the [node-promise][np] library for
a0870ee doc update
ezku authored
8 asynchrony and is unit tested using [vows](http://vowsjs.org/). Read on
a6a6069 added cake tasks
ezku authored
9 for details.
10
5beabd3 fixed markdown messup
ezku authored
11 [cs]: http://jashkenas.github.com/coffee-script/
12 [np]: http://github.com/kriszyp/node-promise
13
b1919f3 added recursion detection test case after many failing attempts
ezku authored
14 # Dependency injection the node.js way
a6a6069 added cake tasks
ezku authored
15
16 Dependency injection is an often used mechanism for achieving inversion
17 of control, a design principle related to the [D][d] in [SOLID][solid].
18 Dependency injection can be applied manually, but [a container
19 object][iocc] specifically built for the occasion can make a
20 programmer's life significantly easier. In statically typed languages,
21 injection can often be automated to a large degree through such an
22 approach with the alternative being meticulously handwritten resource
23 initializations. In dynamic languages such as Javascript and Ruby, type
24 information necessary for such automation is obviously going to be
25 missing, but there are still ways for a container to perform valuable
57b7219 doc update
ezku authored
26 work for you. Why is it useful?
a6a6069 added cake tasks
ezku authored
27
28 [d]: http://en.wikipedia.org/wiki/Dependency_inversion_principle
29 [solid]: http://en.wikipedia.org/wiki/SOLID
a0870ee doc update
ezku authored
30 [iocc]: http://martinfowler.com/articles/injection.html
a6a6069 added cake tasks
ezku authored
31
57b7219 doc update
ezku authored
32 Dependency injection is, at its core, about how you wire different parts
33 of your application together - about configuration. The sweet thing
34 about this is the configured things don't really have to be just object
35 graphs, but resources of any kind. Coffee-Injector borrows in ideology
36 from a minimalistic style applied in [Ruby][ruby-di] and [PHP][php-di]
37 that make use of closures for resource description. This, as opposed to
38 automatic constructor and setter injection of objects, allows the
39 container to remain agnostic of the kind of resources it stores. As
40 Fabien Potencier puts it,
a6a6069 added cake tasks
ezku authored
41
42 [ruby-di]: http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc
43 [php-di]: http://fabien.potencier.org/article/17/on-php-5-3-lambda-functions-and-closures
44
b1919f3 added recursion detection test case after many failing attempts
ezku authored
45 > "Defining objects with lambda functions is great because *the developer can do whatever he wants*"
a6a6069 added cake tasks
ezku authored
46
47 The essence of Coffee-Injector is to give you everything you need to set
48 up your resources and resolve their dependencies, then get out of the
57b7219 doc update
ezku authored
49 way. You get a way to configure your application in one place. Because
50 of the asynchronous nature of node.js, a sprinkle of Promises is added
165ae25 updated docs
ezku authored
51 on top, but that is the extent of it. No bells and whistles. (Promises
52 are one suggestion for a standard asynchronous interface suggested by
53 and discussed at [CommonJS][cjs-promise]. Go check it out!)
5ba7687 updated docs
ezku authored
54
55 [cjs-promise]: http://wiki.commonjs.org/wiki/Promises
b1919f3 added recursion detection test case after many failing attempts
ezku authored
56
964f02c added todo note
ezku authored
57 # Features and usage
b1919f3 added recursion detection test case after many failing attempts
ezku authored
58
59 ## Describing resources and using them
60
165ae25 updated docs
ezku authored
61 Using a dependency injection container should generally follow a pattern
62 of [register-resolve-release][rrr], where you'll first configure the
63 container, then retrieve root components and finally throw the container
64 away. You'll be left with a small set of components with their
65 dependencies fully resolved, meaning you're all set for launching your
66 application. With the last, or the "release" step, being trivial, let's
67 describe the first two in their basic forms with Coffee-Injector. We'll
68 use accessing a local file as a simple example to include some
69 asynchronous operations.
e2e2c03 updated docs, added one test case
ezku authored
70
71 [rrr]: http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasePattern.aspx
72
165ae25 updated docs
ezku authored
73 First, you'll need an instance of the container. This means requiring
74 the container class and instantiating it.
e2e2c03 updated docs, added one test case
ezku authored
75
76 Container = require 'path/to/coffee-injector/container.js'
77 c = new Container
78
165ae25 updated docs
ezku authored
79 There are two ways to register resources with the container. You can
80 either set a fully resolved value - for things that are neither
81 asynchronous nor dependent on other values - or provide a descriptor for
82 resolving said value. The first kind is trivial.
e2e2c03 updated docs, added one test case
ezku authored
83
84 c.set 'filename', 'example.txt'
85 if c.has 'filename'
86 console.log "Successfully set a resource in the container!"
87
165ae25 updated docs
ezku authored
88 To access the value, use - you guessed it - `get`. The result of `get`,
89 however, is not the resource as you'd expect, but a `Promise`. A
90 `Promise` has a singular method, `then`, that accepts two arguments: the
91 first one will be called if the promise was kept and resolved to a
92 value, the second one in case there was an error and the promise was
93 rejected. In this case, the promise will always immediately be resolved
94 to a value.
e2e2c03 updated docs, added one test case
ezku authored
95
96 c.get('filename').then (filename) ->
97 console.log filename
98
165ae25 updated docs
ezku authored
99 The other way is to provide a description of the process required to
100 access the resource. This description will be reused every time the
101 resource is accessed. There are two things to note: the description is
102 just code in which you may do whatever you want, but the results need to
103 be announced using callback functions provided by the container. Let's
104 look at how you would read a file using plain node.js libraries and then
105 transform it into a Coffee-Injector resource description.
e2e2c03 updated docs, added one test case
ezku authored
106
107 fs = require 'fs'
108 fs.readFile 'example.txt', (err, data) ->
109 throw err if err
110 console.log data
111
112 A trivial conversion will look something like the following.
113
114 c.describe 'example', (result, error) ->
115 fs.readFile 'example.txt', (err, data) ->
116 if err
117 error err
118 else
119 result data
120
165ae25 updated docs
ezku authored
121 We provide the container with a callback function that takes two
122 arguments, one for informing the container of a successful resource
123 acquisition and one for reporting an erroneous result. Once our
124 asynchronous callback function (that we passed to `readFile`) is
125 invoked, we take corresponding action based on the callback's input. One
126 thing you'll notice is that `example.txt` is explicitly contained in the
127 definition. To get rid of the explicitness, we'll need to retrieve the
128 value from the container itself. The descriptor function is ran in a
129 scope that has access to accessor methods like `get`. So the full
130 snippet, including using the resource, would look like this.
e2e2c03 updated docs, added one test case
ezku authored
131
357ff5e updated docs
ezku authored
132 fs = require 'fs'
133 Container = require 'path/to/coffee-injector/container.js'
134
135 c = new Container
136 c.set 'filename', 'example.txt'
e2e2c03 updated docs, added one test case
ezku authored
137 c.describe 'example', (result, error) ->
138 @get('filename').then (filename) ->
139 fs.readFile filename, (err, data) ->
140 if err
141 error err
142 else
143 result data
144
145 c.get('example').then (data) ->
146 console.log data
147
165ae25 updated docs
ezku authored
148 The result we get is a decoupling of the wiring and the execution parts
149 of the script.
e2e2c03 updated docs, added one test case
ezku authored
150
165ae25 updated docs
ezku authored
151 <!-- TODO
b1919f3 added recursion detection test case after many failing attempts
ezku authored
152 ## Resource description helpers
165ae25 updated docs
ezku authored
153 -->
57b7219 doc update
ezku authored
154
28af6cf added trivial cyclic dependency detection
ezku authored
155 ## Cyclic dependency detection
156
b1f73b2 fixed typo in docs
ezku authored
157 A cyclic dependency means a situation where a resource depends on itself
158 either directly (which is usually pretty easy to spot) or indirectly
159 through another resource or several (debugging which can be tedious).
160 Given the relative ease at which these situations can arise during
161 development, their prevention is an important feature in dependency
162 injection containers. Coffee-Injector obviously does this as well.
165ae25 updated docs
ezku authored
163
164 In some other languages, cyclic dependencies can be found through static
165 analysis of the dependency graph, but the dynamic nature of Javascript
166 limits us to detection during runtime. Therefore, the following code
167 alone will not fail:
168
169 c.describe 'foo', (result) ->
170 @get('foo').then (value) ->
171 result value
172
173 Attempting to access `foo`, however, will generate an exception:
174
175 try
176 c.get('foo')
177 catch e
178 console.log 'whoops, found a cyclic dependency'
179
180 Coffee-Injector opts for a fail-fast approach, which means exceptions
181 are favored for configuration errors over returning Promises that are
182 resolved to errors.
183
57b7219 doc update
ezku authored
184 # Installation and available cake tasks
185
186 Assuming you have node.js and npm installed:
187
188 npm install coffee-script
189 npm install vows
190 git clone git@github.com:Ezku/coffee-injector.git
191
192 Once you're done, you can run the unit tests with `cake test` or just
193 compile the coffeescript files with `cake compile`.
a6a6069 added cake tasks
ezku authored
194
Something went wrong with that request. Please try again.