Click here to Skip to main content
15,867,453 members
Articles / Database Development / NoSQL

WCF by Example - Chapter XV - RavenDB Implementation

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
3 Feb 2013CPOL9 min read 34.6K   773   26   2
Unit of Work and Repository RavenDB implementation example
This article in the WCF by example series introduces RavenDB for persistence purposes.
Previous   Next
Chapter XIV   Chapter XVI

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. The source code for the series is found at CodePlex. With this article, the series introduces RavenDB for persistence purposes.

Chapter Overview

For those new to the series, the persistence components of the eDirectory solution are designed around the unit of work and repository patterns. The TransManager is responsible for the unit of work implementation, therefore it provides access to the IRepositoryLocator, which gives access to individual entity repositories. The repositories are based on generic implementations that are created as they are needed by the repository locator. These generic repositories expose basic CRUD methods and endpoint to a IQueryble method leveraged by the persistence Linq providers

The following is an example of the code used in the server components:

C#
    public CustomerDto UpdateCustomer(CustomerDto dto)
    {
01		return ExecuteCommand(locator => UpdateCustomerCommand(locator, dto));
    }

    private CustomerDto UpdateCustomerCommand(IRepositoryLocator locator, CustomerDto dto)
    {
02		var instance = locator.GetById<customer>(dto.Id);
03		instance.Update(locator, dto);            
		return Customer_to_Dto(instance);
    } 

Line 01: Entry point for the customer service to update an existing instance

Line 02: Using the Dto.Id property, we use the Locator to resolve the customer instance

Line 03: Then we delegate to the domain class to update itself passing the locator and the Dto instances

Up to this chapter, we have seen so far two implementations of the persistence components: InMemory and NHibernate. In this chapter, we introduce a new implementation: RavenDB.

RavenDB is a document-orientated database that facilitates the persistence of objects. In NHibernate, a mapping is required between the entity classes and the database tables. But in RavenDB, there is not such requirement for additional mapping between entities and the persistence framework.

The article discusses how easy it is to implement a customized set of persistence components so the eDirectory application can save and read objects from the RavenDB document store. We use the Embedded document store implementation in this example and we will also discuss how to set it up to execute tests in the InMemory mode.

As a some sort of disclosure, I decided not to change the domain entities that we have been used so far during the series, as a result, you might find that the best design practices for a RavenDB solution are not strictly followed within this example. Nevertheless, the article should be a good example of how to get RavenDB working.

In a different line, as with NHibernate, the RavenDB implementation in the eDirectory solution is leveraged by using the Linq provider and generic repositories; in a real scenario, this approach may result too restrictive and customized repository implementations might be required in more complex scenarios.

Besides the mentioned constraints, the proposed architecture in this article could work well in many cases or at least be used as a start point for your projects.

Before

A new project was added to the eDirectory solution named eDirectory.RavenDB. Then NuGet was used to get a reference to RavenDB Embedded. It is worth noting that a post-build event was added so all the build artifacts are copied to the libs folder facilitating the usage in the client project.

RavenDB Repository

The repository implementation is very similar to the NHibernate in many aspects:

C#
public class RepositoryRavenDB<TEntity>
	: IRepository<TEntity>
{
	private readonly IDocumentSession _sessionInstance;
	private readonly string _idPrefix;

	public RepositoryRavenDB(IDocumentSession session)
	{
		_sessionInstance = session;
01		_idPrefix = GetPrefix();
	}

	private static string GetPrefix()
	{
		var typeName = typeof (TEntity).Name;
		var flag = typeName.Last().Equals('s');
02		return typeName +
			   (flag
					? "es/"
					: "s/");
	}

	#region Implementation of IRepository<TEntity>

	public TEntity Save(TEntity instance)
	{       
03		_sessionInstance.Store(instance);
		return instance;
	}

	public void Update(TEntity instance)
	{
	}

	public void Remove(TEntity instance)
	{
04		_sessionInstance.Delete(instance);
	}

	public TEntity GetById(long id)
	{
05		return _sessionInstance.Load<TEntity>(_idPrefix +  id);
	}

	public IQueryable<TEntity> FindAll()
	{
06		return _sessionInstance.Query<TEntity>();
	}

	#endregion
}

Line 01: For each repository/entity type, a prefix is generated to be used by the Load method.

Line 02: Prefixes are terminated in plural, for entity names that finish with an 'S', we need an additional check.

Line 03: When the Store method is invoked, a new instance is saved in RavenDB, there is no need for additional mapping to get this working, which is a cool feature. Also, RavenDB detects the Id property in our entities by name convention and the property is populated once the method is returned.

Line 04: The Delete method is used to remove entities from the database.

Line 05: The Load method is the optimized mechanism to retrieve entities by Id, this is where the prefix needs to be used.

Line 06: The FindAll method delegates to the RavenDB Linq provider, exactly the same way it is being done in the NHibernate implementation.

Also another aspect to notice, the Update method is not required in the RavenDB implementation.

