Click here to Skip to main content
Click here to Skip to main content

WCF by Example - Chapter IV - Transaction Manager

, 24 Oct 2012
Rate this:
Please Sign up or sign in to vote.
Generic transaction and exception manager for server side services
Previous Next
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;
    // free managed resources
    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

    /// Need to override this method because
    /// we want to keep the transmanager in the
    /// Entity Store implementation as instances
    /// are stored in memory
    protected override void Dispose(bool disposing)
    {
        if (!disposing) return;
        // free managed resources
        if (!IsDisposed && IsInTranx)
        {
            Rollback();
        }
        //Locator = null;
        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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Enrique Albert
Software Developer (Senior)
Ireland Ireland
No Biography provided
Follow on   Twitter

Comments and Discussions

 
QuestionSpecific Repositories Pinmemberabdel00003-Feb-13 3:17 
GeneralMy vote of 1 Pinmembernvskhan25-Oct-12 23:36 
GeneralRe: My vote of 1 PinmemberEnrique Albert25-Oct-12 23:39 
Questionu6u6 Pinmemberswamilaxmi7-Jun-12 5:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140826.1 | Last Updated 24 Oct 2012
Article Copyright 2010 by Enrique Albert
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid