Click here to Skip to main content
Click here to Skip to main content
Go to top

Use Strategy pattern to support cloud storage

, 1 Oct 2012
Rate this:
Please Sign up or sign in to vote.
In this article, I will explain how to use strategy pattern to add accessing cloud storage support in an on-premise application.

Introduction

One challenge faced when migrating an on-premise application into Cloud is how to redesign how the application deals with files and directories. For a regular .NET based application, when there is a business file needed to be kept somewhere, the application will just save the file into a local or shared drive via the .NET IO API. The problem in the cloud environment is a local or shared drive is not recommended for storing business files anymore. The recommended place for storing business files is cloud storage, such as BLOB in Windows Azure and S3 in Amazon AWS. So, to design something to let the application not be dependent on the actual storage used is crucial for applications to be cloud ready.

How to deal with the difference between local storage and cloud storage

The local storage in Windows contains both files and directories. File is a block of arbitrary information, or resource for storing information, which is available to a computer program and is usually based on some kind of durable storage. Directory, also known as a folder, is a virtual container within a digital file system, in which groups of computer files and possibly other directories can be kept and organized. However, the normal meaning of file and directory is not available in cloud anymore. The cloud storage has the faith to deal with distribution, scalability, availability, and performance, so it doesn’t directly expose itself as file and directory. Use Windows Azure BLOB storage as example, BLOB storage has a three layer structure: Account, Container, and Blob. Account is the access point for BLOB storage. Application must provide the correct account information to get into BLOB storage service in Windows Azure. Account can be thought of as a hard drive in a computer. Container is a grouping of a set of BLOBs. Container can be thought of as a partition in a computer. BLOB is a file of any type and size. BLOB can be thought of as a file in computer. How about a directory? There is no direct correspondence concept of a directory in BLOB storage. All BLOBs are immediately under a container. However, a special type of BLOB can be created to represent a directory. By doing this, a regular directory structure can still be created in BLOB storage.

The difference between local storage and cloud storage adds burden on the application developer to have two sets of code dealing with different storage types. The old approach for this situation is to let code for local storage and code for cloud storage co-exist in an application, and then the application can go to a particular branch based on a condition. This is OK if a limited conditions application wants to support this feature. How about supporting a new type of cloud storage? The code must be changed to add a new branch in the application to deal with the new storage type. Is there a better way?

Strategy Pattern

Strategy pattern is a particular software design pattern, whereby the algorithm can be selected at runtime.

For the storage situation, different storage accessing implementations can be thought of as different algorithms on handling file and directory. By having a storage interface defined for common file and directory operations, we can implement them in different concert classes. Therefore, to support a new type of storage, it’s enough to just add another concert implementation. The context of Strategy pattern will bring in the appropriate implementation at runtime.

Storage interface

The first step in realizing the Strategy pattern is to design interfaces for the algorithm. In our case, they are storage interfaces. There are two storage interfaces: IDirectory and IFile.

For demo purpose, only three methods are defined in IDirectory and IFile.

Storage interface implementation

To allow application access to local storage and cloud storage, we need to have two sets of implementations..

Local directory and file

Source code

IFile
public class LocalFile: IFile
{
    public void Create(string path, Stream s)
    {
        FileStream fs = System.IO.File.Create(LocalStorageUtility.GetRealPath(path));
        s.Position = 0;
        byte[] bytes = new byte[s.Length];
        s.Read(bytes, 0, bytes.Length);
        fs.Write(bytes, 0, bytes.Length);
        fs.Close();
    }

    public void Delete(string path)
    {
        System.IO.File.Delete(LocalStorageUtility.GetRealPath(path));
    }

    public bool Exists(string path)
    {
        return System.IO.File.Exists(LocalStorageUtility.GetRealPath(path));
    }
}
IDirectory:
public class LocalDirectory: IDirectory
{
    public void CreateDirectory(string path)
    {
        Directory.CreateDirectory(LocalStorageUtility.GetRealPath(path));
    }

    public void Delete(string path)
    {
        Directory.Delete(LocalStorageUtility.GetRealPath(path));
    }

    public bool Exists(string path)
    {
        return Directory.Exists(LocalStorageUtility.GetRealPath(path));
    }
}

Cloud directory and file

Source code

CloudDirectory
public class CloudDirectory: IDirectory
{
    public void CreateDirectory(string path)
    {
        string cloudPath = CloudStorageUtility.GetCloudPath(path);

        if (cloudPath.IndexOf("/") != -1)
        {
            //create a placeholder as path
            CloudBlob blob = CloudStorageUtility.Client.GetBlobReference(cloudPath + "/:SpaceBlockPlaceholder:");
            blob.UploadText("");
        }
        else
        {
            CloudBlobContainer container = CloudStorageUtility.Client.GetContainerReference(cloudPath);
            container.CreateIfNotExist();
        }
    }

