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

In memory data access, a methodology to simplify agile development

By , 25 Nov 2008
 

InMemoryDAL1.png

Introduction

Object oriented design concepts and agile development principles state that data access is a detail and not part of the domain of the application model. This means that the architecture of applications should ensure that the implementation of object persistence and retrieval is hidden from the domain of the application model.

With the complete decoupling of the data access layer comes the possibility to test the business model in isolation by providing a data access layer that mimics a real one but doesn't carry the entire heavy infrastructure needed by an RDBMS and the code to access it.

A possible way to simulate a data access layer is to use mock objects. This article proposed a slightly different approach that consists of implementing a simple "in memory" data access and persistence mechanism. This approach allows deferring the development of the database details, typically involving implementing table structures, SQL generation scripts, SQL queries, and other configurations.

But, how do we obtain a complete separation of the data access layer?

Dependency Inversion

The Dependency Inversion Principle, as Robert C. Martin stated in "Agile Principles, Patterns, and Practices in C#", says that:

  • High level modules should not depend upon low level modules. Both should depend upon abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

Dependency inversion separates concerns, and enables a top-down developing process, building high level modules before low level modules.

.NET Implementation

Applied to the .NET world, dependency inversion implies that:

  • The DAL (data access layer) should be in a separate assembly.
  • The business logic layer and the DAL should depend upon common interfaces defined in a third assembly.
  • The DAL shouldn't have the responsibility of instantiating objects, the business logic layer should.

The diagram at the beginning of the article shows the architecture of this solution for layer separation. We have four assemblies:

  • Entities: Holds the definition for the entities in the application model. It doesn't reference any of the other assemblies.
  • Logic: Contains the application business rules (object creation included). It references Entities and Interfaces.
  • Interfaces: Contains contracts for low level modules, data access, in our case. It references Entities.
  • Data Access: Provides services for persisting and retrieving objects. It references Entities and Interfaces.

The code included in this article provides a Test project.

Entities

The sample provided is about business order management. Here is the class diagram that represents the entities in the sample:

InMemoryDAL2.png

Interfaces

The separation we are looking for can be obtained by applying the Abstract Factory and Facade patterns through the definition of a group of interfaces, one for each entity class, wrapped together by an interface adding connection and transaction management. As in the following class diagram:

InMemoryDAL3.png

The IOrdersSession interface will orchestrate the data access functionality provided by all the single entity data access classes.

public interface IOrdersSession: IDisposable
{
    IDbTransaction BeginTransaction();

    ICustomerDAL DbCustomer { get; }
    void Save(Customer customer);
    void Delete(Customer customer);

    IOrderDAL DbOrder { get; }
    void Save(Order order);
    void Delete(Order order);

    IOrderItemDAL DbOrderItem { get; }
    void Save(OrderItem orderItem);
    void Delete(OrderItem orderItem);

    IProductDAL DbProduct { get; }
    void Save(Product product);
    void Delete(Product product);
}

This interface derives from IDisposable to ensure that any database connection is closed (and transaction rolled back, if still present) at object disposal, and to enable the use of the using statement.

This is the code for the Order entity data access interface:

/// method to call to instantiate objects
public delegate Order OrderDataAdapterHandler(Guid id, Guid idCustomer, 
                                              DateTime orderDate);

/// <summary>
/// Order data access interface
/// </summary>
public interface IOrderDAL
{
    List<Order> Search(Guid id, Guid idCustomer, DateTime? 
                             orderDate, OrderDataAdapterHandler orderDataAdapter);
}

A callback mechanism is used to instantiate objects off the data access layer. A delegate, OrderDataAdapterHandler, is passed as a parameter in the methods that retrieve objects (the search method, in our example). A blog post by Scott Stewart is available for detail info about this alternative to using DTOs to retrieve data from the DAL.

Logic

The business logic classes shouldn't have a direct reference to the DAL classes. Instead, they will reference the IOrdersSession interface, and a Dependency Injection (DI) mechanism will provide the actual object implementing it.

