| | |
Chapter III | | Chapter V |
Sharpy Project
| I am currently working on a new project for a Metro application: Sharpy. I intend to use the patterns discussed in the WCF by Example articles for the service side of the Sharpy application; the goal is to demonstrate how similar is the development of Metro applications to the type of applications we have seen so far. There is not code available yet but hopefully this will change soon. I hope you like it. The article is here.
|
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 "Chapter I - Baseline", a draft version of the CustomerService was defined. In "Chapter III - Response", the service was re-factored so business warnings and exceptions are always available at the client side. The re-factor consisted of providing some additional functionality (IDtoResponse ) to the DTOs used in our services. But we did not cover how the service manages business warnings and exceptions.
In this chapter, we will discuss the need for establishing a central management component for the message processing in a transparent manner for our services. There is also a need for managing business transactions, we will see how both requirements can be satisfied in one single place.
We will introduce few classes and interfaces in this chapter. The transaction manager is defined in the Domain assembly, the base class for our services is also declared in this assembly.
We will also create implementations in our in-memory Naive assembly.
The source code for this chapter can be found at Codeplex change set 67474. The latest code for the eDirectory solution is found at Codeplex.
| |
Transaction Manager
Services must be executed within transactions, so in case an exception is thrown we can rollback all the changes for data integrity purposes. The transaction manager exposes a rather basic interface to satisfy this requirement:
| It is worth noting that our interface implements the IDispose interface which will provide a neat way to ensure transactions are well managed. The "ExecuteCommand " generic method returns an instance of the IDtoResponse interface; a function that requires a RepositoryLocator instance and returns an instance of the IDtoResponse interface needs to be passed to the method:
TResult ExecuteCommand<TResult>
(Func<IRepositoryLocator, TResult> command)
where TResult : class, IDtoResponseEnvelop;
|
|
We will create two implementations of the ITransManager
interface, one for NHibernate and the other for the in-memory implementation. Essentially the in-memory implementation will not support transactions. Although some functionality will be common for both implementations so a base class can be declared:
| There a few interesting aspects in this base class that are worthy of a more detailed discussion. Firstly, as we mentioned, the IDispose pattern provides a neat solution for our transactional design; secondly, the "ExecuteCommand " method is key in our design, let's have a look at them:
|
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected bool IsDisposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
if (!IsDisposed && IsInTranx)
{
Rollback();
}
Locator = null;
IsDisposed = true;
}
#endregion
So our dispose implementation ensures that transactions are rollback if our instance happens to be in the transaction stage when the dispose
method is called. We will see later how this design provides a handy way to use our transaction manager instances.
public abstract class TransManagerBase
:ITransManager
{
protected bool IsInTranx;
public IRepositoryLocator Locator { get; set; }
#region ITransManager Members
public TResult ExecuteCommand<TResult>
(Func<Repository.IRepositoryLocator, TResult> command)
where TResult : class, Common.Message.IDtoResponseEnvelop
{
try
{
BeginTransaction();
var result = command.Invoke(Locator);
CommitTransaction();
CheckForWarnings(result);
return result;
}
catch (BusinessException exception)
{
if (IsInTranx) Rollback();
var type = typeof(TResult);
var instance = Activator.CreateInstance
(type, true) as IDtoResponseEnvelop;
if (instance != null) instance.Response.AddBusinessException(exception);
return instance as TResult;
}
catch (Exception e)
{
throw;
}
}
...
#endregion
}
There are a lot of things happening in a few lines of code. In the happy case:
- A transaction is started
- The transaction manager passes an instance of the repositorylocator to the given function
- Invokes the function storing the result
- The transaction at this point is committed
- Then we check for warnings (we will see in a later chapter the details of this method)
- The result is returned to the calling instance
If invoking the given function, a business exception is thrown:
- The transaction is rollback if needed
- A new instance of the passed type is created
- The code returns the "blank" instance with the business exception details
Finally, if another type of exception is thrown when the command is invoked, a catch
exception section is in place. In the current implementation, we don't do anything and the exception is re-thrown. This is probably a good spot for a little of Log4Net code for example.
Transaction Factory
| The main role for the factory is to "inject" a repositorylocator instance when a new manager is created. The manager is then responsible for passing the repositorylocator to the invoked commands but it is not resposible for creating it. This approach aligns very well with the NHibernate guidelines. The naive implementation we will describe later differs from the NHibernate one so we will not fully appreciate this pattern until we cover the NHibernate implementation in a later chapter.
|
ServiceBase Class
At this point, we are ready to use our recently introduced classes in our services. So far, our services are not transactional, they also hold an instance of the RepositoryLocator
:
public class CustomerService
:ICustomerService
{
public IRepositoryLocator Repository { get; set; }
#region ICustomerService Members
public CustomerDto CreateNewCustomer(CustomerDto dto)
{
var customer = Customer.Create(Repository, dto);
return Customer_to_Dto(customer);
}
...
#endregion
}
The fact that the service is holding an instance of the RepositoryLocator
might cause trouble when our application is trying to manage the back-end database transactions. This is where our new TransFactory
class is resulting an excellent solution.
In the first place, a new abstract
class is created that provides common functionality to all our services, this is the ServiceBase
class:
public class ServiceBase
{
public ITransFactory Factory { get; set; }
protected TResult ExecuteCommand<TResult>
(Func<IRepositoryLocator, TResult> command)
where TResult : class, IDtoResponseEnvelop
{
using (ITransManager manager = Factory.CreateManager())
{
return manager.ExecuteCommand(command);
}
}
}
In four lines of code, we have a lot of good and interesting things happening. You may recognize the signature of the ExecuteCommand
, it is exactly the same that the method with same name in our new friend TransManagerBase
class. As we mentioned before, the Dispose
pattern implemented by the TransManager
makes our code very neat indeed when the "using
" statement is applied. Just remember that the factory creates the manager and "injects" an instance of the repositorylocator
so it can be used when the command is invoked by the manager.
There is a little bit taking place in this chapter which is critical for the understanding of the application design. You may want to spend some time debugging the chapter's tests to get a good understanding of what is going on here.
At this point, we need to re-factor the CustomerService
. We want this service to inherit from the new abstract
class and remove the RepositoryLocator
instance. We are going to re-factor the service methods using the "ExecuteCommand
" base class method; an anonymous method and a lambda expression are quite handy at this point:
Customer Service before:
Customer Service after:
For clarity and maintenance purposes, it is good idea to move the command logic to a different private
method. This approach facilitates the debugging of these expressions on Visual Studio as setting break points on anonymous methods might be tricky. So our service method ends up as follows:
|
public class CustomerService
:ServiceBase, ICustomerService
{
#region ICustomerService Members
public CustomerDto CreateNewCustomer
(CustomerDto dto)
{
return ExecuteCommand(locator =>
CreateNewCustomerCommand(locator, dto));
}
private CustomerDto CreateNewCustomerCommand
(IRepositoryLocator locator, CustomerDto dto)
{
var customer = Customer.Create(locator, dto);
return Customer_to_Dto(customer);
}
...
#endregion
}
|
In-memory Implementation
For the time we will only implement the in-memory implementations for the transaction manager and the factory. Couple aspects that are different in these implementations, the in-memory transaction manager is not transactional capable which happens to be ironic given the name of the class. Its role is just to manage warnings and exceptions. The other aspect of this implementation is that the factory always returns the same transaction manager instance. This is achieved overridden the Dispose
method in the base class so the Locator is not terminated:
public class TransManagerEntityStore
: TransManagerBase
{
#region Overrides of TransManager
protected override void Dispose(bool disposing)
{
if (!disposing) return;
if (!IsDisposed && IsInTranx)
{
Rollback();
}
IsDisposed = true;
}
#endregion
}
As we mentioned, the in-memory implementation of the Transaction Manager factory returns the same TransManager
instance:
public class TransManagerEntityStoreFactory
: ITransFactory
{
private TransManagerEntityStore TransManager;
#region Implementation of ITransFactory
public ITransManager CreateManager()
{
if (TransManager != null) return TransManager;
TransManager = new TransManagerEntityStore
{ Locator = new RepositoryLocatorEntityStore() };
return TransManager;
}
#endregion
}
Tests
As a result of the re-factor of our service methods, our tests need some modification. Where the test was creating an instance of the service and setting up a RepositoryLocator
now we need to instantiate a Transaction Manager Factory:
[TestClass]
public class CustomerServiceTests
{
public CustomerService Service { get; set; }
public CustomerDto CustomerInstance { get; set; }
[TestInitialize()]
public void CustomerServiceTestsInitialize()
{
Service = new CustomerService
{ Factory = new TransManagerEntityStoreFactory() };
}
...
}
Chapter Summary
We have made a radical change here in our design in this chapter. The service has been holding an instance of the RepositoryLocator
, but now the service does not know any longer about repositories.
The service just requires a Factory
instance which provides Transaction Managers. In our implementation when the manager is created, the factory indicates which repository locator will be used without our services getting involved at all. This is a huge improvement in our design.
The next chapter introduces a new sort of locator (or we'd better call it manager). We need a mechanism, so services get an instance of the factory in an easy way.