| |
|
Chapter I | | Chapter III |
The Series
WCF by example is a series of articles that describe how to design and develop a WPF client using WCF for communication and NHibernate for persistence purposes. The series introduction describes the scope of the articles and discusses the architect solution at a high level.
Chapter Overview
In the previous chapter, we introduced a basic implementation of the Repository which requires to be implemented for each entity in our domain. As we indicated, this component is key for persistence purposes for our domain layer and services as they need to extensively use these components. As a result, it is a good approach to streamline our design in this area so we can provide a comprehensive level of service without compromising coupling between the business logic and our back-end persistence implementation. This chapter tries to justify the creation of a new sort of service: the repository locator. We will see how the use of Generics will prove to be indispensable in this pattern. The pattern strives on removing duplication and keeping a simple facade for services and domain entities.
The source code for this chapter can be found at CodePlex change set 67243. The latest code for the eDirectory solution can also be found at Codeplex.
Current Design Issues
Having concrete repositories for each entity is a costly way to provide persistence functionality to our business layer. We need a more flexible design. The current design requires the entities knowing the type of repository to use. In the previous chapter, we had:
public static Customer Create(IRepository<customer> repository, CustomerDto operation)
{
...
}
Although the use of an interface using Generics seems to be appropriate, we saw that the implementation is costly when creating the in-memory implementation. If we have tens of entities, this is a very expensive approach. Services suffer from the same problem, the need to hold an instance of the repository.
Introducing the Service Locator
We need to provide a single implementation for our domain layer and services that can be used in a transparent manner without having to develop individual implementations for each entity type. We also need a design that can provide a mechanism for articulating specialised calls to the back-end, if required, for different purposes like reporting, performance, and so on. (We are not covering this aspect in this chapter though.) The proposed pattern exposes generic methods instead of being a generic class. It serves as a proxy to the back-end repositories, providing a transparent mechanism for our services and entities to execute persistence calls.
We are going to leave our IRepository
as it was designed in the previous chapter, but we are adding a new interface: IRepositoryLocator
:
It is worth noting that both interfaces are very similar; they expose the same functionality, but the new implementation uses generic methods:
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>();
}
The last method (GetRepository
) is not really required in the interface, but it is critical for the base implementation, so for clarity purposes, we will expose it on the interface.
The solution will provide two implementations of the RepositoryLocator
, the first one for the in-memory, and the second for NHibernate. Both implementations share common functionality that we will place in a base class: 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
}
The beauty of the above implementation is that entities and services delegate onto the RepositoryLocator
for finding out the correct repository. Concrete implementations of the base class will provide the mechanism to retrieve the correct back-end repository.
Re-factor of Entities and Services
Now we can re-factor the Customer
entity to use the RepositoryLocator
instead:
We also replace the Customer
repository in the service with RepositoryLocator
:
Re-factor of the In-memory Repositories
In the previous chapter, we defined the RepositoryEntityStore
and RepositoryCustomer
. We want to define a single implementation of the IRepository
for our in-memory implementation that is valid for all our entities. Unfortunately, the in-memory implementation requires a mechanism for generating the entity PK, a function that NHibernate resolves delegating to the back-end database. Therefore, a few changes are required in our entities to facilitate a common facade; a new interface is declared:
public interface IEntity
{
long Id { get; }
}
As we mentioned before, we are assuming that all our entities have a numeric PK. We are creating an abstract class implementing this interface: EntityBase
. At this point, it is very simple; we may extend it in later chapters:
public abstract class EntityBase
:IEntity
{
public virtual long Id { get; protected set; }
}
All entities inherit from this base class, therefore the Customer
class will look like:
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)
{
...
}
}
Please note that the properties have been declared virtual
to comply with the lazy load functionality in NHibernate. At this point, we can re-factor our in-memory Repository. We are consolidating the functionality we defined in two classes into one single class; the re-factoring is somehow extensive, so we will discuss only the more relevant aspects:
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
}
The Save
method is expected to generate the Entity ID property; the private helper methods work out the new ID and update the entity. At this point, we can discard our RepositoryCustomer
that is not needed anymore and re-factor our tests.
Note [Nov-2010]: Classes in this assembly were later renamed with the InMemory suffix.
Chapter Summary
We discussed some of the problems of the pattern used in the previous chapter, and the need for a service that provides persistence functionality that does not require additional work if a new entity is added to our entity. The RepositoryLocator
is a neat approach that provides a simple layer of abstraction between the business and the persistence layer in an elegant manner.
In the next chapter, we leave the persistence layer to focus on the communication standards, and provide the baseline for the messaging process between the client and server components.
History
- 4-Nov-2010: Source code revision was done as a result of some typos.