ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


.NET Application Services Part 2: A Unified Factory service

by Satya Komatineni
09/16/2002

This is the second article in a series on building a flexible .NET architecture. The first article offered a simplified configuration service. That article introduced a simple idea for most of the configuration needs of an application.

In that article, I showed that given a configuration file that looks like the following:


<GeneralConfiguration>
<section1>
<key1>value1</key1>
</section1>
<section2>
<key name="x">value_for_x</key>
<key name="y" a1="10" a2 ="15">
<a3>16</a3>
<a4>17</a4>
</key>
</section2>
</GeneralConfiguration>

You can access values from this config file using the following descriptive sequence of calls:


// get a class responsible for implementing IConfig
     IConfig cfg;

// reading a simple key
     cfg.getValue("/section1/key1") -> value1

// reading a key with reserved name attribute
     cfg.getValue("/section2/key/x") -> value_for_x

// reading a key with reserved name attribute and its 
// attribute/child nodes
     cfg.getValue("/section2/key/y/a1") -> 10
     cfg.getValue("/section2/key/y/a3") -> 16

There are a number of benefits to using IConfig:

Factory Service: Journey Into the Domain of Architecture

Related Reading

.NET Framework Essentials
By Thuan L. Thai, Hoang Lam

I think that the key to building large structures starts with simple ideas. Sometimes I also fancy that the more complex a usable structure is, the more likely that it is constructed out of simple ideas. In other words: complexity cannot be built from complexity. My goal in this section is to show you how the simple-minded configuration service that we have dealt with so far will give rise to yet another simple application-level service: a factory service.

So What is a Factory Service, Anyway?

Languages have a tendency to formalize popular programming practices (often called patterns) and codify them into language constructs. For example, Java introduced the keyword interface into the language, which, prior to Java, was merely a popular underground activity in the C++ world.

C# follows suit and also has the keyword interface. (C# has formalized event handling as well, through a keyword called delegate). To understand factory services, one has to understand interfaces. Although I will explain interfaces in some detail here, please refer to your favorite book on C# to read up on them. It is time well spent, as interfaces are the backbone of the architecture.

A Look at Interfaces

An interface is like a class with a set of methods. So, if someone hands you an object and you know the interface it implements, you can call those methods in a typesafe way. That is a lot of talk, but it is quite simple, if you examine the following code:


//Let us define an interface

interface LoggingInterface
{
     public void logMessage(String message);
}

//Let's see how we can use this interface

public void myLoggingFunction( Object o)
{
     if (o is LoggingInterface)
     {
          LoggingInterface log = (LoggingInterface)o;
          log.message("test message");
     }
     else
     {
          // sorry not sure of the type of this object
          throw new Exception("Unexpected type");
     }
}

Now the question is, when you call the logMessage function, who is actually logging the message? The interface above has no code to write this message. How is that done? You need to do the following to make that happen:


public class FileLogger : LoggingInterface
{
     private FileOutputStream fs = null;
     public FileLogger(string filename)
     {
          fs = new FileOutputstream(filename);
     }

     // implement the following method to complete the 
	 // contract required by LoggingInterface
     public void logMessage(String message)
     {
          fs.println(message);
     }

     public close()
     {
          fs.close();
     }
}

Now you can do, in your code:


     FileLogger     fileLogger = new FileLogger("myfile");
     myLoggingFunction(fileLogger);

As you are getting accustomed to this style of coding, a fancy pants consultant like me could descend on your happy blanket and make it now a requirement that all logging needs to go to the event logging supported by the operating system. So, in a hurry, he creates a new class, as follows:


public class EventLogger : ILog
{
     ... other code
     public void logMessage(string message)
     {
          ..  do the needful
     }
}

Now you have to change your happy blanket from:


     FileLogger  fileLogger = new FileLogger("myfile");
     myLoggingFunction(fileLogger);
to

     EventLogger eventLogger = new EventLogger(..);
     myLoggingFunction(eventLogger);

Then you start wishing for a utility function that can give you the right logging interface:


     LoggingInterface log = MyUtility.getLogger();
     myLoggingFunction(log);

So now you start writing this MyUtility.getLogger() function as follows:


     public LoggingInterface getLogger()
     {
          return new FileLogger("myfile");
          // return new EventLogger();
     }