Dependency injection can be provided by a DI framework (such as NInject or Unity) or, as in the sample code here quoted, by a simple method that instantiates an object implementing IOrdersSession. In this case, this is done in a conditional way: business logic classes decorated with the [InMemoryDAL] attribute will be injected with the InMemory access classes.

public class InMemoryDALAttribute : System.Attribute { }

public class GBDipendencyInjection
{
    public static IOrderdSession GetSession()
    {
        // get call stack
        StackTrace stackTrace = new StackTrace();

        // get calling class type
        Type tipo = stackTrace.GetFrame(1).GetMethod().DeclaringType;

        object[] attributes;

        attributes = tipo.GetCustomAttributes(typeof(InMemoryDALAttribute), false);

        if (attributes.Length == 1)
        {
            return new InMemoryOrderSession();
        }
        else
        {
            return new SqlServerOrderSession();
        }
    }
}

Methods that need to retrieve data will instantiate IOrderSession in a using statement, and pass as a parameter a factory method with a signature corresponding to the one defined in the entity DAL interface.

public List<Order> GetByCustomerId(Guid customerId)
{
    List<Order> lis;
    using (IOrdersSession sess = GBDipendencyInjection.GetSession())
    {
        lis = sess.DbOrders.Search(Guid.Empty, customerId, null, CreateOrder);
    }
    return lis;
}

In the OrderLogic class, CreateOrder is the factory method that instantiates objects of type Order.

public Order CreateOrder(Guid id, Guid idCustomer, DateTime orderDate)
{
    Order order = new Order();
    CustomerLogic costumerLogic = new CustomerLogic();
    Customer customer = costumerLogic.GetById(idCustomer);
    if (customer == null)
    {
        throw new ArgumentException("Customer not found.");
    }
    order.Customer = customer;
    order.OrderDate = orderDate;
    return order;
}

In Memory Data Access

A singleton class contains a generic list of objects whose storage is simulated. Since it is a singleton, it maintains the list across threads, thus it can be still used when developing the interface (even a web interface) and defer database development in the last stages of development. A cascading find method is applied to search the list.

public sealed class OrderInMemoryDAL : IOrderDAL
{
    internal List<Order> OrderList;

    static readonly OrderInMemoryDAL _istance = new OrderInMemoryDAL();

    private OrderInMemoryDAL()
    {
        OrderList = new List<Order>();
    }

    internal static OrderInMemoryDAL Istance
    {
        get { return _istance; }
    }

    #region IOrderDAL Members

    public List<Order> Search(Guid id, Guid idCustomer, 
           DateTime? orderDate, OrderDataAdapterHandler orderDataAdapter)
    {
        List<Order> lis = OrderList;
         
        if (id != Guid.Empty)
          { lis = lis.FindAll(delegate(Order entity) { return entity.Id == id; }); }
        if (idCustomer != Guid.Empty)
          { lis = lis.FindAll(delegate(Order entity) 
            { return entity.Costumer.Id == idCustomer; }); }
        if (orderDate.HasValue)
          { lis = lis.FindAll(delegate(Order entity)
            { return entity.OrderDate == orderDate; }); }

        return lis;
    }

    #endregion
}

InMemoryOrderSession implements IOrdersSession, managing saving and deleting simply by adding and removing from the generic lists stored in the singleton objects.

public class InMemoryOrderSession: IOrdersSession
{
...
     public IOrderDAL DbOrders
     {
        get { return OrderInMemoryDAL.Istance; }
     }

     public void Save(Order order)
     {
         Delete(order);
         OrderInMemoryDAL.Istance.OrderList.Add(order);
     }

    public void Delete(Order order)
    {
        OrderInMemoryDAL.Istance.OrderList.RemoveAll(delegate(Order _entity) 
                         { return _entity.Id == order.Id; });
    }

...
}

This approach enables to develop the model and even the user interface without worrying about the database details. This lowers the cost of changes to the model since it is not required to change database structures and SQL scripts and queries.

Another advantage is being able to test the business logic in isolation from the database access code.

The last stage of the development of our application will be writing a class implementing IOrdersSession that makes use of an RDBMS through an access library, like ADO.NET, providing connection and transaction services. This class will be a facade for all the single classes that manage CRUD operations on the RDBMS for model entities persistence.

