UNPKG

5.41 kBMarkdownView Raw
1# depject
2
3> simplest dependency injection
4
5## Installation
6
7```sh
8$ npm install --save depject
9```
10
11## philosophy
12
13A module exposes features to be used by other modules,
14and may also depend on features provided by other modules.
15Any module system can do that. In the node module system,
16modules declare exactly which modules they depend on.
17That works well when the module does a very well defined task,
18that can be abstractly solved. In other words, it works well
19when the module solves a technical problem.
20
21But it doesn't work so well when the module just represents an opinion.
22Developer tools seem to be dominated by technical problems,
23but user applications seem to be dominated by opinions.
24There are many different ways something could be implemented,
25no objectively optimal solution, and loads of pretty good ones.
26
27The contemporary best practice is to embrace that, and create software
28that has strong opinions. That takes a strong leader to make decisions,
29compromises be dammed. I am building a p2p system, and have gone to
30considerable effort to create a decentralized protocol. But then,
31if we have a user interface with strong opinions, then that recentralizes development.
32
33My strong opinion is to reject strong opinions. `depject` is a strategy to
34deopinionate software. It should be easy to change any particular opinion.
35
36Another way to look at this, is the goal is to make pull-requests that merge easily.
37with node's module system, a dependant module must declare exactly which modules they depend on.
38That means, to add a feature, you need to add a new file implementing it,
39and also update files that use that.
40
41To contrast, in `depject` if that feature is the same _shape_ as one already existing,
42you only need to add that file. This means you can add merge two new features,
43with out a conflict.
44
45## patterns
46
47### first - use the first module that has an opinion about a thing.
48
49Say we have a system with multiple types of messages. Each type has a renderer.
50We want to call all the renderers, and get the first one that knows how to handle that value.
51
52### map - get each module's opinion about a thing.
53
54Say we have a menu that is actions which may be performed on a thing.
55We map the modules over that thing, and add all returned items to a menu.
56
57### reduce - compose each modules opinion about a thing into one opinion.
58
59We might want to allow other modules to decorate the value given by our module
60
61## example
62
63### Using `first`
64
65```js
66const combine = require('depject')
67
68const cats = {
69 gives: 'animalSound',
70 create: () => (type) => {
71 if(type !== 'cat') return
72 return 'Meow'
73 }
74}
75
76const dogs = {
77 gives: 'animalSound',
78 create: () => (type) => {
79 if(type !== 'dog') return
80 return 'Woof'
81 }
82}
83
84const speak = {
85 needs: {animalSound: 'first'},
86 gives: 'speak',
87 create: (api) => api.animalSound
88}
89
90const sockets = combine([cats, dogs, speak])
91
92const mySpeak = sockets.speak[0]
93
94console.log(mySpeak('dog'))
95//Woof
96```
97
98### Using `map`
99
100```js
101const combine = require('depject')
102
103const cats = {
104 gives: 'name',
105 create: () => () => 'Fluffy'
106}
107
108const dogs = {
109 gives: 'name',
110 create: () => () => 'Rex'
111}
112
113const animals = {
114 needs: {name: 'map'},
115 gives: 'animals',
116 create: (api) => api.name
117}
118
119var sockets = combine([cats, dogs, animals])
120
121var myAnimals = sockets.animals[0]
122
123console.log(myAnimals())
124//['Fluffy', 'Rex']
125```
126
127## api
128
129### modules
130
131Each module is an object which exposes `{needs, gives, create}` properties. `needs` and `gives` describe the module features that this module requires, and exports.
132
133`needs` is a map of names to types. `{<name> : "map"|"first"|"reduce"}`
134
135`gives` Is a string name of it's export, or if there are multiple exports an object where each key is a name `{<name>: true,...}`.
136
137`create` Is a function that is called with an object connected to modules which provide the `needs` and must return a value which provides the `gives` or an object with keys that match what the module `gives`.
138
139### combine
140
141Actually connect all the modules together!
142Takes an array of modules, resolves dependencies and injects them into each module.
143
144`combine([modules...])`
145
146This will return an array object of arrays of exports.
147
148## exporting more than one thing from a module
149
150```js
151const cats = {
152 gives: {name: true, animalSound: true},
153 create: () => ({
154 name: () => 'Fluffy',
155 animalSound: () => {
156 if(type !== 'cat') return
157 return 'Meow'
158 }
159 })
160}
161```
162
163## requiring more than one thing into a module
164
165```js
166const animalSounds = {
167 needs: {name: 'map', animalSound: 'first'}
168}
169```
170
171## deeply nested modules
172
173It's possible to pass deeply nested modules to combine eg:
174
175```js
176const modules = {
177 a: {
178 b: {
179 c: {
180 gives: 'yes',
181 create: function () {
182 return function () {
183 return true
184 }
185 }
186 }
187 },
188 d: {
189 e: {
190 needs: {
191 yes: 'first'
192 },
193 gives: 'no',
194 create: function (api) {
195 return function () {
196 return !api.yes()
197 }
198 }
199 }
200 }
201 }
202}
203
204const api = combine(modules)
205```
206
207### design questions
208
209Should there be a way to create a routed plugin?
210i.e. check a field and call a specific plugin directly?
211
212How does this interact with interfaces provided remotely?
213i.e. muxrpc?
214
215## License
216
217MIT © [Dominic Tarr](http://dominictarr.com)