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 }