Pluggable Providers


There is a fine line between what should be core functionality and what should be "pluggable." Providers let flex points in an application block ensure that the core functionality is still applicable even when something outside of that core functionality changes. Throughout this book I have described and created many providers for all of the application blocks that ship with Enterprise Library. For example, Chapter 3 described the database providers (SqlDatabase, OracleDatabase, and Db2Database) that ship with the Data Access Application Block and showed how to create a new database provider that can be used to access other databases. Chapter 7 discussed the AuthenticationProvider that ships with the Security Application Block for authenticating users against a database and showed how to create a new AuthenticationProvider to authenticate users against a Microsoft Active Directory. Providers allow an application block to be adaptable so that it can fit into an environment where the needs may be different than what Enterprise Library allows out-of-the-box.

There are several characteristics that define how an application block should be designed to use providers.

  • An application block should provide at least one specific implementation of a provider type that demonstrates how the application block can be used.

  • The application block should be developed so that its core functionality is decoupled from any specific provider implementation.

  • It is almost always a good idea to ensure that providers are configurable; that is, that they can initialize themselves with configuration data that specifies how they should execute.

Provider Type Implementation

A provider type defines the kind of provider that is expected to be used by an application block. For example, the Data Access Application Block uses database providers, while the Security Application Block uses authentication, authorization, security cache, role management, and profile management providers. Every application block must determine which type of providers it expects to use and should provide at least one implementation of such a provider.

The Data Mapping Application Block expects to use DataMappingProviders. I have defined the IDataMappingProvider interface to represent the expected functionality that any DataMappingProvider should possess. When the DatabaseWrapper maps database command, table, and field information to the properties and fields of an "entity" (e.g., a typed DataSet), it uses the methods exposed by an implementation of the IDataMappingProvider interface.

The DatabaseWrapper can use any class that implements the IDataMappingProvider interface to map database constructs to the corresponding construct for that provider. The DataSetMappingProvider is provided with the application block as an implementation of the DataMappingProvider type and is intended to be used to map typed DataSets to database commands and command parameters. Figure 9.2 shows how the DataSetMappingProvider implements the IDataMappingProvider interface to serve as a provider type implementation for the DataMappingProvider type.

Figure 9.2. Primary Classes Used for DataSetMappingProvider Implementation


Decoupling from the Core Functionality

By developing the block so that it uses the interface exposed by the provider type and not any one specific provider, the application block is decoupled from any single provider's implementation. This helps to achieve the following goals.

  • Variability. Developers and architects can choose from various different implementations for solving a particular problem. For example, I once had to design a Web service that would convert Microsoft Word 2003 documents from Word Markup Language (WordML) to PDF format. There were many software vendors that offered application interfaces to allow for this type of functionality.

    To choose the vendor that met the requirements the best, I created a Document Conversion Application Block with an interface that met the requirements needed for this service. Then I developed a provider for each vendor's offering. This let me switch between vendors with ease and without the need to modify any code in the Web service or the application block. Moreover, it allowed me to create unit and load tests to compare how each solution matched the requirements for the service. If I need to evaluate another vendor, I simply created another provider that encapsulated the functionality for that vendor. There was a great deal of variety in determining which vendor best met the requirements for the service while not tying the service to any one vendor's implementation.

  • Extensibility. An application block can be used in environments where a specific capability might have a mandatory implementation with which the block does not ship. There is an example of this in Chapter 6 in the discussion of the extensibility for distribution strategies and distributors in the Logging and Instrumentation Application Block. Some enterprises will not use MSMQ in their corporate environment, but this doesn't eliminate the ability to use the asynchronous distribution strategy in the Logging and Instrumentation Application Block. A new provider can be created and used with the block to distribute logs in a way that is more in line with the corporate strategy (e.g., a relational database or MQSeries).

  • Encapsulation. Each provider encapsulates the way it implements the functionality that is expected from the application block. For example, the SqlDatabase provider encapsulates the logic needed to perform database operations against a Microsoft SQL Server database; the OracleDatabase provider does the same for Oracle databases. These implementations are in the providers and not in the application block that the provider supports.

    Additionally, this allows functionality that is not a part of the application block's core functionality to be replaced or upgraded without affecting other areas of the application block. For example, if a new database provider were created that worked more efficiently to perform database operations against a Microsoft SQL Server 2005 database, that provider could be used by the Data Access Application Block without the need for code changes in the block itself.

  • Minimized coupling between application blocks. Application blocks that are dependent on other application blocks may encapsulate this dependency into a provider. This means that the application block is less vulnerable to revisions in the application block on which it depends. For example, the Exception Handling Application Block includes the LoggingExceptionHandler. This handler is dependent on the Logging Application Block and is included as a provider. A new version of the Logging Application Block would require only a new logging handler provider; the rest of the Exception Handling Application Block can remain unchanged.

Not only does Figure 9.2 show how the IDataMappingProvider interface exposed specific functionality that was implemented by the DataSet-MappingProvider class, but it also showed the use of several abstract base classes that complement the IDataMappingProvider. The Data Mapping Application Block takes advantage of these classes to remain completely decoupled from any one provider's implementation. The DatabaseWrapper accesses a DataMappingCollection from the provider that it is configured to use and accesses all of the settings for mapping entities from the DataMapping objects contained in this collection.

The DataMappingCollection class and the DataMapping class are both abstract; an implementation of a DataMappingProvider must supply the specific way that it intends to map database fields to entities. The DataSetMappingProvider supplies this by way of the derived DataSet-Mapping class; however, the Data Mapping Application Block knows nothing of this derived class. It just expects to retrieve a class that is derived from the DataMappingCollection class and use it to access classes that must derive from the DataMapping class.

