使用策略模式支持云存储
在本文中,我将解释如何使用策略模式为本地应用程序添加云存储访问支持。
引言
将本地应用程序迁移到云环境时面临的一个挑战是如何重新设计应用程序处理文件和目录的方式。对于一个普通的基于 .NET 的应用程序,当需要保存业务文件时,应用程序会通过 .NET IO API 将文件保存到本地或共享驱动器。云环境中的问题在于,不再推荐将本地或共享驱动器用于存储业务文件。推荐的业务文件存储位置是云存储,例如 Windows Azure 中的 BLOB 和 Amazon AWS 中的 S3。因此,设计一个让应用程序不依赖于实际使用的存储的方案,对于应用程序准备好迎接云至关重要。
如何处理本地存储和云存储之间的差异
Windows 中的本地存储包含文件和目录。文件是任意信息的块,或用于存储信息的资源,可供计算机程序使用,通常基于某种持久化存储。目录(也称为文件夹)是在数字文件系统中位于内部的虚拟容器,其中可以保留和组织计算机文件以及可能的其他目录的组。然而,文件和目录的正常含义在云中已不再可用。云存储负责处理分发、可伸缩性、可用性和性能,因此它不会直接暴露为文件和目录。以 Windows Azure BLOB 存储为例,BLOB 存储具有三层结构:账户、容器和 BLOB。账户是 BLOB 存储的访问点。应用程序必须提供正确的账户信息才能访问 Windows Azure 中的 BLOB 存储服务。账户可以被认为是计算机中的硬盘。容器是 BLOB 集合的分组。容器可以被认为是计算机中的分区。BLOB 是任何类型和大小的文件。BLOB 可以被认为是计算机中的文件。那么目录呢?在 BLOB 存储中没有直接对应的目录概念。所有 BLOB 都直接位于容器下。但是,可以创建一种特殊类型的 BLOB 来表示目录。通过这样做,仍然可以在 BLOB 存储中创建常规的目录结构。
本地存储和云存储之间的差异给应用程序开发人员带来了额外的负担,需要编写两套处理不同存储类型的代码。在这种情况下,旧的方法是让处理本地存储的代码和处理云存储的代码在应用程序中共存,然后应用程序可以根据条件进入特定的分支。如果一个应用程序需要支持这个功能,并且条件有限,那么这样做是可以的。但是,如果需要支持一种新的云存储类型呢?就必须修改代码,在应用程序中添加一个新的分支来处理新的存储类型。有没有更好的方法?
策略模式
策略模式是一种特定的软件设计模式,它允许在运行时选择算法。
对于存储情况,不同的存储访问实现可以被视为处理文件和目录的不同算法。通过为常见的文件和目录操作定义一个存储接口,我们可以在不同的具体类中实现它们。因此,要支持一种新的存储类型,只需添加另一个具体实现就足够了。策略模式的上下文将在运行时引入适当的实现。
存储接口
实现策略模式的第一步是为算法设计接口。在我们的例子中,它们是存储接口。有两个存储接口:IDirectory
和 IFile
。
为了演示目的,IDirectory
和 IFile
中只定义了三个方法。
存储接口实现
为了允许应用程序访问本地存储和云存储,我们需要两套实现。
本地目录和文件
源代码
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));
}
}
云目录和文件
源代码
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;
}
}
}
}
切换实现
上下文应该能够在运行时切换不同的实现。实现这一点的一种方法是使用 .NET 反射。上下文将使用配置文件中的信息来创建一个 IDirectory
或 IFile
实例,并让应用程序使用它。
如何运行演示应用程序
演示应用程序包含 LocalStorage
和 CloudStorage
的单元测试。本地存储单元测试将在 Demo.Tests
项目的 Debug 文件夹中创建一个文件夹和文件。云存储单元测试将在 Windows Azure 的 BLOB 存储中创建一个文件夹和文件,因此在运行它之前必须设置 Windows Azure 存储。
设置 Windows Azure 存储
- 登录 Windows Azure。
- 单击 STORAGE 以显示所有现有的云存储。
- 单击 NEW 按钮添加一个新的云存储。
- 新创建的存储包含 Windows Azure 中的 BLOB、Table 和 Queue。
为了让单元测试代码连接到你的 BLOB 存储,你的配置文件中还必须有正确的 StorageConnectionString
。StorageConnectionString
的格式如下:
DefaultEndpointsProtocol=[Protocol];AccountName=[Account Name];AccountKey=[Account Key]
示例: DefaultEndpointsProtocol=https;AccountName=librarydemo;AccountKey=abcdefghijklmnopqrstuvwxyz
对于 DefaultEndpointsProtocol
,建议使用 https。AccountName
是你的云存储的名称。AccountKey
可以通过单击存储页面上的 **MANAGE KEYS** 按钮从管理密钥窗口获取。
摘要
转向云是计算机行业的趋势,在将本地应用程序迁移到云时,有很多事情需要考虑。通过明智地使用设计模式,我们可以在不牺牲本地选项的情况下,使应用程序为云做好准备。