1 /**
2 Aedi, a dependency injection library.
3 
4 Aedi is a dependency injection library. It does provide a set of containers that do
5 IoC, and an interface to configure application components (structs, objects, etc.)
6 
7 Aim:
8 
9 The aim of library is to provide a dependency injection solution that is
10 feature rich, easy to use, easy to learn, and easy to extend up to your needs.
11 
12 Aedi does provide basic containers singleton and prototype, which can be combined into some-
13 thing more complex. Though, on top of their behavior additional one can be built, such as a container
14 that can be enabled/disabled, or a container that adds observer pattern. Out of the box framework does provide
15 following decorating containers:
16 
17 $(UL
18     $(LI aliasing - provides aliasing functions)
19     $(LI gc rigistered - registers instantiated components with gc, when other that gc allocator are used)
20     $(LI subscribable - provides observable pattern on container events)
21     $(LI typed - serves components based on their implemented interfaces)
22     $(LI proxy - serves component proxies instead of original ones)
23     $(LI deffered - provides deffered construction of components)
24 )
25 
26 Each decorating container listed below will be explained in detail, followed by a short explanation of what current example does.
27 
28 Aliasing:
29 
30 The aliasing container decorator adds ability to add aliases to existing components in container. Therefore
31 a "foo" component can have multiple aliases "moo", "boo" etc. Each alias will resolve to same component in container.
32 Such feature is useful when an existing component in container for compatibility reasons should have another identity as
33 well. To use aliasing container simply wrap any container in $(D_INLINECODE aliasing) method which will decorate existing one.
34 UFCS syntax is desired for fluent like style.
35 
36 -----------------
37 auto cont = aggregate(
38     singleton, "singleton",
39     prototype, "prototype",
40     values, "parameters",
41     gasoline, "gasoline",
42     electric, "electric",
43     diesel, "diesel"
44 ).aliasing;
45 -----------------
46 
47 GcRegistered:
48 
49 GC registered decorating container registers any created component with global gc available in D. Since any component can have customized
50 memory allocation strategy through std.experimental.allocator, were one of strategy is to use garbage collector, and each component
51 can have dependencies to other components, in some cases, gc on collection cycle could deallocate a component (which technically isn't
52 referenced by anyone) even if it is still referenced by other component of which gc is not aware of. GC registered container aims to register
53 all public components provided by underlying container into GC in order to avoid the case from above. To use it, just suffix any container with $(D_INLINECODE gcRegistered).
54 
55 -----------------
56 auto cont = aggregate(
57     singleton, "singleton",
58     prototype, "prototype",
59     values, "parameters",
60     gasoline, "gasoline",
61     electric, "electric",
62     diesel, "diesel"
63 ).aliasing.gcRegistered;
64 -----------------
65 
66 Subscribable:
67 
68 Subscribable container offers a list of events to which a listener can subscribe and perform operations on. Currently only two events are provided:
69 $(OL
70     $(LI instantiation pre event - fired before instantiation of container)
71     $(LI instantiation post event - fired after instantiation of container)
72 )
73 An example of such listeners can be like in listing below, which will enable a container before instantiation based on profile argument
74 -----------------
75 cont.subscribable.subscribe(
76     ContainerInstantiationEventType.pre,
77     {
78         if (cont.has(cont.locate!string("profile"))) {
79             cont.locate!Switchable(cont.locate!string("profile")).enabled = true;
80             return;
81         }
82 
83         throw new Exception("No engine profile selected. Specify it with -p");
84     }
85 );
86 -----------------
87 
88 Typed:
89 
90 Typed container decorates underlying container with ability to provide a component based on interface it implements.
91 Code using typed container attempts to get a component by an interface such as $(D_INLINECODE Engine) in this example,
92 typed container will check if underlying container has a component identified by provided interface. If yes it will give
93 the component from decorated container. If not, it will search for all registered components that implement the required interface
94 and use first found component to serve it. Notice that in this example, no $(D_INLINECODE Engine) was registered in container by
95 $(D_INLINECODE Engine) interface. Each instance $(D_INLINECODE GasolineEngine), $(D_INLINECODE ElectricEngine), and $(D_INLINECODE DieselEngine)
96 are registered by their type only. No attempt to alias one of them to $(D_INLINECODE Engine) is done, yet when Car is instantiated, a component
97 implementing $(D_INLINECODE Engine) interface is supplied. This magic is done by typed container, which supplied first available component implementing
98 $(D_INLINECODE Engine) interface.
99 
100 Proxy:
101 
102 A proxy container proxies all components supplied by decorated container. It will supply proxy objects instead of real components, which in turn
103 will on each public call of a proxy will redirect it to component in redirected container, and return a value from it. The proxy container
104 alone is not much useful. It is intended to be used along with containers that are dependendet on some global state to provide components.
105 An example of such containers could be containers that provide different instances of same component per thread or fiber, or in case of
106 web application, a container that gives new instances of same component for each new available request web application receives.
107 
108 Deffered:
109 
110 Deffered container, executes delayed components construction and configuration. Construction and configuration of a component can occur in cases
111 when there is a circular dependency between a set of components. In such case to resolve it, injection of one dependency can be delayed.
112 Delaying of construction or configuration is left to component factory, while execution of delayed action is enforced upon Deffered container.
113 To enable circular dependency resolution, decorate container with $(D_INLINECODE deffered) decorator, and register components using $(D_INLINECODE withConfigurationDefferring)
114 configuration context (simply append it after $(D_INLINECODE container.configure) method). This exampe shows a typical example of circular
115 dependency. A car has a dependency on four tires, while each tire has a dependency on a car instance, resulting in a circular dependency.
116 Removing $(D_INLINECODE deffered) or $(D_INLINECODE withConfigurationDefferring) will result in circular dependency exception thrown by container.
117 
118 Example:
119 Such behavior can be useful, like in car company example, that at certian point of time decided that their application should provide
120 upon start cars with different engines, electric/diesel/gasoline.
121 
122 The workflow needed to implement in application in order to allow 3 different configurations is
123 shown below and consists of following steps:
124 $(OL
125     $(LI read profile argument from command line or environment )
126     $(LI switch on it, and register diesel/gasoline/electric engine by Engine interface depending on selected profile )
127 )
128 
129 Following snippet of code shows all decorating containers in use except of proxy one.
130 
131 ------------------
132 auto decorated() {
133     auto gasoline = singleton.typed.switchable.enabled(false);
134     auto electric = singleton.typed.switchable.enabled(false);
135     auto diesel = singleton.typed.switchable.enabled(false);
136 
137     auto cont = aggregate(
138         singleton, "singleton",
139         prototype, "prototype",
140         values, "parameters",
141         gasoline, "gasoline",
142         electric, "electric",
143         diesel, "diesel"
144     ).aliasing.gcRegistered.deffered;
145 
146     return cont.subscribable.subscribe(
147         ContainerInstantiationEventType.pre,
148         {
149             if (cont.has(cont.locate!string("profile"))) {
150                 cont.locate!Switchable(cont.locate!string("profile")).enabled = true;
151                 return;
152             }
153 
154             throw new Exception("No engine profile selected. Specify it with -p");
155         }
156     );
157 }
158 ------------------
159 
160 The profile based container is assembled from 3 typed and switchable containers, and a subscribable
161 composite container with gc component registration and construction of deffered components.
162 When the application is booted up, the code from $(D_INLINECODE main(string[] args)) loads into container
163 "profile" argument. Afterwards components are registered into container, and for each profile,
164 the right engine is registered in respective gasoline, electric, diesel containers. Once this
165 is finished, the container is instantiated using $(D_INLINECODE intantiate) method. During instantiation phase,
166 subscribable composite container fires an pre-instantiation event on which, a delegate is attached, that
167 checks for "profile" argument, and enables the container identified by value in profile container.
168 Afterwards construction of components proceeds. When car is constructed typed container jumps in and
169 provides an implenentation of $(D_INLINECODE Engine) for car depending on enabled container. When
170 construction arrives at a Tire, a circular dependency is detected, and construction of a component is
171 deffered at later stage in order to break dependency chain. Once component is constructed, it is registered
172 with global gc instance in order to avoid destruction of gc managed components while they are still referenced
173 by non-managed components. Once instantiation is finished, car is fetched from container and displayed in console.
174 
175 Try running this example, pass as argument $(D_INLINECODE --profile) with value of
176 $(D_INLINECODE gasoline), $(D_INLINECODE electric), $(D_INLINECODE diesel).
177 Experiment with it, to understand decorating containers.
178 
179 License:
180 	Boost Software License - Version 1.0 - August 17th, 2003
181 
182 	Permission is hereby granted, free of charge, to any person or organization
183 	obtaining a copy of the software and accompanying documentation covered by
184 	this license (the "Software") to use, reproduce, display, distribute,
185 	execute, and transmit the Software, and to prepare derivative works of the
186 	Software, and to permit third-parties to whom the Software is furnished to
187 	do so, all subject to the following:
188 
189 	The copyright notices in the Software and this entire statement, including
190 	the above license grant, this restriction and the following disclaimer,
191 	must be included in all copies of the Software, in whole or in part, and
192 	all derivative works of the Software, unless such copies or derivative
193 	works are solely in the form of machine-executable object code generated by
194 	a source language processor.
195 
196 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
197 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
198 	FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
199 	SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
200 	FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
201 	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
202 	DEALINGS IN THE SOFTWARE.
203 
204 Authors:
205 	aermicioi
206 **/
207 
208 module decorating_containers;
209 
210 import aermicioi.aedi;
211 import std.stdio;
212 import std.algorithm;
213 import std.experimental.allocator.mallocator;
214 import std.experimental.allocator;
215 
216 /**
217 A struct that should be managed by container.
218 **/
219 struct Color {
220     ubyte r;
221     ubyte g;
222     ubyte b;
223 }
224 
225 /**
226 Size of a car.
227 **/
228 struct Size {
229 
230     ulong width;
231     ulong height;
232     ulong length;
233 }
234 
235 /**
236 Interface for engines.
237 
238 An engine should implement it, in order to be installable in a car.
239 **/
240 interface Engine {
241 
242     public {
243 
244         void turnOn();
245         void run();
246         void turnOff();
247     }
248 }
249 
250 /**
251 A concrete implementation of Engine that uses gasoline for propelling.
252 **/
253 class GasolineEngine : Engine {
254 
255     public {
256 
257         void turnOn() {
258             writeln("pururukVrooomVrrr");
259 
260         }
261 
262         void run() {
263             writeln("vrooom");
264         }
265 
266         void turnOff() {
267             writeln("vrrrPrrft");
268         }
269     }
270 }
271 
272 /**
273 A concrete implementation of Engine that uses diesel for propelling.
274 **/
275 class DieselEngine : Engine {
276 
277     public {
278 
279         void turnOn() {
280             writeln("pururukVruumVrrr");
281 
282         }
283 
284         void run() {
285             writeln("vruum");
286         }
287 
288         void turnOff() {
289             writeln("vrrrPft");
290         }
291     }
292 }
293 
294 /**
295 A concrete implementation of Engine that uses electricity for propelling.
296 **/
297 class ElectricEngine : Engine {
298     public {
299 
300         void turnOn() {
301             writeln("pzt");
302 
303         }
304 
305         void run() {
306             writeln("vvvvvvvvv");
307         }
308 
309         void turnOff() {
310             writeln("tzp");
311         }
312     }
313 }
314 
315 /**
316 Tire, what it can represent else?
317 **/
318 class Tire {
319     private {
320         int size_;
321         float pressure_;
322         string vendor_;
323         Car car_;
324     }
325 
326     public @property {
327         @autowired
328         Tire car(Car car) {
329             this.car_ = car;
330             return this;
331         }
332 
333         Car car() {
334             return this.car_;
335         }
336 
337         Tire size(int size) @safe nothrow {
338         	this.size_ = size;
339 
340         	return this;
341         }
342 
343         int size() @safe nothrow {
344         	return this.size_;
345         }
346 
347         Tire pressure(float pressure) @safe nothrow {
348         	this.pressure_ = pressure;
349 
350         	return this;
351         }
352 
353         float pressure() @safe nothrow {
354         	return this.pressure_;
355         }
356 
357         Tire vendor(string vendor) @safe nothrow {
358         	this.vendor_ = vendor;
359 
360         	return this;
361         }
362 
363         string vendor() @safe nothrow {
364         	return this.vendor_;
365         }
366     }
367 
368     public override string toString() {
369         import std.algorithm;
370         import std.range;
371         import std.conv;
372         import std.utf;
373 
374         return only("Tire(", this.size.to!string, " inch, ", this.pressure.to!string, " atm, ", this.vendor, ")")
375             .joiner
376             .byChar
377             .array;
378     }
379 }
380 
381 /**
382 A class representing a car.
383 **/
384 class Car {
385 
386     private {
387         Color color_; // Car color
388         Size size_; // Car size
389         Engine engine_; // Car engine
390 
391         Tire frontLeft_;
392         Tire frontRight_;
393         Tire backLeft_;
394         Tire backRight_;
395     }
396 
397     public {
398 
399         /**
400         Constructor of car.
401 
402         Constructs a car with a set of sizes. A car cannot of course have
403         undefined sizes, so we should provide it during construction.
404 
405         Params:
406             size = size of a car.
407         **/
408         this(Size size, Engine engine) {
409             this.size_ = size;
410             this.engine = engine;
411         }
412 
413         @property {
414 
415             /**
416             Set color of car
417 
418             Set color of car. A car can live with undefined color (physics allow it).
419 
420             Params:
421             	color = color of a car.
422 
423             Returns:
424             	Car
425             **/
426             Car color(Color color) @safe nothrow {
427             	this.color_ = color;
428 
429             	return this;
430             }
431 
432             Color color() @safe nothrow {
433             	return this.color_;
434             }
435 
436             Size size() @safe nothrow {
437             	return this.size_;
438             }
439 
440             /**
441             Set engine used in car.
442 
443             Params:
444             	engine = engine used in car to propel it.
445 
446             Returns:
447             	Car
448             **/
449             Car engine(Engine engine) @safe nothrow {
450             	this.engine_ = engine;
451 
452             	return this;
453             }
454 
455             Engine engine() @safe nothrow {
456             	return this.engine_;
457             }
458 
459             Car frontLeft(Tire frontLeft) @safe nothrow {
460             	this.frontLeft_ = frontLeft;
461 
462             	return this;
463             }
464 
465             Tire frontLeft() @safe nothrow {
466             	return this.frontLeft_;
467             }
468 
469             Car frontRight(Tire frontRight) @safe nothrow {
470             	this.frontRight_ = frontRight;
471 
472             	return this;
473             }
474 
475             Tire frontRight() @safe nothrow {
476             	return this.frontRight_;
477             }
478 
479             Car backLeft(Tire backLeft) @safe nothrow {
480             	this.backLeft_ = backLeft;
481 
482             	return this;
483             }
484 
485             Tire backLeft() @safe nothrow {
486             	return this.backLeft_;
487             }
488 
489             Car backRight(Tire backRight) @safe nothrow {
490             	this.backRight_ = backRight;
491 
492             	return this;
493             }
494 
495             Tire backRight() @safe nothrow {
496             	return this.backRight_;
497             }
498 
499         }
500 
501         void start() {
502             engine.turnOn();
503         }
504 
505         void run() {
506             engine.run();
507         }
508 
509         void stop() {
510             engine.turnOff();
511         }
512     }
513 }
514 
515 void drive(Car car, string name) {
516     writeln("Uuh, what a nice ", name," car, with following specs:");
517     writeln("Size:\t", car.size);
518     writeln("Color:\t", car.color);
519     writeln("Engine:\t", car.engine);
520     writeln("Tire front left:\t", car.frontLeft, "\t located at memory ", cast(void*) car.frontLeft());
521     writeln("Tire front right:\t", car.frontRight, "\t located at memory ", cast(void*) car.frontRight());
522     writeln("Tire back left: \t", car.backLeft, "\t located at memory ", cast(void*) car.backLeft());
523     writeln("Tire back right:\t", car.backRight, "\t located at memory ", cast(void*) car.backRight());
524 }
525 
526 auto decorated() {
527     auto gasoline = singleton.typed.switchable.enabled(false);
528     auto electric = singleton.typed.switchable.enabled(false);
529     auto diesel = singleton.typed.switchable.enabled(false);
530 
531     auto cont = aggregate(
532         singleton, "singleton",
533         prototype, "prototype",
534         values, "parameters",
535         gasoline, "gasoline",
536         electric, "electric",
537         diesel, "diesel"
538     ).aliasing.gcRegistered.deffered;
539 
540     return cont.subscribable.subscribe(
541         ContainerInstantiationEventType.pre,
542         {
543             if (cont.has(cont.locate!string("profile"))) {
544                 cont.locate!Switchable(cont.locate!string("profile")).enabled = true;
545                 return;
546             }
547 
548             throw new Exception("No engine profile selected. Specify it with -p");
549         }
550     );
551 }
552 
553 void main(string[] args) {
554 
555     auto cont = decorated();
556     scope(exit) container.terminate();
557 
558     import std.getopt;
559     string profile;
560 
561     auto help = getopt(args,
562         "p|profile", &profile
563     );
564 
565     with (cont.locate!ValueContainer("parameters").configure) {
566 
567         register(profile, "profile");
568     }
569 
570     with (cont.configure("singleton", Mallocator.instance.allocatorObject)) {
571         register!Color;
572 
573         register!Size
574             .set!"width"(200UL)
575             .set!"height"(150UL)
576             .set!"length"(300UL);
577 
578         register!Car
579             .autowire
580             .autowire!"color"
581             .set!"frontLeft"(lref!Tire)
582             .set!"frontRight"(lref!Tire)
583             .set!"backLeft"(lref!Tire)
584             .set!"backRight"(lref!Tire);
585     }
586 
587     with (cont.configure("prototype").withConfigurationDefferring) {
588         register!Tire
589             .autowire!"car"
590             .set!"size"(17)
591             .set!"pressure"(3.0)
592             .set!"vendor"("divine tire");
593     }
594 
595     cont.configure("diesel").register!DieselEngine;
596     cont.configure("gasoline").register!GasolineEngine;
597     cont.configure("electric").register!ElectricEngine;
598 
599     cont.link("electric", "ecological");
600 
601     cont.instantiate();
602 
603     cont.locate!Car.drive(profile);
604 }