Listing 9.1 shows the code that illustrates how the DatabaseWrapper is decoupled from any one provider's implementation. In this method, the DatabaseWrapper is not referencing any classes that are used in the DataSetMappingProvider implementation; it only references the IDataMappingProvider interface and the abstract base classes shown in Figure 9.2.

Listing 9.1. Binding Only to the IDataMappingProvider Interface

[C#] private static DataMappingCollection GetConfigInfo {     get     {          DataMappingCollection configDataMappings = null;          try          {               IDataMappingProvider mappingProvider =                    DataMappingsFactory.GetDataMappingProvider ();               configDataMappings =                    mappingProvider.DataMappingCollection;          }          catch (Exception ex)          {               throw new DataWrapperException                     ("Cannot obtain configuration information: " +                         ex.Message, ex);          }          return configDataMappings;     } } [Visual Basic] Private Shared ReadOnly Property GetConfigInfo() _               As DataMappingCollection     Get          Dim configDataMappings As DataMappingCollection = Nothing          Try               Dim mappingProvider As IDataMappingProvider = _                    DataMappingsFactory.GetDataMappingProvider ()               configDataMappings = _                    mappingProvider.DataMappingCollection          Catch ex As Exception               Throw New DataWrapperException _                     ("Cannot obtain configuration information: " & _                         ex.Message, ex)          End Try          Return configDataMappings     End Get End Property

Configurable

As I created new providers in the previous chapters, I described the benefit that can be achieved by enabling each provider to initialize itself with the configuration information that it needs to run properly. For example, for the XMLFileDatabase database provider to run properly, it needs to obtain configuration information about which file to use to store and retrieve data. Therefore, in Chapter 3, I designed the XMLFileDatabase database provider so that it could initialize itself with information about which XML file to use. The configuration data for a provider is generally stored with the rest of the configuration data for an application block and is specified in configuration by its type information and a name.

As discussed in Chapter 1, the abstract ConfigurationProvider base class defines the contract for initializing a provider with configuration information. A provider is afforded the ability to initialize itself with configuration data by deriving from this abstract base class and implementing the Initialize method. The Initialize method accepts an object containing the current ConfigurationView and uses it to obtain the provider's runtime configuration data. When building a provider for an application block, it is important to derive that provider either directly or indirectly from the ConfigurationProvider class so that it can initialize itself with configuration data. The DataSetMappingProvider, for example, implements the IDataMappingProvider interface and derives from the ConfigurationProvider class.

Additionally, the runtime configuration data for a provider is typically contained in a class that derives from the ProviderData class. In the case of the DataSetMappingProvider, the runtime configuration data is contained in a class named DataSetMappingProviderData.DataSetMappingProviderData derives from an abstract DataMappingProviderData class that derives from ProviderData. The DataMappingProviderData class contains runtime configuration information that would be common for all DataMappingProviders. The DataSetMappingProviderData class just adds configuration information that is specific to its implementation.

Listing 9.2 has a property for accessing the CacheManager that is used to cache typed DataSets and a property for returning the DataMappingCollection. This listing also demonstrates how each of these properties is decorated with attributes that allow the XmlSerializer to be used to serialize and deserialize the class. That is how the Configuration Application Block creates the provider classes (or any classes, for that matter) from the configuration data. You can read more about the ConfigurationProvider, ConfigurationView, and the other configuration runtime classes in Chapter 1.

Listing 9.2. Representing the Configuration Data for the DataSetMappingProvider

[C#] public class DataSetMappingProviderData : DataMappingProviderData {     private string cacheManager;     public DataSetMappingProviderData() : this(string.Empty)     {     }     public DataSetMappingProviderData(string name) : base(name)     {          cacheManager = String.Empty;          dataMappingCollection = new DataSetMappingDataCollection();     }     [XmlAttribute("cacheManager")]     public string CacheManager     {          get {return cacheManager;}          set {cacheManager= value;}     }     [XmlArray("DataSetMappings")]     [XmlArrayItem(Type=typeof(DataSetMapping))]     public override DataMappingCollection DataMappings     {          get { return base.DataMappings; }     }     [XmlIgnore]     public override string TypeName     {          get          {               return               typeof(DataSetMappingProvider).AssemblyQualifiedName;          }          set {}     } } [Visual Basic] Public Class DataSetMappingProviderData : _               Inherits DataMappingProviderData Private cacheManager As String     Public Sub New()          Me.New(String.Empty)     End Sub     Public Sub New(ByVal name As String)          MyBase.New(name)          cacheManager = String.Empty          dataMappingCollection = New DataSetMappingDataCollection()     End Sub     <XmlAttribute("cacheManager")> _     Public Property CacheManager() As String          Get               Return cacheManager          End Get          Set               cacheManager = Value          End Set     End Property     <XmlArray("DataSetMappings"), _     XmlArrayItem(Type:=GetType(DataSetMapping))> _     Public Overrides ReadOnly Property DataMappings() _               As DataMappingCollection          Get               Return MyBase.DataMappings          End Get     End Property     <XmlIgnore> _     Public Overrides Property TypeName() As String          Get               Return _               GetType(DataSetMappingProvider).AssemblyQualifiedName          End Get          Set          End Set     End Property End Class




Fenster Effective Use of Microsoft Enterprise Library(c) Building Blocks for Creating Enterprise Applications and Services 2006
Effective Use of Microsoft Enterprise Library: Building Blocks for Creating Enterprise Applications and Services
ISBN: 0321334213
EAN: 2147483647
Year: 2004
Pages: 103
Authors: Len Fenster

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net