Now you are fancy-pants-proof. But still there is a nagging feeling, the sort that hovers around when you haven't had your cup of coffee after a satisfying feast. So you start fidgeting to spot your cup of Java (sorry, couldn't resist). At this time, the configuration service that you have so long ignored flashes by. You imagine a config file as follows:


     <request name="logger'>
          <classname>EventLogger
     </request>

You also remember that in a good number of not-so-macho-interpreted languages, it is not difficult to instantiate a class if you know its name and pedigree (package, assembly, etc). Armed with information, you modify your getLogger() utility function to the following:


     public LoggingInterface getLogger()
     {
          IConfig cfg = ...
          string loggerClassname = cfg.getValue
("/request/logger/classname","EventLogger");

          Object o = instantiateAnObjectForItsClassname(
		      loggerClassName);
// code is not provided for this function
          LoggingInterface log = (LoggingInterface)o;

          return log;
     }

Now your quest is complete (almost). You are able to instantiate a logging class dynamically using a config file. Then, as your needs grow, you want this capability to instantiate dynamic objects for all sorts of classes and not just the logger. So you modify the utility function yet again:


     public static Object getObject(string interfacename)
     {
          IConfig cfg = ...
          string interfaceClassname = cfg.getValue("/request/" +
interfacename + "/classname");

          Object o = instantiateAnObjectForItsClassname(
		      loggerClassName);
// code is not provided for this function
          return o;
     }

Now you have a general purpose function that can give you an object, given a symbolic name. This utility function is called a factory service. This is a bare-bones factory service. We will expand on this to cover more cases so that you can take advantage of this metaphor.

Factory Service Formalized

So let us formalize our need for a factory into a factory service interface.


public interface IFactory
{
     public Object getObject(string symbolicname):
}

So, essentially, a factory service is a service that returns to you an object given a symbolic name.

Factory Service Enhanced With Caching

Now imagine a scenario where two clients are trying to log in to your application using this service. Here is the scenario:


//client1
     IFactory fact;
     LoggingInterface log = (LoggingInterface)fact.getObject(
	     "logger");
     log.logMessage("Message from client1");

//client2
     IFactory fact;
     LoggingInterface log = (LoggingInterface)fact.getObject(
	     "logger");
     log.logMessage("Message from client2");

Also assume that we are using a FileLogger as our implementor. So the question is, how many FileLogger objects do you expect to see in your application? It would be nice to share the same object in both clients. So your factory can now take this into consideration and cache the requested object. But how does the factory know to cache this object? You have to give the factory a clue that this class is a single instance throughout the application.

There are two ways of doing this.

There is an important difference between the two methods. For example, you have designed your FileLogger to be unaware of threads, assuming that there will be multiple instances of it. And the person that is managing the config files erroneously has changed the "multi-instance" to "single-instance." This will cause threading issues.

Whether something is a single instance or a multi-instance is an important design time constraint that I believe is better expressed as an interface tag. An interface tag is just a fancy name to say that the mentioned interface is an interface with no methods in it.

So let us go ahead and define the caching characterization.


public interface SingleInstanceInterface {}
public interface MultiInstanceInterface {}

If a mentioned class does not implement either of the above interfaces, it is defaulted to MultipleInstanceInterface.

Factory Improved With Initialization

So far, I am hiding an important detail with the FileLogger. Let us examine the config file for this one more time:


<request name="logger'>
     <classname>FileLogger
</request>

and you started using this as follows:


     IFactory fact;
     LoggingInterface log = (LoggingInterface)fact.getObject(
	     "logger");
     log.logMessage("Message from client2");

How does the FileLogger know what file to which to write? Because the factory is not taking an argument that it can pass to the FileLogger class. Let us investigate one obvious scenario. Why don't we alter the factory service to pass arguments to the getObject method so that it can pass them to the FileLogger? With this intent, the above function looks like this:


     IFactory fact;
     LoggingInterface log = (LoggingInterface)fact.getObject(
	     "logger", "filename");
     log.logMessage("Message from client2");

Oops, what if you want to change your logging to EventLogger? In essence, these additional parameters will break the factory contract. So we will have to use an alternative. Let us consider the first one.


public class FileLogger
{
     FileLogger(string filename);
     FileLogger() // default constructor, one that the 
	              // factory is interested in
     {
          // read the filename from  config
          IConfig cfg..
          string filename = cfg.getValue("/Logging/filename",null);
          this(filename);
     }
}

To support the above picture, you would need the following configuration:


<request name="logger'>
     <classname>FileLogger</classname>
