Поделиться через


Module

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

A module in the Composite Application Library is a logical unit in your application. Modules assist in implementing a Modularity design. These modules are defined in such a way that they can be discovered and loaded by the application at run time. Because modules are self-contained, they promote separation of concerns in your application. Modules can communicate with other modules and access services through various means. They reduce the friction of maintaining, adding, and removing system functionality. Modules also aid in testing and deployment.

A common usage of a module is to represent different portions of the system. The following are some examples of modules:

  • A module that contains a specific application feature such as news
  • A module that contains a specific subsystem such as purchasing, invoicing, and general ledger
  • A module that contains infrastructure services such as logging and authorization services or Web services
  • A module that contains services that invoke line-of-business (LOB) systems, such as Siebel CRM and SAP, in addition to other internal systems

For example, there are four modules in the Stock Trader Reference Implementation (Stock Trader RI):

  • NewsModule. This module is responsible for aggregating and displaying the news related to the user’s currently selected investment in their portfolio.
  • PositionModule. This module is responsible for displaying the positions and for handling buy/sell orders.
  • MarketModule. This module handles aggregating and displaying trend information for the portfolio.
  • WatchModule. This module handles displaying the Watch List, which is a list of funds the user monitors. This module also handles adding and removing items from this list.

Team Development Using Modules

Modules have explicit boundaries, typically by subsystem or feature. Having these boundaries makes it easier for separate teams to develop modules. On large applications, teams may be organized by cross-cutting capabilities in addition to being organized by a specific subsystem or feature. For example, there may be a team assigned to shared components of the application, such as the shell or the common infrastructure module.

The following are the different teams developing the Stock Trader Reference Implementation, as illustrated in Figure 1.

  • Infrastructure team. This team develops the core cross-cutting services used in the application and cross-module types and interfaces.
  • Postions team. This team maintains the Positions and Buy/Sell functionality.
  • News team. This team handles aggregating and displaying news
  • UI team. This team manages the shell and contains a set of graphic designers who set the overall look and feel of the application. They work across each of the development teams.
  • Operations team. This team is responsible for managing deploying modules to staging and production. They also manage the master module configuration files.

Figure 1 illustrates an example of a composite WPF application's modules and associated teams.

Ff648781.0fce11c1-fae3-4c5f-abf6-058b13070224(en-us,PandP.10).png

Figure 1
Modules and associated teams of a composite WPF application

Module Design

Like the application, modules can be designed following layered principals. Modules may consist of a presentation layer, a business or domain layer, and a resource access layer, as illustrated in Figure 2.

Ff648781.aba045b5-cd41-4b5b-8f99-c68f67c83b75(en-us,PandP.10).png

Figure 2
Module design

These responsibilities may be split across modules. One module may contain the resource access layer that other modules rely on. Or, one module may contain all the views that could then be more easily consumed by user interface designers.

IModule

A module is a class that implements the IModule interface. This interface contains a single Initialize method that is called during the module's initialization process.

public interface IModule
{
    void Initialize();
}

Module Dependencies

Modules may need access to dependencies. Because they are constructed by the container, these dependencies are injected through the module's constructor. For example, in the Stock Trader RI, the container and the regionManager are injected into the WatchModule, as shown in the following code.

public WatchModule(IUnityContainer container, IRegionManager regionManager)
{
    _container = container;
    _regionManager = regionManager;
} 

View and Service Registration

When a module is initialized, it can register views and services. Registration allows their dependencies to be provided through the container and allows them to be accessed from other modules.

To do this, the module will need to have the container injected into the module constructor (as shown in the preceding code). The registration is done in the RegisterViewsAndServices method, which is not required if there is no registration. The following code shows how the MarketModule in the Stock Trader RI registers a MarketHistoryService, MarketFeedService, and TrendLineView.

