Dependency Injection
From APIDesign
(New page: A special form of component injection following the rule where the framework calls, and beans just react to such calls. See wikipedia::component injection.) |
(→Composition) |
||
(30 intermediate revisions not shown.) | |||
Line 1: | Line 1: | ||
- | A special form of component [[injection]] following the | + | A special form of component [[injection]] following the classical ''Hollywood'' principle: the framework calls, and beans just react to such calls. See [[wikipedia::dependency injection]]. |
+ | |||
+ | == Translation Basics == | ||
+ | |||
+ | During our [[JavaOne2010]] talk about [[Modularity patterns]] we compared [[dependency injection]] and [[injectable singleton]]s. As the comparison was well accepted and as I think it revealed some things that were just implicitly present in our minds, here is an explicit enumeration. Here is the beginner's guide to [[modularity]] for [[dependency injection]] (namely [[Spring]]) fans. | ||
+ | |||
+ | === Imports vs. [[Dependencies]] === | ||
+ | |||
+ | Foremost, for some obvious, difference when bringing the [[DI]] knowledge to the [[modularity]] world is where to find the set of [[dependencies]] of a single component. The answer is simple, but let's seek it the longer way. | ||
+ | |||
+ | The [[DI]] fans often look at the code from the perspective of individual source files. Often one can find advice to create an interface (in this case really the [[Java]] interface, not just an abstract definition) next to the class that is using it (something like ''BankAccount'') and also define its implementation (like ''BankAccountImpl'') next to that interface. So all these classes, the ''BankAccount'', its implementation ''BankAccountImpl'', and the class ''Transfer'' which needs two accounts to move money back and forth are in the same package next too each other. | ||
+ | |||
+ | This is something a true believer in [[modularity]] would never do. Why separate ''BankAccount'' from ''BankAccountImpl'' when they are packaged together? Why make a publicly visible [[API]] like ''BankAccount'' [[Java]] interface, as that limits future [[evolution]] as argued in [[Code Against Interfaces, Not Implementations]] chapter? The only reason to make ''BankAccount'' interface (either [[Java]] interface or abstract class) is to allow someone, unknown for the current moment to implement it and register it sometimes later! | ||
+ | |||
+ | So where is the difference? [[Dependency injection]] fans treat a single source file as a unit. The [[modularity]] guys treat the whole set of sources compiled together as a unit. As a result, when worshiping [[DI]] look into '''import''' section of each class to find its [[dependencies]]. When living in world of [[modularity]] , look into the ''javac's'' compile time [[dependencies]] (for example into [[Maven]]'s ''pom.xml''). | ||
+ | |||
+ | === Versioning === | ||
+ | |||
+ | [[Modularity]] is first and foremost created for a distributed world. A world, where software is compiled, linked, assembled and evolved by [[DistributedDevelopment|distributed groups of people]], on their own individual schedules, independently. This reflects in making the compilation unit the unit of [[dependency]]. When compiling one can reference other such units made by others long time ago. Such units then need an identification, a version number. Hence the versioning is inherently present in the way [[modularity]] users build their applications. It is commonly expected that replacing one dependent unit with newer version will cause no harm. To guarantee that [[evolution]] plays central role when dealing with this kind of systems. | ||
+ | |||
+ | Of course, neither applications using [[DI]] live in vacuum. They may require external libraries in their [[environment]], they may even be split into multiple parts. But unless they explicitly decide to enter the world of [[modularity]] (by creating a framework to be used by others distributed elsewhere), they are usually compiled as a whole at once. As a result the units of [[DI]] (aka individual source files) don't need any versioning with respect to each other. They either compile or not. You can change them as you wish. The new version either compiles, or not. Everything compiles together. No emphasis on [[evolution]] is needed. | ||
+ | |||
+ | Classical use of [[DI]] is not targeted towards [[distributed development]] (a mantra of [[modularity]]) at all. This is one reason why usage of [[Java]] ''interface''s is so favorable in [[DI]] world, and often so impractical when designing an [[API]] (especially [[ClientAPI]]). | ||
+ | |||
+ | === Application Factory & co. === | ||
+ | |||
+ | The important limitation that [[DI]] camp needs to realize is that in [[modularity]] one solves [[dependencies]] only on the level of ''ApplicationFactory''. Obviously, [[dependencies]] are consumed by the compiler, and compiler compiles all the module sources. The [[Dependency Injection]] frameworks are more richer. They offer various levels of factories including application, session, request, etc. It is not goal of [[modularity]] (e.g. [[OSGi]] runtimes or [[NetBeans Runtime Container]]) to provides anything else than global application factories. See the [[Co-existence]] page for enumeration of various cases where [[DI]] excels. | ||
+ | |||
+ | === Initialization === | ||
+ | |||
+ | A pure [[DI]] class cannot work on its own. It is importing the [[API]]s, but it requires the implementations to execute. This may be the case in [[modularity]] too, but properly designed [[Injectable Singleton]] can work on its own without any need for a complex initialization. | ||
+ | |||
+ | This means that initialization is inherently present in modular systems, but it is always (too) verbose in [[DI]] systems. It requires creation of various [[XML]] files (in [[Spring]]) or module setups (in [[Guice]]). That is indeed fine, when one creates the whole system, but as soon as various system pieces are going to be deployed by independent teams, the initialization of everything may get too complex. Associating the [[API]] with its default implementation makes the initialization voluntary and simplifies bits of such complexity. | ||
+ | |||
+ | === Injection === | ||
+ | |||
+ | [[Injection]] may but may not be necessary in either approach. In [[DI]] approach not all ''import'' statements in a class mean request for [[injection]]. One may import ''java.util.ArrayList'' and directly instantiate it. One may also import ''Math'' class and call its static methods. There is nothing wrong on this, some classes are just used as they are, they don't represent any abstractions. | ||
+ | |||
+ | In similar style a [[dependency]] on another module may not mean any form of [[injection]] at all. One can import just a [[simple library]] - e.g. library that provides ''ArrayList'' or ''Math'' and just call its classes. | ||
+ | |||
+ | On the other hand, one can also depend on [[modular library]]. Such library just defines some [[API]], but the implementation is provided/injected from elsewhere. This is a common approach used by [[Injectable Singleton]]s (like ''DocumentBuilderFactory'', etc.). | ||
+ | |||
+ | === Composition === | ||
+ | |||
+ | When using [[DI]] there usually is single, central authority that defines the [[injection]] ''context'' (aka [[Spring]]'s '''ApplicationContext''' or [[Guice]]'s module). True, it is often possible to merge multiple contexts into one, but this is not inherently present in the framework. One can do it ad-hoc, if needed, using own rules. | ||
+ | |||
+ | On the other hand, [[module system]]s often spend great amount of effort to merge individual registrations (via [[ServiceLoader]], or other extension points) together. There is a defined registration place, a search policy, a way to order individual registrations. There is a recipe how to build the master context without knowing any details about the individual pieces that contribute to it. Rather, the individual pieces can prescribe by using additional meta information how they shall be registered with respect to other pieces (if they are present). | ||
+ | |||
+ | Again, this is just another example, that [[modularity]]'s primary concern is [[distributed development]], while [[DI]] cares more about better organization of one's own code. | ||
+ | |||
+ | |||
+ | <comments/> |
Current revision
A special form of component injection following the classical Hollywood principle: the framework calls, and beans just react to such calls. See wikipedia::dependency injection.
Contents |
Translation Basics
During our JavaOne2010 talk about Modularity patterns we compared dependency injection and injectable singletons. As the comparison was well accepted and as I think it revealed some things that were just implicitly present in our minds, here is an explicit enumeration. Here is the beginner's guide to modularity for dependency injection (namely Spring) fans.
Imports vs. Dependencies
Foremost, for some obvious, difference when bringing the DI knowledge to the modularity world is where to find the set of dependencies of a single component. The answer is simple, but let's seek it the longer way.
The DI fans often look at the code from the perspective of individual source files. Often one can find advice to create an interface (in this case really the Java interface, not just an abstract definition) next to the class that is using it (something like BankAccount) and also define its implementation (like BankAccountImpl) next to that interface. So all these classes, the BankAccount, its implementation BankAccountImpl, and the class Transfer which needs two accounts to move money back and forth are in the same package next too each other.
This is something a true believer in modularity would never do. Why separate BankAccount from BankAccountImpl when they are packaged together? Why make a publicly visible API like BankAccount Java interface, as that limits future evolution as argued in Code Against Interfaces, Not Implementations chapter? The only reason to make BankAccount interface (either Java interface or abstract class) is to allow someone, unknown for the current moment to implement it and register it sometimes later!
So where is the difference? Dependency injection fans treat a single source file as a unit. The modularity guys treat the whole set of sources compiled together as a unit. As a result, when worshiping DI look into import section of each class to find its dependencies. When living in world of modularity , look into the javac's compile time dependencies (for example into Maven's pom.xml).
Versioning
Modularity is first and foremost created for a distributed world. A world, where software is compiled, linked, assembled and evolved by distributed groups of people, on their own individual schedules, independently. This reflects in making the compilation unit the unit of dependency. When compiling one can reference other such units made by others long time ago. Such units then need an identification, a version number. Hence the versioning is inherently present in the way modularity users build their applications. It is commonly expected that replacing one dependent unit with newer version will cause no harm. To guarantee that evolution plays central role when dealing with this kind of systems.
Of course, neither applications using DI live in vacuum. They may require external libraries in their environment, they may even be split into multiple parts. But unless they explicitly decide to enter the world of modularity (by creating a framework to be used by others distributed elsewhere), they are usually compiled as a whole at once. As a result the units of DI (aka individual source files) don't need any versioning with respect to each other. They either compile or not. You can change them as you wish. The new version either compiles, or not. Everything compiles together. No emphasis on evolution is needed.
Classical use of DI is not targeted towards distributed development (a mantra of modularity) at all. This is one reason why usage of Java interfaces is so favorable in DI world, and often so impractical when designing an API (especially ClientAPI).
Application Factory & co.
The important limitation that DI camp needs to realize is that in modularity one solves dependencies only on the level of ApplicationFactory. Obviously, dependencies are consumed by the compiler, and compiler compiles all the module sources. The Dependency Injection frameworks are more richer. They offer various levels of factories including application, session, request, etc. It is not goal of modularity (e.g. OSGi runtimes or NetBeans Runtime Container) to provides anything else than global application factories. See the Co-existence page for enumeration of various cases where DI excels.
Initialization
A pure DI class cannot work on its own. It is importing the APIs, but it requires the implementations to execute. This may be the case in modularity too, but properly designed Injectable Singleton can work on its own without any need for a complex initialization.
This means that initialization is inherently present in modular systems, but it is always (too) verbose in DI systems. It requires creation of various XML files (in Spring) or module setups (in Guice). That is indeed fine, when one creates the whole system, but as soon as various system pieces are going to be deployed by independent teams, the initialization of everything may get too complex. Associating the API with its default implementation makes the initialization voluntary and simplifies bits of such complexity.
Injection
Injection may but may not be necessary in either approach. In DI approach not all import statements in a class mean request for injection. One may import java.util.ArrayList and directly instantiate it. One may also import Math class and call its static methods. There is nothing wrong on this, some classes are just used as they are, they don't represent any abstractions.
In similar style a dependency on another module may not mean any form of injection at all. One can import just a simple library - e.g. library that provides ArrayList or Math and just call its classes.
On the other hand, one can also depend on modular library. Such library just defines some API, but the implementation is provided/injected from elsewhere. This is a common approach used by Injectable Singletons (like DocumentBuilderFactory, etc.).
Composition
When using DI there usually is single, central authority that defines the injection context (aka Spring's ApplicationContext or Guice's module). True, it is often possible to merge multiple contexts into one, but this is not inherently present in the framework. One can do it ad-hoc, if needed, using own rules.
On the other hand, module systems often spend great amount of effort to merge individual registrations (via ServiceLoader, or other extension points) together. There is a defined registration place, a search policy, a way to order individual registrations. There is a recipe how to build the master context without knowing any details about the individual pieces that contribute to it. Rather, the individual pieces can prescribe by using additional meta information how they shall be registered with respect to other pieces (if they are present).
Again, this is just another example, that modularity's primary concern is distributed development, while DI cares more about better organization of one's own code.
<comments/>