June 15, 2004

Decorating Proxy Pattern for Implementation of Caching

I came up with this design pattern variation a while back which is the synthesis of Proxy and Decorator. It’s not really a brand new pattern, but it varies from the normal pattern of usage for Decorator and Proxy. Feedback welcome.

Decorating Proxy

A hybrid of Proxy and .

“The proxy pattern makes the clients of a component communicate with a representative rather than to the component itself. Introducing such a placeholder can serve many purposes, including enhanced efficiency, easier access and protection from unauthorized access.”

The Decorator pattern serves to attach “additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality.”

Uses:

One use of this pattern is to implement caching. One way of doing this is to define a shared interface for both the object and the proxy, and to define a constructor for the proxy which accepts a reference to the DAL object. By defining a constructor which accepts a parameter of the same base type, we are in effect blurring the line between Proxy and Decorator.

Example:

public interface IHotelGateway
{
  IHotel GetHotel(string Destination, string HotelCode);
}
public class HotelCacheGateway: IHotelGateway
{
    private IHotelGateway store = null;
    public HotelCacheGateway(IHotelGateway Store)
    {
        store = Store;
    }
    public IHotel GetHotel(string Destination, string HotelCode)
    {    
        IHotel hotel = Cache[GenerateKey(Destination, HotelCode)] as IHotel;
        if (null != hotel) return hotel;
        hotel = store.GetHotel(Destination, HotelCode);
        if (null != hotel) Cache[GenerateKey(Destination, HotelCode)] = hotel;
        return hotel;
    }
}

Then, instead of asking the Data Access Layer (DAL) for an object directly, the client code can ask the Decorating Proxy which will either
retrieve the object from a cache or delegate the call to the DAL.

IHotelGateway proxy = null, dal = null;
dal = new SqlHotelGateway(connection_string);
proxy = new HotelCacheGateway(dal);
IHotel hotel = proxy.GetHotel(destination, hotelcode);

Or, better still, the creation of the Proxy and DAL can be encapsulated in a Factory Method which keeps the client from having to even know about the Proxy.

public class HotelGatewayFactory
{
    private const string ConnectionString = "...";
    public static IHotelGateway Create()
    {
        IHotelGateway proxy = null, dal = null;
        dal = new SqlHotelGateway(ConnectionString);
        proxy = new HotelCacheGateway(dal);
        return proxy;
    }
}

IHotelGateway gateway = HotelGatewayFactory.Create();
IHotel hotel = gateway.GetHotel(destination, hotelcode);

Benefits:

  • Layering (separation of concerns). Client code does not need to concern itself with caching and can focus on what it needs to do with the object
  • Maintainability. It becomes trivial to add/remove the caching functionality because the interface of the cache is the same as the DAL.
  • Simplicity. Designing a caching layer doesn’t require thinking about interface (since it’s already defined) allowing the developer to focus on the most efficient implementation

Extended example:

using System;
using System.Web;
using System.Web.Caching;
// minimal implementation - a better implementation would allow for dependency and expiration control
public class CacheManager 
{       
    private static CacheManager singleton = null;       
    private CacheManager()
    {
                
    }   
    public static CacheManager Instance 
    {   
        get 
        { 
            if (null == singleton) singleton = new CacheManager(); 
            return singleton;
        }
    }
   public static void SetItem(string key, object value)
   {
       HttpContext.Current.Cache[key] = value;
    }   
    public static object GetItem(string key)
    {
        return HttpContext.Current.Cache[key];
    }   
}

using System;
public interface IHotelGateway
{
  IHotel GetHotel(string Destination, string HotelCode);
}

using System; /* -- note lack of dependency on System.Web.Caching -- */
public class HotelCacheGateway: IHotelGateway
{
    private IHotelGateway store = null;
    public HotelCacheGateway(IHotelGateway Store)
    {
        store = Store;
    }
    public IHotel GetHotel(string Destination, string HotelCode)
    {    
        IHotel hotel = CacheManager.Instance.GetItem(GenerateKey(Destination, HotelCode)) as IHotel;
        if (null != hotel) return hotel;
        hotel = store.GetHotel(Destination, HotelCode);
        if (null != hotel) CacheManager.Instance.SetItem(GenerateKey(Destination, HotelCode), hotel);
        return hotel;
    }    
    private string GenerateKey(string Destination, string HotelCode)
    {
        return string.Format("Hotel:{1}-{0}", Destination, HotelCode);
    }
}

using System;
using System.Data;
using System.Data.SqlClient;

public class SqlHotelGateway: IHotelGateway
{
    private string ConnectionString = null;
    public SqlHotelGateway(string ConnString)
    {
        ConnectionString = ConnString;
    }
    public IHotel GetHotel(string Destination, string HotelCode)
    {    
        IHotel hotel = new Hotel(Destination, HotelCode);
        using (SqlConnection db = new SqlConnection(ConnectionString))
        {
            SqlCommand sp = new SqlCommand(db);
            sp.CommandType = CommandType.StoredProcedure;
            sp.CommandText = "usp_GetHotel";
            sp.Parameters.Add(new SqlParameter("", Destination));
            sp.Parameters.Add(new SqlParameter("", HotelCode));
            using (SqlDataReader rdr = sp.ExecuteReader())
            {
                while (rdr.Read())
                {
                    hotel.Description = rdr["Description"].ToString();
                    hotel.SetContact(rdr["ContactName"].ToString(), rdr["ContactEmail"].ToString());
                    hotel.RoomCount = DBNull.Value == rdr["NumberOfRooms"] ? -1 : int.Parse(rdr["NumberOfRooms"]);
                }
            }
        }        
        return hotel;
    }    
}

using System;
public class HotelGatewayFactory
{
    private const string ConnectionString = "...";
    public static IHotelGateway Create()
    {
        IHotelGateway proxy = null, dal = null;
        dal = new SqlHotelGateway(ConnectionString);
        proxy = new HotelCacheGateway(dal);
        return proxy;
    }
}

// Client code 
IHotelGateway gateway = HotelGatewayFactory.Create();
IHotel hotel = gateway.GetHotel(destination, hotelcode);
The main thing to notice about the code above is that the dependencies on specific parts of the framework (like System.Web and System.Data) are limited only to those objects that need to work with them directly. This makes swapping out implementations easier because the dependenices are localized. Changing the CacheManager to work with the file system would not affect client code one bit. Likewise, a migration to an Oracle backend would only affect one class.

Of course, this also assumes certain things about how you package the classes into assemblies. For instance, if you package these classes into one monolithic assembly, then while most code maintenance benefits remain, you will force a recompilation every time you add a new Decorating Proxy that the Factory class needs to know about. This pattern forces you to separate the Factory into a separate assembly so that you minimize the impact of changes. Of course, having an assembly with one class — especially such a small one — is lame, so I would advocate packaging all or several factory classes together into an assembly that, in essence, defines the API for the application. If this sounds familiar, it’s probably because it sounds a lot like Martin Fowler’s Service Layer pattern from , albeit at a lower level. Service Layer is more of a UI layer pattern. This pattern is more of a Data Access Service Layer which defines a common interface for accessing data without requiring the caller to care about implementation details like whether or not the data is being retrieved from a cache or the backing data store.

Posted by Christian at June 15, 2004 01:02 PM |
Comments
Post a comment