Repository Locator

The locator implementation is very simple, it is very much the same we have seen before:

C#
public class RepositoryLocatorRavenDB
	: RepositoryLocatorBase, IResetable, IStoreInitialiser
{
	private readonly IDocumentSession _sessionInstance;

	public RepositoryLocatorRavenDB(IDocumentSession session)
	{
		_sessionInstance = session;
	}

	#region Overrides of RepositoryLocatorBase

	protected override IRepository<T> CreateRepository<T>()
	{
01              return new RepositoryRavenDB<T>(_sessionInstance);
	}

	#endregion
	#region Implementation of IResetable
02		...        
	#endregion
	#region Implementation of IStoreInitialiser
02		...
	#endregion
}

Line 01: The base locator class calls this method to obtain an instance of a repository for a given type.

Line 02: These two sections are omitted for the time being as their purpose is solely for testing reasons, we will discuss them later within this article.

Transaction Manager

Again, the implementation of this class is very similar to the NHibernate one:

C#
public class TransManagerRavenDB
	: TransManagerBase
{
	private readonly IDocumentSession _sessionInstance;

	public TransManagerRavenDB(IDocumentSession session)
	{
		_sessionInstance = session;
01		Locator = new RepositoryLocatorRavenDB(_sessionInstance);
	}

	#region Overriden Base Methods

	public override void CommitTransaction()
	{
		base.CommitTransaction();
02		_sessionInstance.SaveChanges();
	}

	public override void Rollback()
	{
		base.Rollback();
03		_sessionInstance.Advanced.Clear();
	}

	protected override void Dispose(bool disposing)
	{
	  ...
	}
	
	private void Close()
	{
	  ...
	}

	#endregion
}

Line 01: A IDocumentSession is passed in the constructor that is used for the creation of the RepositoryLocatorRavenDB.

Line 02: To commit all the changes done since the transaction manager was created, we need to invoke the SaveChanges method in the IDocumentSession.

Line 03: The Clear is used to discard all the changes if a rollback is required.

It is worth nothing that there is no specific mechanism for starting the transaction like we have in NHibernate.

Transaction Manager Factory

This component is responsible for two key roles; the creation of the IDocumentSession and the TransManager:

C#
    public class TransManagerFactoryRavenDB
        : ITransFactory
    {
        private IDocumentStore _documentStore;

        private IDocumentStore DocumentStore
        {
            get
            {
03              if (_documentStore != null) return _documentStore;
                _documentStore = InitialiseDocumentStore();
                return _documentStore;
            }
        }

        private IDocumentStore InitialiseDocumentStore()
        {
            var store = new EmbeddableDocumentStore { DataDirectory = "eDirectory" };
01          store.Initialize();
            return store;
        }

        #region Implementation of ITransFactory

        public ITransManager CreateManager()
        {
02          return new TransManagerRavenDB(DocumentStore.OpenSession());
        }

        #endregion
    }

Line 01: Creates an embeddable document store instance named "eDirectory", this would automatically generate a RavenDB instance on your deployment folder. The Initialize method is critical before any action can be invoked against the store.

Line 02: This is the factory method that returns a Transaction Manager passing a new session to its constructor. Like with NHibernate, this session instance is not available to the code besides the Locator and Repositories. This is a key aspect to get the unit of work correctly working.

Line 03: You may want to enhance this method to avoid a race condition, for example, a lazy initialization of the document store would be advisable.

How to Configure the Client UI

In order to get the WPF Client to use the RavenDB, just follow next instructions:

  • Ensure all projects build correctly, you may want to manually delete all the files in your Debug or Release folder
  • Modify the App.config in the client (eDirectory.WPF) so the SpringConfigFile is set to: file://RavenDBConfiguration.xml
  • Execute the client project
  • Create a new customer using the 'Customer with address view'

At this point, after creating the first customer instance, you will probably get an empty grid like the following:

We create the first customer:

Image 3

And after pressing the Save button, the customer grid is empty:

Image 4

If we wait 10-15 secs and we refresh pressing the command button, the customer record appears:

Image 5

What we have replicated here is what is called a stale index state in RavenDB, it seems that when the first record is created in a RavenDB store instance for the first time, the index takes a long time to be updated and the query does not return the record that was just created. It is worth noting that the refresh is done in a different request than the one for the save action, in this case.

If you close the application and restart it once more, you see this issue does not happen again, even if an address instance is created for the first time. You may want to have a look at the official documentation regarding this 'feature'. We will discuss it again in the Test section and see how we can avoid this sort of situation.

Other Aspects

There is one additional change required in the Domain entities, we need to tag the customer and address entities with the JsonObject attribute so the NewtonSoft library can serialize our objects correctly. There is also a change in our Linq queries where the 'Equals' must be replaced by '=='; the RavenDB Linq struggles if the 'Equals' method is used.

Testing with RavenDB

If we want to use RavenDB when running our tests, there is a key aspect that we need to consider:

  • Tear down the database between test executions

InMemory