The next step will be removing the [InMemoryDAL] attribute from the business logic class. Now, the simple DI mechanism will inject the "real" DAL in the application. Running Unit Tests on the business logic classes again, the DAL will be tested.

In the following article, Building an Agile SQL Data Access Layer, I focus on the development of the code needed for SSL Server data access, with sample code extending the one attached to this article.

License

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

About the Author

Giorgio Bozio
Italy Italy
Member
Software architect. At present working on C# development, with mainly Asp.net Ajax and MVC user inteface. Particularly interested in OOP, test driven, agile development.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionThe Best [modified]memberMember 45586617 Sep '12 - 4:16 
Thank you. Is more examples without use of Microsoft.VisualStudio.QualityTools.UnitTestFramework?
Rand


modified 20 Sep '12 - 6:09.

GeneralMolto Bene GiorgomemberRS29013 Feb '09 - 15:06 
Very easy to understand
GeneralVery good articlememberDonsw16 Jan '09 - 6:59 
Congrats, I will now read the followup article. goo basis for the next one.
GeneralThank you for article.memberSpLove27 Nov '08 - 4:58 
But some moments weren't clear for me.
First: I think that your GBDependencyInjection is very strange object. Because its name likes with Inversion of Control and Dependency Injection, but it doesn't implement both.
Second: Could you tell more about why delegates are received into DAL methods, but code doesn't use them?
GeneralRe: Thank you for article. [modified]memberGiorgio Bozio27 Nov '08 - 5:21 
Hi SpLove,
GBDependencyInjection is in a separate "Helper" assembly, it serves me the purpose to not have to reference directly the implementation of the dal interfaces from the logic classes, in this sense it's a dependency injection mechanism.
The delegates are used by the "real" DAL implementations to return instances from the db. This is not necessary if objects are already in memory like in this "In memory" db. I should be able to explain that in future article.
I hope I was able to explain myself, let me know if something is still not clear (English is not my native language)
Thank you,
Giorgio
 
modified on Thursday, November 27, 2008 12:09 PM

GeneralRe: Thank you for article.memberSpLove27 Nov '08 - 21:20 
Hi Giorgio Bozio.
Your code strongly ties to your GBDependencyInjection object. It isn't dependency injection mechanism, because such mechanism assumes that injection are mixed through constructor, setter and interface. In your case it isn't true. Wikipedia about Dependency Injection[^].
I will wait your next article.
P.S.: English isn't my native language too.
Thank you, SpLove.
GeneralRe: Thank you for article.memberGiorgio Bozio27 Nov '08 - 23:44 
The decoupling that I obtain with my mechanism is enough for my needs. It fulfills my requirements, being able to switch concrete implementation of the data access interfaces during the development of the software. In fact I usually wrap the conditional part of the factory method that instantiates the concrete implementation of the dal session interface in a #if DEBUG region. I tried using NInject to obtain th same result, but it had a bug in the conditional injection and I had to cook my own (very simplified) solution. Making it more complex would have smelled of needless complexity (besides not having the time...).
Thank you,
Giorgio
GeneralRe: Thank you for article.memberSpLove28 Nov '08 - 5:37 
Which bug do you say about? Simply I use Unity Application Block for DI/IoC and don't imagine your problem.
In my work I develop separate class where I configurate dependences with help of configuration file. Such solution make bisiness logic more transparent.
Thank you,
SpLove.
GeneralRe: Thank you for article.memberGiorgio Bozio28 Nov '08 - 5:44 
http://groups.google.com/group/ninject/browse_thread/thread/20127d291162deb7
 
tried different types of conditional injection on NInject and I got the same error. I would like to take a look at Unity too, but just browsing around the docs it seemed less clean and simple then NInject. Could you point me to some docs that explain how to do conditional injection with Unity based on attribute or class name of the class that asks for injection?
thanks,
Giorgio
GeneralRe: Thank you for article.memberO1eg Smirnov30 Nov '08 - 9:35 
Go to My Article[^]

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 25 Nov 2008
Article Copyright 2008 by Giorgio Bozio
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid