WCF 示例 – 第二章 – 存储库






4.82/5 (23投票s)
介绍 RepositoryLocator 模式。
第一章 | 第三章 |
系列文章
WCF by example 是一系列文章,描述了如何设计和开发 WPF 客户端,使用 WCF 进行通信,并使用 NHibernate 进行持久化。 系列介绍 描述了文章的范围,并从高层次讨论了架构解决方案。
章节概述
在上一章中,我们介绍了一个基本的 Repository 实现,它需要在我们的领域中的每个实体都实现。正如我们所指出的,这个组件对于我们的领域层和服务的持久化目的至关重要,因为它们需要广泛地使用这些组件。因此,在这一领域精简我们的设计是一个好方法,这样我们可以在不影响业务逻辑和后端持久化实现之间的耦合的情况下,提供全面的服务。本章试图证明创建一种新型服务是合理的:Repository Locator。我们将看到泛型的使用在这个模式中将是不可或缺的。该模式致力于消除重复,并为服务和领域实体保持一个简单的外观。
本章的源代码可以在 CodePlex change set 67243 中找到。 eDirectory 解决方案的最新代码也可以在 Codeplex 中找到。
当前设计问题
为每个实体拥有具体的仓库来为我们的业务层提供持久化功能是一种成本高昂的方式。我们需要更灵活的设计。当前的设计要求实体知道要使用的仓库的类型。在上一章中,我们有
public static Customer Create(IRepository<customer> repository, CustomerDto operation)
{
...
}
虽然使用带有泛型的接口似乎是合适的,但我们看到创建内存中实现时成本很高。如果我们有几十个实体,这是一种非常昂贵的方法。服务也面临同样的问题,需要持有仓库的实例。
介绍 Service Locator
我们需要为我们的领域层和服务提供一个单一的实现,该实现可以以透明的方式使用,而无需为每个实体类型开发单独的实现。我们还需要一个设计,能够为不同的目的(如报告、性能等)提供一个机制来发出专门的后端调用。(本章不涉及这方面。)所提出的模式公开泛型方法,而不是成为一个泛型类。它充当后端仓库的代理,为我们的服务和实体提供一个透明的机制来执行持久化调用。
我们将保留我们在上一章中设计的 IRepository
,但我们将添加一个新接口:IRepositoryLocator
值得注意的是,这两个接口非常相似;它们公开相同的功能,但新实现使用了泛型方法
public interface IRepositoryLocator
{
#region CRUD operations
TEntity Save<TEntity>(TEntity instance);
void Update<TEntity>(TEntity instance);
void Remove<TEntity>(TEntity instance);
#endregion
#region Retrieval Operations
TEntity GetById<TEntity>(long id);
IQueryable<TEntity> FindAll<TEntity>();
#endregion
IRepository<T> GetRepository<T>();
}
最后一个方法(GetRepository
)在接口中不是必需的,但它对于基类的实现至关重要,因此为了清晰起见,我们将其公开在接口上。
该解决方案将提供 RepositoryLocator
的两个实现,第一个用于内存中,第二个用于 NHibernate。两个实现共享一些通用功能,我们将把这些功能放在一个基类中:RepositoryLocatorBase
。
public abstract class RepositoryLocatorBase
: IRepositoryLocator
{
#region IRepositoryLocator Members
public TEntity Save<TEntity>(TEntity instance)
{
return GetRepository<TEntity>().Save(instance);
}
public void Update<TEntity>(TEntity instance)
{
GetRepository<TEntity>().Update(instance);
}
public void Remove<TEntity>(TEntity instance)
{
GetRepository<TEntity>().Remove(instance);
}
public TEntity GetById<TEntity>(long id)
{
return GetRepository<TEntity>().GetById(id);
}
public IQueryable<TEntity> FindAll<TEntity>()
{
return GetRepository<TEntity>().FindAll();
}
public abstract IRepository<T> GetRepository<T>();
#endregion
}
上述实现的优点在于,实体和服务的委托会找到正确的仓库,而 RepositoryLocator
则负责查找。基类的具体实现将提供检索正确后端仓库的机制。
重构实体和服务
现在我们可以重构 Customer
实体来使用 RepositoryLocator
我们还将服务中的 Customer
仓库替换为 RepositoryLocator
重构内存中仓库
在上一章中,我们定义了 RepositoryEntityStore
和 RepositoryCustomer
。我们希望为我们的内存中实现定义一个单一的 IRepository
实现,该实现对所有实体都有效。不幸的是,内存中实现需要一种生成实体 PK 的机制,NHibernate 会通过委托给后端数据库来解决这个问题。因此,我们需要对我们的实体进行一些更改,以便实现一个通用的外观;我们声明了一个新接口
public interface IEntity
{
long Id { get; }
}
正如我们之前提到的,我们假设我们所有的实体都有一个数字 PK。我们正在创建一个实现此接口的抽象类:EntityBase
。此时,它非常简单;我们可能会在后续章节中扩展它
public abstract class EntityBase
:IEntity
{
public virtual long Id { get; protected set; }
}
所有实体都继承自这个基类,因此 Customer
类将看起来像
public class Customer
:EntityBase
{
protected Customer() { }
public virtual string FirstName { get; protected set; }
public virtual string LastName { get; protected set; }
public virtual string Telephone { get; protected set; }
public static Customer Create(IRepositoryLocator locator, CustomerDto operation)
{
...
}
}
请注意,属性已声明为 virtual
,以符合 NHibernate 中的延迟加载功能。此时,我们可以重构我们的内存中仓库。我们将之前在两个类中定义的合并到单个类中;重构有些广泛,所以我们将只讨论最相关方面
public class RepositoryEntityStore<TEntity>
:IRepository<TEntity>
{
protected readonly IDictionary<long, TEntity> RepositoryMap =
new Dictionary<long, TEntity>();
#region IRepository<TEntity> Members
public TEntity Save(TEntity instance)
{
IEntity entityInstance = GetEntityInstance(instance);
if (entityInstance.Id != 0)
{
throw new ApplicationException("Entity instance cannot " +
"be saved, the Id field was not cero");
}
GetNewId(instance);
RepositoryMap.Add(entityInstance.Id, instance);
return instance;
}
public void Update(TEntity instance) { ... }
public void Remove(TEntity instance)
{
IEntity entityInstance = GetEntityInstance(instance);
RepositoryMap.Remove(entityInstance.Id);
}
public TEntity GetById(long id)
{
return RepositoryMap[id];
}
public IQueryable<TEntity> FindAll()
{
return RepositoryMap.Values.AsQueryable();
}
#endregion
#region Helper Methods
private void GetNewId(TEntity instance) { ... }
private readonly IDictionary<Type, MethodInfo> Setters =
new Dictionary<Type, MethodInfo>();
private MethodInfo GetSetter(Type type) { ... }
private IEntity GetEntityInstance(TEntity instance)
{
var entityInstance = instance as IEntity;
if (entityInstance == null)
throw new ArgumentException("Passed instance is not an IEntity");
return entityInstance;
}
#endregion
}
Save
方法预计会生成 Entity ID 属性;私有辅助方法会找出新的 ID 并更新实体。此时,我们可以丢弃不再需要的 RepositoryCustomer
并重构我们的测试。
注释 [2010 年 11 月]:此程序集中的类后来重命名为带有 *InMemory* 后缀。
章节总结
我们讨论了上一章中使用的一些模式问题,以及对一个不需要额外工作就能添加新实体的持久化功能的服务的需求。RepositoryLocator
是一种优雅的方法,它在业务层和持久化层之间提供了一个简单的抽象层。
在下一章中,我们将暂时搁置持久化层,专注于通信标准,并为客户端和服务器组件之间的消息传递过程提供基础。
历史
- 2010 年 11 月 4 日:由于一些错别字,对源代码进行了修订。