acm-header
Sign In

Communications of the ACM

Developing and integrating enterprise components and services

Overcoming Independent Extensibility Challenges




  • Build time. Program source is compiled into components or deployment units during build time. Components are not a priori committed to either specific programming language constructs (such as classes), or granularity. Moreover, a component might also contain other resources and constant data beyond code (such as bitmap graphics). To enable reuse beyond source sharing, the build process must be able to refer to metadata attached to previously built components (details later in this article). To enable independent extensibility, the build process must be able to create individual components—not just closed applications.
  • Load time. Components are loaded into an execution environment at load time, yielding an executable image. Metadata in a loaded unit indicates static dependencies on other units. The loader has to check whether these are already loaded and, if not, locate and load these as well. (A loader's strategy may vary depending on how lazily or how eagerly the resolution of such dependencies is performed.)
  • Runtime. Executables are run at runtime. Like loading, execution might cause transitive building, loading, and subsequent execution of other components demanded dynamically by executing code.

This is of course a simplification of real life where a program might go through design time, link time, JIT time, and so forth, but that would only clutter the presentation at this point. The important issue is that in each of these phases, names have to be bound to values, and that these bindings might change when transitioning between binding times.

The C# fragment shown in Figure 1 defines a class X, and then creates instances of that class X, and of two other classes Y and Z. The instance-creating statements rest on a spectrum of binding times. The use of type X can be resolved at build time; the reference to the externally defined type Y will be resolved at load time, while the type named "Z" is resolved at runtime.


Reuse beyond source sharing and independent extensibility has several consequences for how to name components, and how names on the level of program source are mapped onto names of components.



  • either guarantee that R2 is backward compatible with R0 and R1 and have the foresight to load R2 even if encountering a request to load R0 or R1 first; or,
  • allow R0, R1 and R2 to be used side-by-side (details in section "Side-by-Side"); or,
  • fail.

Technically, there is a fourth alternative: we can simply bind against whatever is found first and hope that things will work out. What typically happens is that at deployment time of components S and T, the dependent component Ri overwrites the currently installed version of R. So, the order of installation of the components determines which version of R will be present. The resulting DLL hell is, unfortunately, the state of the art for the vast majority of systems in use today.



  • One extreme solution is to leave all source-level names unresolved until runtime; this is roughly what Smalltalk does.
  • The Java compiler preserves source-level names at build time, and performs final name resolution at load time. In Java, a component (class, or jar file) does not include dependency information about which (version of which) external components it is compiled against.
  • The C# compiler uses a two-stage approach where source-level names are mapped to the strong name of a component, and a relative name within that unit. At load time, the strong name is resolved (details later), and then the relative name is resolved within the resulting component. Components do contain information about other components they are built against.
  • In the .NET assembler language, MSIL, source-level names are pairs of strong names and relative names to start with.
  • In COM, source-level names are mapped to GUIDs (the most rudimentary form of a strong name), which at runtime are bound to values.