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 Code api, offers a rich set of functionality, and flexibility. Though alternatives to 13 configuring a container with components should be provided along with their strengths 14 and disadvantages. For this purpose as an alternative to code api, the framework implements 15 annotation based configuration of components. 16 17 The main idea of annotation based configuration of components is that, 18 information about component's dependencies and configuration properties are 19 stored in form of annotations right in it. The framework does provide 20 annotation based counterparts of almost all configuration primitives available 21 in code api. Example below shows a component configured using annotation based information: 22 23 -------------------- 24 @component // Mark component to be registered in container 25 @qualifier("custom.identity") // Specify custom identity for component in container instead of it's type as identity 26 class Car { 27 28 // ... 29 30 public { 31 32 @constructor(lref!Size, lref!Engine) // Construct this component using arguments passed to constructor annotation 33 this(Size size, Engine engine) { 34 this.size_ = size; 35 this.engine = engine; 36 } 37 38 @property { 39 40 @setter("color.green".lref) // Set the property to arguments passed in setter 41 Car color(Color color) @safe nothrow { 42 this.color_ = color; 43 44 return this; 45 } 46 47 // ... 48 49 @setter(lref!Engine) // Set the property to arguments passed in setter 50 Car engine(Engine engine) @safe nothrow { 51 this.engine_ = engine; 52 53 return this; 54 } 55 56 // ... 57 58 @autowired // Automatically wire property using components from container identified by their types 59 Car frontLeft(Tire frontLeft) @safe nothrow { 60 this.frontLeft_ = frontLeft; 61 62 return this; 63 } 64 65 // ... 66 67 @callback( 68 function (Locator!() locator, ref Car configured) { 69 configured.frontRight = locator.locate!Tire; 70 } 71 ) // Use a callback to configure the property, or entire object. Can be attached anywhere on component 72 Car frontRight(Tire frontRight) @safe nothrow { 73 this.frontRight_ = frontRight; 74 75 return this; 76 } 77 78 // ... 79 80 @autowired // Automatically wire property using components from container identified by their types 81 Car backLeft(Tire backLeft) @safe nothrow { 82 this.backLeft_ = backLeft; 83 84 return this; 85 } 86 87 // ... 88 89 @autowired // Automatically wire property using components from container identified by their types 90 Car backRight(Tire backRight) @safe nothrow { 91 this.backRight_ = backRight; 92 93 return this; 94 } 95 96 // ... 97 98 } 99 100 // ... 101 } 102 } 103 -------------------- 104 105 As seen above describing a component using annotation consists of 106 annotating it with $(D_INLINECODE @component), optionally specyfing 107 an identity using $(D_INLINECODE @qualifier), and annotating it 108 with construction and configuration annotations such as: 109 $(UL 110 $(LI $(D_INLINECODE @constructor ) (only on constructors) - annotates component with it's construction dependencies ) 111 $(LI $(D_INLINECODE @setter ) (only on setters) - annotates component with it's setter based dependencies ) 112 $(LI $(D_INLINECODE @autowired ) (not on constructors) - annotates component with it's setter based dependencies automatically ) 113 $(LI $(D_INLINECODE @autowired ) (on constructors) - annotates component with it's construction dependencies automatically ) 114 $(LI $(D_INLINECODE @callback ) (not on constructors) - annotates component with a custom function used to configure component ) 115 $(LI $(D_INLINECODE @callback ) (on constructors) - annotates component with a custom function used to create component ) 116 $(LI $(D_INLINECODE @contained) (on component) - annotates component with information about container that manages it in a composite/joint container) 117 ) 118 119 Though annotations provide information about a component for framework, it does not 120 automatically register them into container. To add annotated components to a container 121 use $(D_INLINECODE scan ) family of functions. Example below shows how it is 122 possible to register an entire module using just one line of code: 123 124 ------------------- 125 container.scan!(app); // load all annotated components from module "app" 126 ------------------- 127 128 Other forms of scan exists. Check api documentation to see alternatives of module 129 based component registration if needed. 130 131 The result of running example, will yield into following output: 132 ------------------- 133 Uuh, what a nice car, Electric car with following specs: 134 Size: Size(200, 150, 500) 135 Color: Color(0, 255, 0) 136 Engine: app.ElectricEngine 137 Tire front left: Tire(17 inch, 3 atm, divine tire) located at memory 7FCC35376100 138 Tire front right: Tire(17 inch, 3 atm, divine tire) located at memory 7FCC35376140 139 Tire back left: Tire(17 inch, 3 atm, divine tire) located at memory 7FCC35376180 140 Tire back right: Tire(17 inch, 3 atm, divine tire) located at memory 7FCC353761C0 141 ------------------- 142 143 Check example below. Modify it, run it to understand annotation based configuration. 144 145 License: 146 Boost Software License - Version 1.0 - August 17th, 2003 147 148 Permission is hereby granted, free of charge, to any person or organization 149 obtaining a copy of the software and accompanying documentation covered by 150 this license (the "Software") to use, reproduce, display, distribute, 151 execute, and transmit the Software, and to prepare derivative works of the 152 Software, and to permit third-parties to whom the Software is furnished to 153 do so, all subject to the following: 154 155 The copyright notices in the Software and this entire statement, including 156 the above license grant, this restriction and the following disclaimer, 157 must be included in all copies of the Software, in whole or in part, and 158 all derivative works of the Software, unless such copies or derivative 159 works are solely in the form of machine-executable object code generated by 160 a source language processor. 161 162 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 163 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 164 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 165 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 166 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 167 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 168 DEALINGS IN THE SOFTWARE. 169 170 Authors: 171 aermicioi 172 **/ 173 174 module annotation_configuration; 175 176 import aermicioi.aedi; 177 import std.stdio; 178 179 /** 180 A struct that should be managed by container. 181 **/ 182 @component // Mark component to be registered in container 183 struct Color { 184 ubyte r; 185 ubyte g; 186 ubyte b; 187 } 188 189 /** 190 Size of a car. 191 **/ 192 @component // Mark component to be registered in container 193 struct Size { 194 195 @setter(200UL) // Set the property to arguments passed in setter 196 ulong width; 197 198 @setter(150UL) // Set the property to arguments passed in setter 199 ulong height; 200 201 @setter(500UL) // Set the property to arguments passed in setter 202 ulong length; 203 } 204 205 /** 206 Interface for engines. 207 208 An engine should implement it, in order to be installable in a car. 209 **/ 210 interface Engine { 211 212 public { 213 214 void turnOn(); 215 void run(); 216 void turnOff(); 217 } 218 } 219 220 /** 221 A concrete implementation of Engine that uses gasoline for propelling. 222 **/ 223 @component // Mark component to be registered in container 224 @qualifier("gasoline") 225 class GasolineEngine : Engine { 226 227 public { 228 229 void turnOn() { 230 writeln("pururukVrooomVrrr"); 231 232 } 233 234 void run() { 235 writeln("vrooom"); 236 } 237 238 void turnOff() { 239 writeln("vrrrPrrft"); 240 } 241 } 242 } 243 244 /** 245 A concrete implementation of Engine that uses diesel for propelling. 246 **/ 247 @component // Mark component to be registered in container 248 @qualifier("diesel") 249 class DieselEngine : Engine { 250 251 public { 252 253 void turnOn() { 254 writeln("pururukVruumVrrr"); 255 256 } 257 258 void run() { 259 writeln("vruum"); 260 } 261 262 void turnOff() { 263 writeln("vrrrPft"); 264 } 265 } 266 } 267 268 /** 269 A concrete implementation of Engine that uses electricity for propelling. 270 **/ 271 @component // Mark component to be registered in container 272 @qualifier!Engine() 273 class ElectricEngine : Engine { 274 public { 275 276 void turnOn() { 277 writeln("pzt"); 278 279 } 280 281 void run() { 282 writeln("vvvvvvvvv"); 283 } 284 285 void turnOff() { 286 writeln("tzp"); 287 } 288 } 289 } 290 291 /** 292 Tire, what it can represent else? 293 **/ 294 @component // Mark component to be registered in container 295 @contained("prototype") 296 class Tire { 297 private { 298 int size_; 299 float pressure_; 300 string vendor_; 301 } 302 303 public @property { 304 @setter(17) // Set the property to arguments passed in setter 305 Tire size(int size) @safe nothrow { 306 this.size_ = size; 307 308 return this; 309 } 310 311 int size() @safe nothrow { 312 return this.size_; 313 } 314 315 @setter(3.0) // Set the property to arguments passed in setter 316 Tire pressure(float pressure) @safe nothrow { 317 this.pressure_ = pressure; 318 319 return this; 320 } 321 322 float pressure() @safe nothrow { 323 return this.pressure_; 324 } 325 326 @setter("tire.vendor".lref) // Set the property to arguments passed in setter 327 Tire vendor(string vendor) @safe nothrow { 328 this.vendor_ = vendor; 329 330 return this; 331 } 332 333 string vendor() @safe nothrow { 334 return this.vendor_; 335 } 336 } 337 338 public override string toString() { 339 import std.algorithm; 340 import std.range; 341 import std.conv; 342 import std.utf; 343 344 return only("Tire(", this.size.to!string, " inch, ", this.pressure.to!string, " atm, ", this.vendor, ")") 345 .joiner 346 .byChar 347 .array; 348 } 349 } 350 351 /** 352 A class representing a car. 353 **/ 354 @component // Mark component to be registered in container 355 @qualifier("custom.identity") 356 class Car { 357 358 private { 359 Color color_; // Car color 360 Size size_; // Car size 361 Engine engine_; // Car engine 362 363 Tire frontLeft_; 364 Tire frontRight_; 365 Tire backLeft_; 366 Tire backRight_; 367 } 368 369 public { 370 371 /** 372 Constructor of car. 373 374 Constructs a car with a set of sizes. A car cannot of course have 375 undefined sizes, so we should provide it during construction. 376 377 Params: 378 size = size of a car. 379 **/ 380 @constructor(lref!Size, lref!Engine) // Construct this component using arguments passed to constructor annotation 381 this(Size size, Engine engine) { 382 this.size_ = size; 383 this.engine = engine; 384 } 385 386 @property { 387 388 /** 389 Set color of car 390 391 Set color of car. A car can live with undefined color (physics allow it). 392 393 Params: 394 color = color of a car. 395 396 Returns: 397 Car 398 **/ 399 @setter("color.green".lref) // Set the property to arguments passed in setter 400 Car color(Color color) @safe nothrow { 401 this.color_ = color; 402 403 return this; 404 } 405 406 Color color() @safe nothrow { 407 return this.color_; 408 } 409 410 Size size() @safe nothrow { 411 return this.size_; 412 } 413 414 /** 415 Set engine used in car. 416 417 Params: 418 engine = engine used in car to propel it. 419 420 Returns: 421 Car 422 **/ 423 @setter(lref!Engine) // Set the property to arguments passed in setter 424 Car engine(Engine engine) @safe nothrow { 425 this.engine_ = engine; 426 427 return this; 428 } 429 430 Engine engine() @safe nothrow { 431 return this.engine_; 432 } 433 434 @autowired // Automatically wire property using components from container identified by their types 435 Car frontLeft(Tire frontLeft) @safe nothrow { 436 this.frontLeft_ = frontLeft; 437 438 return this; 439 } 440 441 Tire frontLeft() @safe nothrow { 442 return this.frontLeft_; 443 } 444 445 @callback( 446 function (Locator!() locator, Car configured) { 447 configured.frontRight = locator.locate!Tire; 448 } 449 ) // Use a callback to configure the property, or entire object. Can be attached anywhere on component 450 Car frontRight(Tire frontRight) @safe nothrow { 451 this.frontRight_ = frontRight; 452 453 return this; 454 } 455 456 Tire frontRight() @safe nothrow { 457 return this.frontRight_; 458 } 459 460 @autowired // Automatically wire property using components from container identified by their types 461 Car backLeft(Tire backLeft) @safe nothrow { 462 this.backLeft_ = backLeft; 463 464 return this; 465 } 466 467 Tire backLeft() @safe nothrow { 468 return this.backLeft_; 469 } 470 471 @autowired // Automatically wire property using components from container identified by their types 472 Car backRight(Tire backRight) @safe nothrow { 473 this.backRight_ = backRight; 474 475 return this; 476 } 477 478 Tire backRight() @safe nothrow { 479 return this.backRight_; 480 } 481 482 } 483 484 void start() { 485 engine.turnOn(); 486 } 487 488 void run() { 489 engine.run(); 490 } 491 492 void stop() { 493 engine.turnOff(); 494 } 495 } 496 } 497 498 class ManufacturedCar : Car { 499 /** 500 Constructor of car. 501 502 Constructs a car with a set of sizes. A car cannot of course have 503 undefined sizes, so we should provide it during construction. 504 505 Params: 506 size = size of a car. 507 **/ 508 @constructor(lref!Size, lref!Engine) // Construct this component using arguments passed to constructor annotation 509 this(Size size, Engine engine) { 510 super(size, engine); 511 } 512 } 513 514 /** 515 A manufacturer of cars. 516 **/ 517 @component 518 class CarManufacturer { 519 520 public { 521 @component 522 ManufacturedCar manufacture(Size size) { 523 return new ManufacturedCar(size, new DieselEngine()); // Manufacture a car. 524 } 525 } 526 } 527 528 void drive(Car car, string name) { 529 writeln("Uuh, what a nice car, ", name," with following specs:"); 530 writeln("Size:\t", car.size()); 531 writeln("Color:\t", car.color()); 532 writeln("Engine:\t", car.engine()); 533 writeln("Tire front left:\t", car.frontLeft(), "\t located at memory ", cast(void*) car.frontLeft()); 534 writeln("Tire front right:\t", car.frontRight(), "\t located at memory ", cast(void*) car.frontRight()); 535 writeln("Tire back left: \t", car.backLeft(), "\t located at memory ", cast(void*) car.backLeft()); 536 writeln("Tire back right:\t", car.backRight(), "\t located at memory ", cast(void*) car.backRight()); 537 } 538 539 void main() { 540 auto container = aggregate( // Create a joint container hosting other two containers 541 singleton(), name!(Storage!(ObjectFactory, string)), // Create singleton container, and store it in joint container by "singleton" identity 542 singleton(), "singleton", 543 prototype(), "prototype", // Create prototype container, and store it in joint container by "prototype" identity 544 values(), "parameters" // Create value container, and store it in joint container by "prototype" identity 545 ); 546 scope(exit) container.terminate(); 547 548 container.scan!(annotation_configuration); // load all annotated components from module "annotation_configuration" 549 550 with (container.configure("singleton")) { // add components that have custom identity 551 552 register(Color(0, 255, 0), "color.green"); 553 register("divine tire", "tire.vendor"); 554 } 555 556 with (container.locate!ValueContainer("parameters").configure) { // add instantiated components 557 558 register(Size(200, 150, 300), "size.smarty"); 559 } 560 561 container.instantiate(); // Boot containers. 562 563 container.locate!Car("custom.identity").drive("Electric car"); // drive the car 564 }