</request>

<Logging>
     <filename>abc</filename>
</Logging>

Well, I am back to my hovering doubt with this one, although it works quite well. The sticky point here for me is that configuration information that is part and parcel of FileLogger is being mentioned somewhere else. Here is how I intend to fix the problem. First I am going to fix my config:


<request name="logger'>
     <classname>FileLogger</classname>
     <filename>abc</filename>
</request>

Now I am going to attack my FileLogger class with the following interface:


public interface InitializableInterface
{
     public void initialize(string requestname, IConfig cfg);
}

public class FileLogger :
          LoggingInterface 
		  // main job
          ,SingleInstaceInterface 
		  // only one copy exists
          ,InitializableInterface 
		  // we will see now what this is
{
     private string filename;
     FileLogger();
     public void initialize(string requestname, IConfig cfg)
     {
          filename = cfg.getValue("/request/" + requestname +
"/filename",null);
          .. other initialization stuff
     }
}

Summary of Factory-Related Interfaces

Let me summarize the basic interfaces I have covered so far under the name of factory services. And as a note, for less typing I am going to be using the "I" convention to represent an interface.


public interface IFactory
{
     // Instantiates an object and returns it
     public object getObject(string symbolicName);

     .. More methods to be covered in subsequent articles
}

public interface ISingleInstance {}
     // Indicates to the factory that the intantiated object 
	 // needs to be cached

public interface IMultiInstance {}
     // Object can not be cached

public interface IInitializable
{
     // used to read further unified configuration information
     public void initialize(string symbolicname);
}

Implementing Components Using the Above Factory Service

A component can be seen as a collection of typed methods.


Public interface IComponent1
{
     ReturnType1    method1(arg1, arg2, etc.);
     ReturnType2    method2(arg1,arg2, etc..)
}

And one can implement this interface as follows:


Public class MyComponentImplementation : IComponent1, 
    ISingleInstance
{
     // any local variables you may have
     ReturnType1    method1(arg1, arg2, etc.)
     {
          .. Method1 implementation
     }

     ReturnType2    method2(arg1,arg2, etc..)
     {
          .. Method2 implementation
     }
}

And you can use the following config file to specify the implementation at run time.


     <request name="IComponent1>
          <type>MyComponentImplementation,MyAssembly</type>
     </request>

Where type is the mechanism indicated by .NET for specifying a classname. Once the above config is in place, you can use your component as follows:


     Ifactory fact;
     IComponent1   compInterface = (Icomponent1)fact.getObject
("IComponent1");
     compInterface.method1(..);
     compInterface.method2(..);

What is Appealing About These Components?

Firstly, these components are type-safe, which means programmers are able to invoke the methods on a component using type-safe methods. Once the component is obtained, utilizing a symbolic name, methods on that component are type-safe from that point on.

Secondly, these components could be singletons, which means that regardless of where in your application you ask for a certain component, you will get back the same object. Assuming the component is implemented as a stateless collection of methods, you can take advantage of that fact. If you want to impose stateful methods, you can just not implement the ISingleInstance tag. If you are familiar with the session beans of EJBs, what you have here is a poor man's implementation of session beans. This paradigm is particularly useful if your implementations vary often; for example, an email component that can send mails via multiple channels (SMTP, MAPI, etc.) or a logging component that can log to a variety of outputs.

What is Not So Proper About These Components

The primary drawback of the model is this: As you start adding more methods to your component, the implementation source file becomes unwieldy -- try adding more than 10 methods to a component. Secondly, short of implementing session beans of EJB under .NET, it is difficult to impose such things as security and transactions on a method-by-method basis.

I believe both of these drawbacks can be addressed to a usable extent by introducing an abstraction called task. A task is basically a method that has a name, input arguments, and output arguments. A class is usually responsible for executing this method. This class is then known as the task implementor. So each task is coded in its own class, thereby breaking the code bloat that is normally present in a single monolithic component. These tasks can be declaratively defined in a config file. We can use a higher-level batch program or GUI to create our type-safe components out of these tasks by grouping them into stateless components.

The expected benefits are:

The next article will look into the detail of this task-based design for component services, as well as include the source code for the factory implementation.

Satya Komatineni is the CTO at Indent, Inc. and the author of Aspire, an open source web development RAD tool for J2EE/XML.


Return to .NET DevCenter

Copyright © 2009 O'Reilly Media, Inc.