protected void RegisterViewsAndServices()
{
    _container.RegisterType<IMarketHistoryService, MarketHistoryService>();
    _container.RegisterType<IMarketFeedService, MarketFeedService>(new ContainerControlledLifetimeManager());
    _container.RegisterType<ITrendLineView, TrendLineView>();
    _container.RegisterType<ITrendLinePresenter, TrendLinePresenter>();
}

Depending on which container you use, registration can also be done outside of the code through configuration.

Note

The advantage of registering in code is that the registration only happens if the module loads.

Displaying Views

During a module's initialization, you may also want to inject a view into a Shell region. To do this, the region manager needs to be injected into the constructor. In the Initialize method, you can then get a hold of a region through the Regions[regionName] method, and then add a view. In the Initialize method of the Position module, as shown in the following code, the PositionSummary view is added to the MainRegion.

public void Initialize()
{
    RegisterViewsAndServices();

    IPositionSummaryPresentationModel presentationModel = _container.Resolve<IPositionSummaryPresentationModel>();
    IRegion mainRegion = _regionManager.Regions["MainRegion"];
    mainRegion.Add(presentationModel.View);
    …
}

Note

Before resolving a view, make sure that it is registered with the container.

Considerations for Modules

When creating your modules, consider the following:

  • Consider putting each module in a separate namespace.
  • Consider not having one module directly accessing concrete types from another module. Use interfaces instead. This reduces coupling, thereby increasing testability and prevents circular references between modules.

Module Loading

Module loading in the Composite Application Library is a two-step process:

  1. The module enumerator discovers modules and creates a collection of metadata about those modules.
  2. The module loader instantiates the module and calls its Initialize method.

Figure 3 illustrates module loading in the Composite Application Library.

Ff648781.c30a781a-0515-4095-ad28-bdbe9c365d0e(en-us,PandP.10).png

Figure 3
Module Loading

Types of Module Loading

The Composite Application Library provides several ways to load modules out of the box. You can also provide your own custom implementation.

Static Module Loading

In static module loading, the shell has a direct reference to the modules, but the modules themselves do not directly reference the shell. They can be in the same assembly or a different assembly. The benefit of this style of loading over dynamic module loading is that modules are easier to use and debug because they are directly referenced. The disadvantage of static module loading is that you have to add new references to the shell and a line of code to the bootstrapper for each module.

The Stock Trader RI uses this style of loading. Figure 4 illustrates the Stock Trader RI references and highlights the shell references to the modules.

Ff648781.5b6f89ff-35f8-434a-b749-b62ef90c5c24(en-us,PandP.10).png

Figure 4
Stock Trader RI references

The following code shows where the modules are statically defined in the Stock Trader RI bootstrapper.

protected override IModuleEnumerator GetModuleEnumerator()
{
    return new StaticModuleEnumerator()
        .AddModule(typeof (NewsModule))
        .AddModule(typeof (MarketModule))
        .AddModule(typeof (WatchModule), "MarketModule")
        .AddModule(typeof (PositionModule), "MarketModule", "NewsModule");
}

Dynamic Module Loading

In dynamic module loading, modules are discovered at run time, which the shell does not directly reference. The advantage of dynamic module loading is that modules can be added to the application without having to add any new references or modify the executable. The other advantage is that it gives you flexibility as to how modules are discovered; for example, the Composite Application Library ships with support for loading modules by scanning a directory, specifying in configuration, or on demand.

Directory Driven Module Loading

In this style of loading, modules are located by the DirectoryLookupModuleEnumerator, which scans the assemblies in a directory and locates all the types that implement IModule. It will also look for the ModuleDependency attribute to determine the dependent modules that need to be loaded before loading the current module. For an example of this style of loading, see the Dynamic Modularity (DirectoryLookup) QuickStart. The following code shows where Module A and Module B are defined and that Module B depends on Module A.

[Module(ModuleName = "ModuleA")]
public class ModuleA : IModule
{
     …
}

[Module(ModuleName = "ModuleB")]
[ModuleDependency("ModuleA")]
public class ModuleB : IModule
{
     …
}

