Tuesday, August 11, 2009

Inversion of Control Containers vs the Dependency Injection patter

IoC containers are not about a design pattern

While the essence of
Inversion-of-Control container appears to be easily comprehensible and well understood, it is subtly confusable and badly misread. This paradox is reflected from various seemingly evident and widely taken for granted but misleading interpretations, such as the following two well known assertions on what these containers are about:
  • Inversion-of-Control (IoC) is simply the Dependency-Injection (DI) design pattern for loose coupling between business logic implementations.
  • IoC containers (hence, tend to be referred to as DI frameworks) are programming frameworks primarily to facilitate the DI design pattern.
Misled by these interpretations, many readers disappointingly concluded that IoC containers were merely about a trivial design pattern that not only was doable by hand in a few lines of code without a container but also had already been a plain old practice on the street for years before it became a buzzword hype. These stereotypes missed the substances for the superficies (such as IoC/DI type 1,2,3, reflection, and design patterns). Consequently, the significances and implications of IoC containers have largely been neglected, as one can tell from the sigh of some DI folks: “I was expecting a paradigm shift, and all I got was a lousy constructor”.

IoC containers emerged as a mainstream solution by successfully challenged the dominance of their predecessor(s), the old EJB (2.x), that was based on the service locator design pattern. If IoC containers were merely to facilitate a plain old hand doable design pattern for loose couplings between components, then what made them superior to their predecessor(s)? Was the EJB's service locator design the cause of tight couplings between components? Was the service locator design pattern not doable by hand (if this was counted as an advantage)? Was the core engine of old EJB containers primarily to facilitate the service locator design pattern? Or, was the change from service locator to IoC/DI any paradigm shift (if we were expecting any)?

Firstly, neither the EJB's service locator design incurs tight couplings between components, nor the IoC/DI design relatively loosens such couplings if any! The objective and strength of IoC containers are orthogonal to the attempt of reducing the component-component (or component-service) couplings. Rather, they are superior to their EJB predecessor(s) by completely eliminating component-container couplings. As an example: if A and B are two components (either concrete instances or abstract interfaces) and C is an IoC container that injects B's reference into A, then, contrary to the popular misinterpretation, the injection here has nothing to do with loose coupling between A and B but serves to completely decouple A (and B as well) from the C. Here, in this example, the C itself is neither being injected with the reference of A or B nor has its reference been injected into A or B.

Secondly, neither the service locator design was hard to do by hand, nor the core engine of old EJB was to facilitate this design pattern. Similarly, although appearing as a core design pattern in IoC containers,
as Stefano Mazzocchi once pointed out, IoC (or DI) is not what IoC containers focus to do but what they merely use. In fact, it is not IoC containers facilitate the IoC/DI design but this design serves the containers. In another word, the essence of IoC containers is not "IoC" but the "container". It is just like the essence of "electric cars" is not "electricity" but the "car". It would miss the point to say electricity could be generated without cars, and worse, to conclude electric cars were merely to facilitate (i.e. to generate) electricity.

Thirdly, changing from the old EJB's service locator design to the new IoC/DI design makes component containers seamless and neutral/non-invasive. However, this design change neither unveils a new paradigm nor alters the primary objective of the containers. Instead, it is this inherited objective itself, to be discussed further in this article,
implies mindset and paradigm changes.
IoC containers != DI frameworks

As being pointed out above,
IoC containers are neither about loose couplings between components nor meant to facilitate the DI design pattern (or the Dependency Inversion Principle). However, many programming libraries known as DI frameworks (and the JSR 299) are precisely to facilitate this pattern. Although these DI frameworks are orthogonal and even opposite to IoC containers on focus, objective, and philosophy, they are constantly claimed to be substitutes or even killers of IoC containers. To avoid further confusions and meaningless apple to orange comparisons, it is now necessary to differentiate IoC containers from these DI frameworks.

DI frameworks are programming libraries with the following objectives, focus, and characters:

  • They are primarily to facilitate the DI design pattern in wiring loosely coupled business logic implementation modules (objects) into applications.
  • They focus on providing programmatic APIs (e.g. templates, annotations, and binder classes/functions) for DI configuration authoring in the same implementation languages of their business logic modules.
  • They favor DI annotations tightly coupled with business/service logic modules and consider DI configurations a statically built-in integral part of their applications.

Hence, DI frameworks emphasize the advantages of the programmatic APIs for DI configuration programming. This reflects the belief of
"the most powerful way to 'configure' something is to write the code that produces what you want. Code is precise. Code is good." In DI frameworks, these configuration "codes" are distinct from the "data". They are either step-through debuggable and testable procedural programs tightly integrated with their applications or binary metadata structures/templates and annotations to be read by DI frameworks through runtime reflection.

Quite differently, IoC containers are a category of assembly and deployment engines for component-based applications (namely, applications built from prefabricated coarse-grained high-level application or service function modules) with the following characters and objectives:
  • They use the IoC design to non-invasively control container-agnostic components (e.g. plain old C++ or Java objects/service interfaces) in assembling and deploying applications.
  • They are primarily to support the "code is model, code is data" paradigm for composite authoring, where:
    • program code explicitly model the required composite arrangements rather than the procedures that produce such arrangements (or their plumbing contexts, builders, or binders),
    • program code are not written in the same implementation languages of their applications but in data representations neutral to these languages.
  • They clearly separate these composite arrangement model/data representations from their components and applications, and have them (composite arrangement representations) manipulable as first class data objects independently.
Hence, in the vision of IoC containers, an application is a composite of binary components and external services. Such a composite is declarative programmed by modeling it in a first class data representation. This is "code is model, code is data" programming paradigm is what IoC containers (and their predecessors) are primarily about, why they are useful, and where they are fundamentally different from plumbing by hand (or facilitated by DI frameworks) in classic OO languages. Therefore, it is this programming paradigm, rather than an OO design pattern, the very essence of IoC containers.

By "code is mode", IoC containers consider "a cost effective, intuitive, and maintainable way to configure (assemble and deploy) applications is to model what you want rather than to write the code of how to produce them". Therefore, in IoC containers, user codes are self-documenting models that visualize user requirements (i.e. what users want) rather than verbalize user solutions (i.e. how to produce the configurations). In general, code orders in these IoC programs do not imply the actual step-by-step imperative execution orders of the underlying plumbing operations performed transparently by IoC containers. This enables more strong static code verifications and frees users from the concerns of IoC procedural bugs and the burdens of runtime debugging.

By "code is data", these composite modeling codes are not merely or necessary user manual programs but, most importantly, able to serve as language neutral mediating data worksheets to be handed from or exchanged between independent modeling applications or plugins in forms of internal data objects, online messages, as well as external off-line documents or records. Comparing to the binary immutable metadata/annotation/API driven tight integration of DI frameworks, this data driven approach enables a much flexible while safe integration between IoC containers and third party modeling applications or plugins that generate, edit, display, transform, merge, refactor, compare, analyze, verify, and query these modeling codes at primitive POJO/POCO component API levels as well as at various domain specific modeling levels.

Certainly, this classification does not exclude the existence and usefulness of hybrid solutions sitting between these two, for instance, configuration modules are separated from their applications but coded as metadata of the same implementation languages and even manipulate these metadata at the source code syntax level.

2 comments:

Anonymous said...

Genial post and this post helped me alot in my college assignement. Say thank you you as your information.

Anonymous said...

Hi
Very nice and intrestingss story.