    public void Delete(string path)
    {
        string cloudPath = CloudStorageUtility.GetCloudPath(path);

        if (cloudPath.IndexOf("/") != -1)
        {
            CloudBlobDirectory dir = CloudStorageUtility.Client.GetBlobDirectoryReference(cloudPath + "/");
            IEnumerable<ilistblobitem> items = dir.ListBlobs();
            foreach (IListBlobItem item in items)
            {
                if (item.GetType() == typeof(CloudBlobDirectory))
                {
                    Delete(item.Uri.PathAndQuery);
                }
                else if (item.GetType() == typeof(CloudBlob) || item.GetType().BaseType == typeof(CloudBlob))
                {
                    ((CloudBlob)item).DeleteIfExists();
                }
            }
        }
        else
        {
            CloudBlobContainer container = CloudStorageUtility.Client.GetContainerReference(cloudPath);
            container.Delete();
        }
    }

    public bool Exists(string path)
    {
        string cloudPath = CloudStorageUtility.GetCloudPath(path);

        CloudBlobContainer container = CloudStorageUtility.Client.GetContainerReference(cloudPath);
        try
        {
            container.FetchAttributes();
            return true;
        }
        catch
        {
            return false;
        }
    }
}</ilistblobitem>
CloudFile
public class CloudFile: IFile
{
    public void Create(string path, Stream s)
    {
        CloudBlob blob = CloudStorageUtility.Client.GetBlobReference(CloudStorageUtility.GetCloudPath(path));
        MemoryStream ms = new MemoryStream();
        s.Position = 0;
        byte[] bytes = new byte[s.Length];
        s.Read(bytes, 0, bytes.Length);
        ms.Write(bytes, 0, bytes.Length);
        ms.Position = 0;
        blob.UploadFromStream(ms);
    }

    public void Delete(string path)
    {
        CloudBlob blob = CloudStorageUtility.Client.GetBlobReference(CloudStorageUtility.GetCloudPath(path));
        blob.DeleteIfExists();
    }

    public bool Exists(string path)
    {
        CloudBlob blob = CloudStorageUtility.Client.GetBlobReference(CloudStorageUtility.GetCloudPath(path));
        try
        {
            BlobRequestOptions opts = new BlobRequestOptions();
            opts.UseFlatBlobListing = true;
            blob.FetchAttributes(opts);
            return true;
        }
        catch (StorageClientException ex)
        {
            if (ex.ErrorCode == StorageErrorCode.ResourceNotFound)
            {
                return false;
            }
            else
            {
                throw;
            }
        }
    }
}

Switch the implementation

The context should be able to switch different implementations at runtime. One way to achieve this is by using .NET Reflection. The context will use information in the configuration file to create an IDirectory or IFile instance and let the application use it.

How to run the demo application

The demo application includes unit tests for LocalStorage and CloudStorage. The local storage unit test creates a folder and file in the Demo.Tests project’s Debug folder. The cloud storage unit test creates a folder and file in Windows Azure's BLOB storage, therefore Windows Azure storage must be setup before you can run it.

Set up Windows Azure storage

  1. Login Windows Azure
  2. Click STORAGE to show all existing cloud storages.
  3. Click the NEW button to add a new cloud storage.
  4. The newly created storage includes BLOB, Table, and Queue in Windows Azure.

To allow unit test code to connect with your BLOB storage, you must also have the right StorageConnectionString in your config file. The StorageConnectionString has format like this:

DefaultEndpointsProtocol=[Protocol];AccountName=[Account Name];AccountKey=[Account Key]

Example: DefaultEndpointsProtocol=https;AccountName=librarydemo;AccountKey=abcdefghijklmnopqrstuvwxyz

The https is recommended for DefaultEndpointsProtocol. AccountName is the name of your cloud storage. AccountKey can be gotten from the manage key window by clicking the MANAGE KEYS button in your storage page.

Summary

Moving to cloud is the trend in computer industry, there are many things that needs to be considered when we are moving an on-premise application into cloud. By wisely using a Design Pattern, we can make an application be cloud ready without sacrificing the on-premise option.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Henry He
Software Developer (Senior)
United States United States
Senior Software Developer from New Jersey, USA

Have 12+ years experience on enterprise application development with various technologies.

Comments and Discussions

 
GeneralMy vote of 3 PinmemberMatt Gerrans8-Oct-12 13:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140926.1 | Last Updated 1 Oct 2012
Article Copyright 2012 by Henry He
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid