| | |
Introduction | | Chapter II |
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 the project.
Chapter Overview
This is the first chapter of the series. This chapter sets the baseline for the eDirectory solution; we will introduce some core solution concepts as the Customer
entity, the definition of the repository, a customer service, and our first couple tests. In later chapters we discuss how to evolve the original design described in this articles and how we can streamline the persistence components so when additional new entities are required, there is very little effort in getting this side of the solution working.
The latest code for the eDirectory solution is found at CodePlex.
The Model - Customer Entity
As we mentioned in our introduction, we will keep our model very simple, only one entity for the moment will be required. The customer
class is declared as:
public class Customer
{
protected Customer() { }
public long Id { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Telephone { get; private set; }
}
Couple things to observe; properties are protected
so they can only be changed by action methods; the class does not provide a public
constructor, in this way we strive in setting up classes where their state can only be changed by using well known methods declared within the entity. We should not assume that our classes are going to be used in the proper way so making the constructor protected
provides a nice way to control the creation of new instances. We will justify this approach when we cover persistence in a Chapter II - Repositories.
Also, entities are defined in the eDirectory.Domain
assembly which will be used only by our server side components; however, the client does not use the domain classes, instead DTOs are provided to the client.
The Customer Services
The eDirectory provides the following customer services:
Function | Description |
GetById | Retrieves a customer by providing a unique ID |
ListAll | Returns all customer instances |
CreateNewCustomer | Creates a new customer instance |
UpdateCustomer | Amends an existing customer instance |
Services are defined using the following conventions:
- Input parameters will be primitives or data transfer objects
- All the service methods are functions, that is, they all return some minimal information back to the client.
We must keep our service signatures simple so WCF serialization problems are avoided. We also want to provide a communication pattern between the client and server without relying on a specific WCF mechanism, we will see in
Chapter III - Response how this aspect is resolved.
Also, service contracts (interfaces) are declared in a common assembly as both the server and client components require them, we will not use the Web References wizard feature in the client side to generate WCF proxies in the client side. The same is true for the DTOs where they are shared across both the server and client components.
At this stage, we need our first DTO which looks very similar to our Customer
entity:
public class CustomerDto
{
public long CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Telephone { get; set; }
}
Now, we can define our first service contract:
[ServiceContract(Namespace = "http://wcfbyexample/customerservices/")]
public interface ICustomerService
{
[OperationContract]
CustomerDto CreateNewCustomer(CustomerDto customer);
[OperationContract]
CustomerDto GetById(long id);
[OperationContract]
CustomerDto UpdateCustomer(CustomerDto customer);
[OperationContract]
List<customerdto> FindAll();
}
Let's try to create a mechanism for creating customer instances.
Repository
Domain entities should be decoupled from the persistence back-end mechanism. The repository pattern provides the required persistence functionality to our domain entities fulfilling the decoupling aspect.
At this point, the following repository definition is sufficient:
public interface IRepository<TEntity>
{
#region CRUD operations
TEntity Save(TEntity instance);
void Update(TEntity instance);
void Remove(TEntity instance);
#endregion
#region Retrieval Operations
TEntity GetById(long id);
IQueryable<TEntity> FindAll();
#endregion
}
The most important aspects to notice are that we have defined a generic repository and that the FindAll
method returns an IQueryable
instance.
Create Factory Method
At this point, we can go back to our domain class and provide a mechanism to facilitate the creation of customer instances:
public static Customer Create(IRepository<customer> repository, CustomerDto operation)
{
var instance = new Customer
{
FirstName = operation.FirstName,
LastName = operation.LastName,
Telephone = operation.Telephone
};
repository.Save(instance);
return instance;
}
See how the DTO instance is used to populate the properties of a new customer instance. Then the save
method is executed against the passed repository. Instead of using a reference to the repository implementation in our entities, we will pass an instance of the repository to our entity methods and factories. This approach will evolve as we cover future aspects of the application. Another aspect to notice here is that the factory method creates persisted customer instances, so when the customer instance is returned, the persistence context is fully aware of the new instance, in fact the Id is available after the save method is invoked.
In-Memory Repository Implementation
At this point, we could start doing some TDD, we could mock repositories and so on, but providing an in-memory repository implementation is relatively inexpensive and leverages testing and business exploration. As the In-Memory persistor will never be used in a production environment, we want to declare this implementation in a different assembly: eDirectory.Naive
. We define an abstract class for in-memory persistors with the common functionality:
public abstract class RepositoryEntityStore<TEntity>
:IRepository<TEntity>
{
protected readonly IDictionary<long, TEntity> RepositoryMap = new Dictionary<long, TEntity>();
#region IRepository<TEntity> Members
public abstract TEntity Save(TEntity instance);
public abstract void Update(TEntity instance);
public abstract void Remove(TEntity instance);
public TEntity GetById(long id)
{
return RepositoryMap[id];
}
public IQueryable<TEntity> FindAll()
{
return RepositoryMap.Values.AsQueryable();
}
#endregion
}
Then we create the Customer
implementation:
public class RepositoryCustomer
: RepositoryEntityStore<customer>
{
public override Customer Save(Customer instance)
{
...
}
public override void Update(Customer instance)
{
...
}
public override void Remove(Customer instance)
{
...
}
private void GetNewId(Customer instance)
{
...
}
private readonly IDictionary<type,> Setters = new Dictionary<type,>();
private MethodInfo GetSetter(Type type)
{
...
}
}
There is plenty of room for improvement in the above code, but it is worth noting how the entities are stored in a hashed map using the entity ID. As I mentioned in the series introduction, we have full control over our database design so we have chosen to have a numeric primary key in all our tables which eases the domain entities definition. In the next later chapter: Chapter II - Repositories , we will see how the above code evolves and one single implementation for all entity types alone is only required.
Our First Test
Let's create a customer using the factory and then we check that the ID and first name are correct:
[TestClass]
public class CustomerTests
{
[TestMethod]
public void CreateCustomer()
{
var repository = new RepositoryCustomer();
var dto = new CustomerDto
{
FirstName = "Joe",
LastName = "Bloggs",
Telephone = "9999-8888"
};
var customer = Customer.Create(repository, dto);
Assert.IsFalse(customer.Id == 0, "Customer Id should have been updated");
Assert.AreSame(customer.FirstName, dto.FirstName, "First Name are different");
}
}
Customer Service Implementation
We are now ready to create our first service implementation:
public class CustomerService
:ICustomerService
{
public IRepository<customer> Repository { get; set; }
#region ICustomerService Members
public Common.Dto.Customer.CustomerDto CreateNewCustomer(CustomerDto dto)
{
var customer = Customer.Create(Repository, dto);
return new CustomerDto
{
CustomerId = customer.Id,
FirstName = customer.FirstName,
LastName = customer.LastName,
Telephone = customer.Telephone
};
}
...
}
Let's create a test that executes this service:
[TestClass]
public class CustomerServiceTests
{
[TestMethod]
public void CreateCustomer()
{
var service = new CustomerService { Repository = new RepositoryCustomer() };
var dto = new CustomerDto
{
FirstName = "Joe",
LastName = "Bloggs",
Telephone = "9999-8888"
};
var customer = service.CreateNewCustomer(dto);
Assert.IsFalse(customer.CustomerId == 0,
"Customer Id should have been updated");
Assert.AreSame(customer.FirstName, dto.FirstName,
"First Name are different");
}
}
Chapter Conclusion
We have covered a lot of aspects in this article. In the first place, we set up a few projects: Common, Domain, and Naive. We described the basics about designing domain entities and their relation to the repository. We concluded the chapter using the service to create a customer instance and wrote our first couple tests.
In the next chapter, we will discuss some of the proposed patterns used so far, we will identify a few aspects that could be improved, and we will introduce the Repository Locator concept.