Configuration Driven Module Loading

In this style of loading, modules are located by the ConfigurationModuleEnumerator, which scans the App.config file for modules and module dependencies. The following example shows where Module A and Module B are defined and that Module B depends on Module A. For an example of this style of loading, see the Dynamic Modularity (Configuration) QuickStart.

<modules>
  <module assemblyFile="Modules/ModuleA.dll" moduleType="ModuleA.ModuleA" moduleName="ModuleA">
          …
  </module>
  <module assemblyFile="Modules/ModuleB.dll" moduleType="ModuleB.ModuleB" moduleName="ModuleB">
    <dependencies>
      <dependency moduleName="ModuleA"/>
    </dependencies>
  </module>
  …
</modules>

On-Demand Module Loading

In this style of loading, modules are loaded on demand in the application. The loader will use any available module enumerator to load the module (including the DirectoryLookupModuleEnumerator and the ConfigurationModuleEnumerator). For examples of this style of loading, see the Dynamic Modularity (Configuration) QuickStart and the Dynamic Modularity (DirectoryLookup) QuickStart. The following code shows an example of Module C loading on demand in the Dynamic Modularity (Configuration) QuickStart. For more information, see the Dynamic Modularity Quickstarts.

private void OnLoadModuleCClick(object sender, RoutedEventArgs e)
{
    moduleLoader.Initialize(moduleEnumerator.GetModule("ModuleC"));
}

Module Enumerator

The module enumerator identifies the modules to be loaded. Each module enumerator must implement the IModuleEnumerator interface. The Composite Application Library includes three enumerators: StaticModuleEnumerator, DirectoryLookupEnumerator, and ConfigurationModuleEnumerator. You could write your own module enumerator that obtains module information over a Web service or from a database by simply implementing the IModuleEnumerator interface and retrieving the module information from wherever is appropriate for your situation, as shown in the following code.

public interface IModuleEnumerator
{
    ModuleInfo[] GetModules();
    ModuleInfo[] GetStartupLoadedModules();
    ModuleInfo GetModule(string moduleName);
}

The following describes each method in the preceding code:

  • GetModules. This method returns a list of all known modules, including those that load during startup and those that load on-demand.
  • GetStartupLoadedModules. This method returns only those modules that will load at application start (determined either through the ModuleAttribute or in configuration).
  • GetModule. This method returns a specific module.

ModuleInfo

The module enumerator returns a list of module metadata that the module loader will use to load and initialize the module.

Note

Modules that are statically loaded are loaded by the common language runtime (CLR) instead of by the ModuleLoader service. However, they are initialized.

The following code shows the ModuleInfo constructor.

public ModuleInfo(string assemblyFile, string moduleType, string moduleName, params string[] dependsOn)
    : this(assemblyFile, moduleType, moduleName, true, dependsOn)
{
}

In addition to information about the module, the preceding code shows that you can provide a collection of module names that the modules depend on. The module loader will build its load order based on these dependencies.

Module Loader

The module loader, which implements the IModuleLoader interface, loads the assemblies that contain the modules. The IModuleLoader.Initialize method accepts an array of ModuleInfo instances, which determines the list of modules to be loaded. The following code shows the IModuleLoader interface.

public interface IModuleLoader
{
    void Initialize(ModuleInfo[] moduleInfos);
}

The module loader will use the ModuleDependencySolver to parse the list of modules and create a load order based on the module dependencies that have been defined. During loading, the module loader instantiates each module by resolving it off of the container. This is to allow the module dependencies to get injected.

Note

The ModuleLoader service does not depend on a specific container; instead, it uses an IContainerFacade instance that can wrap any container. By default, the Composite Application Library uses a UnityContainerAdapter, but this can be replaced.

After the module is resolved, the IModule.Initialize method is invoked—this allows the module's code to execute.

More Information

For more information about modules, see the following resources:

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.