But before we discuss how to tear down the document store, there is an interesting feature worth mentioning: RavenDb can be configured to run in-memory, hence performance is improved without losing any behavior/functionality. As a result, I modified the TransManagerFactoryRavenDB so the tests can be run in memory:

C#
    public class TransManagerFactoryRavenDB
        : ITransFactory
    {
		...
01      private bool IsSetForTesting { get; set; }

        private IDocumentStore InitialiseDocumentStore()
        {
            var store =
                IsSetForTesting
02                  ? new EmbeddableDocumentStore
                          {
                              RunInMemory = true
                          }
                    : new EmbeddableDocumentStore {DataDirectory = "eDirectory"};

            store.Initialize();
            return store;
        }
		...
    }

Line 01: A new flag can be set to indicate that an InMemory instance must be created.

Line 02: If the flag is set, then the RavenDB instance is created in memory.

The Spring configuration file used for the tests sets the IsSetForTesting property so when the tests are executed an InMemory RavenDB instance is used instead. You may want to look at the TestRavenDBConfiguration file located at the test project for more details.

Tear Down

I took the idea of using an index from Vladimir Petrov's blog, so when entity instances are created during the tests, they can be deleted using a customized index. RavenDB indexes are created in code by inheriting from the AbstractIndexCreationTask class:

C#
    public class AllDocumentsById : AbstractIndexCreationTask
    {
        public const string Name = "AllDocumentsById";

        #region Overrides of AbstractIndexCreationTask

        public override IndexDefinition CreateIndexDefinition()
        {
            return new IndexDefinition
            {
01              Name = AllDocumentsById.Name,
02              Map = "from doc in docs let DocId = 
                       doc[\"@metadata\"][\"@id\"] select new {DocId};"                
            };            
        }
C#
    #endregion
}

Line 01: We give the index a well known name, we'll need it when we have to delete the entities.

Line 02: Declares the index mapping so it creates an entry for any doc created in the store.

In order to create the index, the test project has some extra functionality to determine if the RepositoryLocator implements the IStoreInitialiser interface, if so, it invokes the ConfigureStore method. The RepositoryLocatorRavenDB does so:

C#
    public class RepositoryLocatorRavenDB
01      : RepositoryLocatorBase, IResetable, IStoreInitialiser
    {
        ...

        #region Implementation of IStoreInitialiser

        public void ConfigureStore()
        {
            var documentStore = _sessionInstance.Advanced.DocumentStore 
                                as EmbeddableDocumentStore;
            if (documentStore == null) return;
02          IndexCreation.CreateIndexes(typeof(AllDocumentsById).Assembly, documentStore);
        }

        #endregion
    }

So how do the tests use the index?. Well, it happens that the RepositoryLocatorRavenDB also implements the IResetable interface:

C#
    public class RepositoryLocatorRavenDB
        : RepositoryLocatorBase, IResetable, IStoreInitialiser
    {
        ...

        #region Implementation of IResetable

        public void Reset()
        {
            var documentStore = _sessionInstance.Advanced.DocumentStore 
                                as EmbeddableDocumentStore;
            if (documentStore == null) return;
01          while (documentStore.DatabaseCommands.GetStatistics().StaleIndexes.Length != 0)
            {
                Thread.Sleep(10);
            }
02          _sessionInstance.Advanced.DatabaseCommands.DeleteByIndex
                             (AllDocumentsById.Name, new IndexQuery());
        }

        #endregion

        ...
    }

Line 01: I will explain in a second.

Line 02: It uses the DeleteByIndex passing the name of our customized index.

There is a problem with the above index: when the test execution only creates one entity instance, the AllDocumentsById index is stale. If more than one instance is created, it seems that the issue is resolved. As a result, line 01 is used to ensure that the index is not stale when the deletion method is invoked.

Set Configuration to Use RavenDB

In order to use the RavenDB implementation when running the tests, ensure that the App.config is configured so the SpringConfigFile appSetting is set to TestRavenDBConfiguration.xml:

Conclusion

It has been very easy to adapt RavenDB to the eDirectory solution, in fact, it just took a couple hours to get it working, getting the tests working took a little more time as a result of the stale index issue. Not having to create a mapping between the domain entities and the document store makes the difference.

As a note: if best design practices are to be followed when designing the domain for a RavenDB store, you need to move away from the traditional relational approach, documents should store as much information as possible; what does it mean? in a nutshell, in domain terms, your documents become aggregates and duplication is not a 'sin'. It is a radical change in the way of thinking if you have been using something like NHibernate or any other ORM framework.

Other Resource Links

History

  • 4th February, 2013: Initial version

License

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


Written By
Software Developer (Senior)
Ireland Ireland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThank you for your keep supporting Pin
caosnight18-Dec-12 15:15
caosnight18-Dec-12 15:15 
AnswerRe: Thank you for your keep supporting Pin
Enrique Albert19-Dec-12 5:19
Enrique Albert19-Dec-12 5:19 

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

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