Command Query Separation (CQS) - A simple but powerful pattern

Posted by: Tim Sommer , on 9/30/2018, in Category Patterns & Practices
Views: 7115
Abstract: This tutorial demonstrates how to design software using the Command Query Separation (CQS) pattern. We will create a console application and will use C#, Entity Framework, Autofac and Log4Net for our example.

When you choose an architecture, a lot of factors come into play.

These factors, let’s call them architectural goals, include (but are not limited to) :

  • the complexity of the system
  • ease of use
  • simplicity
  • scalability
  • auditability
  • performance and
  • reusability

In this tutorial, I'll focus on how to design software using the Command Query Separation (CQS) pattern. This will allow us to tackle a lot of these goals; and to keep code readable, structured, reusable and less error prone.

This tutorial is from the DotNetCurry(DNC) Magazine with in-depth tutorials and best practices in .NET and JavaScript. This magazine is aimed at Developers, Architects and Technical Managers and covers C#, Patterns, .NET Core, MVC, Azure, DevOps, ALM, TypeScript, Angular, React, and more. Subscribe to this magazine for FREE and receive all previous, current and upcoming editions, right in your Inbox. No Spam Policy.

Command Query Separation (CQS)

Bertrand Meyer devised CQS, or Command Query Separation, as part of his work on the Eiffel programming language. In case it's the first time you have ever heard about this term, I encourage you to read the formal definition of CQS on Martin Fowler’s blog.

The pattern itself is pretty simple. It all comes down to two principles.

Every method should either be a command that performs an action, or a query that returns data to the caller, but never both.

Methods should return a value only if they create no side effects.

Simply put: a query should never mutate state, while a command can mutate state but should never have a return value.

So, what does that really mean? In its most basic form, you separate queries from commands by function:

interface IBookService
{
    void Edit(string name);
    Book GetById(long id);
    IEnumerable<Book> GetAllBooks();
}

The Edit function does not return a value, its only job is to edit a book by name. We are certain that this function will have side effects as it will mutate data and does not have a return value. So, this is a command.

The Get methods have no side effects (they do not mutate the state). These actions are responsible for fetching the data, but not changing it. These are queries.

In practice, you will most likely separate queries from commands by object. Which means you will have a GetBookByIdQuery, a GetAllBooksQuery and an EditBookCommand. Either a QueryHandler or a CommandHandler will typically handle these objects .

In this article, I will focus mainly on this approach.

So, the CQS pattern is fairly simple. If you follow it, your code will get more structured, more readable and more maintainable. It encourages you to write better code, that follows SOLID principles and allows the introduction of (better and more) Unit Testing.

Building an application using the CQS pattern

Now that we have a basic understanding of the CQS pattern, let’s see how we would implement that into an application.

As we move from pattern to application, I will always separate Commands and Queries by an object, which if you take a high-level overview of, will look something like this:

CQS-pattern-high-level

Figure 1: A high-level overview of an application built using the CQS pattern

Now, this is a bit more complex than the CQS pattern itself, but it should still be straightforward. Advantages of designing your software like this are:

  • You'll get a decent level of separation of concerns (SoC). It will motivate you to work towards more SOLID code and write more unit tests.
  • Code for reading and mutating data is completely separated. This logical separation (i.e. in code) automatically provides for a better structure on the method or class level. You can also implement (and enforce) performance optimizations more easily, which is especially valuable on the “read” side, as any modern application has more “reads” than “writes”.
  • A physical separation (i.e. different assemblies or projects that can be deployed on different servers) is possible and allows you to scale up on both “read” and “write” sides. But, this is fairly complex and leans more to CQRS (which we’ll take a quick look at in the next section). But if you know what you are doing, it is possible.
  • The CQS pattern is not that complex. Implementing it into your application is therefore also fairly straightforward.

While the advantages far outweigh the disadvantages, I encountered some:

  • Separating Queries from Commands by the object can lead to a lot of classes. In a complex application, the number of Queries and Commands objects can get big, fast.
  • If you rigidly follow the pattern, you could get stuck with unnecessary server calls. See “Some thoughts” in the “Building a CQS architecture” section for an example.

CQS vs CQRS?

Before we go further, I should state the difference between CQS (Command Query Separation) and CQRS (Command Query Responsibility Segregation). Even though they are very much related, they are not the same thing.

You could say that CQS works more on the method or class level, whilst CQRS takes the concepts of CQS and takes it to the application level. With CQS you separate methods for querying from or writing to a model. CQRS is similar, except it's more of a path through your system. A Query request takes a separate path from a Command.

Explaining CQRS in detail far outreaches the boundaries of this article as it is a much more complex subject.

But, I will leave you with this: We can say that the biggest difference between the two patterns is that, while not a requirement, CQRS allows for separate data stores and models (data or ORM) for Commands and Queries. The data between data stores is usually synchronized asynchronously using a service bus.

An architecture that is built upon the CQS pattern will follow the same logical separation, but Queries and Commands will be executed on the same datastore and share the same model. It should be noted though that you can use CQS in an application that does not have a data store at all.

CQRS applications are much more complex than CQS applications. The latter is much simpler, both to build and to maintain.

Building an architecture using the CQS pattern

I’ve written a sample application alongside this article in which you will see that implementing the CQS pattern within a basic architecture can be done easily. But, before we dive into the code, I'll give you a run-down of all the things we will be using.

The building blocks listed here are concepts I often use when designing software. But they are in no way required for building an application that follows the CQS pattern.

I’ve seen lots of different implementations online, which are all good! But I use the concepts listed below, which works great for me.

  • The Query, QueryResult, Command and CommandResult objects: These are the actual classes used to make a request to the data store in the sample application. GetBookByIdQuery, GetAllBooksQuery, UpdateBookCommand and DeleteBookCommand could all be actions you’d find in a CQS architecture.
  • The Dispatcher object: This is where the chain starts. You tell the Dispatcher to dispatch a Query or a Command object. The Dispatcher then passes your request to the correct Handler.
  • Abstract base class for both Command- and QueryHandlers: These objects allow you to manage code that should be in all handlers. Functionalities like logging, authorization, exception handling, etc. can be implemented here.
  • The Handlers themselves: The classes that handle your request and either retrieves data or mutates data. There should always be one Handler mapping to one action. So, you would have a GetBookByIdQueryHandler, GetAllBooksQueryHandler, UpdateBookCommandHandler and DeleteBookCommandHandler that handle the Queries and Commands above.
  • IoC: I use Autofac as Inversion of Control Container to glue everything together.

The execution of a Query in the sample application will go like this:

CQS-queryhandler-flow-sample

Figure 2: A representation of the execution of a Query as presented in the sample application.

For a Command, the execution will be almost the same:

CQS-commandhandler-flow-sample

Figure 3: A representation of the execution of a Command as presented in the sample application

Some thoughts

In the sample application you'll notice I kept things as simple as possible.

A CommandHandler in a production application will do far more than just mutate data in your data store. There will be more logging, you'll have different layers of authorization, have Assemblers (a building block that maps Data Transfer Objects to Entity objects), have a Unit of Work (a building block that handles transactions, scopes and different contexts), perform data validation, maybe some data audits, etc.

But, in the sample, I kept the CommandHandler simple so you can grasp the key concept.

The CQS pattern states one thing clearly: a Command can only mutate state and cannot have a return value.

As I implement this pattern into an architecture, I allow (or even enforce) Commands to have a return value. The main reason to follow the CQS pattern is to keep code readable and reusable.

Your code gets more trustworthy as it ensures certain code will not cause unexpected side effects. A Query that changes state would be a clear and serious violation and I would never allow that.

But, for Commands I make an exception in two cases.

First, say you create a new entity. Your client now needs to know the ID of that newly created object.

Following CQS strictly, you now have two options: you could either create the ID on the client side (such as a GUID or a HiLo implementation), or the client could request the ID from the system using a Query before or after creating the object.

This is a silly constraint. I prefer to just return the ID when the create Command is executed. This makes things much less complex and allows for less server calls. In this case (and only this case), I allow the Command to return an ID.

The second case is to get details about execution. A Command should always return information about the mutation carried out by it. Did any exceptions occur? Were there any validation errors? Were there warnings or notifications the user needs to know about?

I generally include this information in my result, so my client is able to handle program flow based on the execution details.

It is not present in the sample application, but a CommandResult in a production application would basically look like this, including the Command Execution information:

public abstract class Result : IResult
{
    protected Result()
    {
       ValidationBag = new ValidationBag();
    }
    public ValidationBag ValidationBag { get; set; }
    public Exception Exception { get; set; }
    public bool Success { get; set; }
}

This topic is up for debate, so probably not everyone will agree with me on this.

But CQS is a pattern, a guideline. If it makes sense to deviate a bit, do it. Especially if it makes your code clearer, more readable and faster in execution! And if you don't like it, you can easily exclude it from the sample application.

The execution of a Query in a production environment would go something like this:

CQS-queryhandler-flow-production

Figure 4: An example of the execution of a Query in a production environment.

A Command will have way more checks causing its execution to be a bit more complex:

CQS-commandhandler-flow-production

Figure 5: An example of a Command flow in a production environment.

Ok, you should now have a basic understanding of the CQS pattern, how we use it, when do we implement it in a Software Design and how it is different from CQRS.

Let’s dive into the code!

Sample Application using CQS architecture

I’ve written a Console Application to explain the key concepts of the CQS architecture I built. It is not as fancy as a web application, but it will allow us to focus more on the key concepts.

You will also find that you can easily apply (or copy) these techniques and implement them in your own (web) application.

You can find the sample code on my github account. This article is not intended as a how-to guide, as this would require a huge amount of code samples. Instead I'll be focusing on the core concepts so you can get a better understanding on how the sample application works, and how CQS fits in the story.

A database of books

The sample application will perform basic create, read and update actions on a book database. We will be working with Entity Framework as the ORM, Autofac as the IOC Container and Log4Net as the logging framework. If you are not familiar with these topics you can find more information on EF here, and on Autofac here.

The source code can be found on the Github project under the folder 'Cqs.SampleApp.Console'. If you open the program.cs file, you will see the following:

private static readonly ILog _Log = LogManager.GetLogger(typeof(Program).Name);
private static void Main(string[] args)
{
     _Log.Info("Bootsrapping application..");
     var _container = Bootstrapper.Bootstrap();
     WithoutCqs(_container);
     WithCqs(_container);
     System.Console.ReadLine();
}

To allow you to examine and fully understand the CQS architecture, I’ve kept “the old way of doing things” available in the sample application. This “old way” (method WithoutCqs) uses the EF DbContext to simply get and update data.

But first, let’s take a look at the static Bootstrapper class which configures the IoC container:

public static class Bootstrapper
{
    public static IAutofacContainer Bootstrap()
    {
        var _container = new AutofacContainer(builder =>
        {
            builder.RegisterModule<ApplicationModule>();
            builder.RegisterModule<CqsModule>();
        });

        return _container;
    }

    public class ApplicationModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            //register the EF DbContext
            builder.RegisterType<ApplicationDbContext>()
                   .AsSelf()
                   .WithParameter("connectionString", "BooksContext")
                   .InstancePerLifetimeScope();
        }
    }

    public class CqsModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            var _assembly = typeof(CqsModule).Assembly;

            //register the QueryDispatcher and CommandDispatcher
            builder.RegisterType<QueryDispatcher>().As<IQueryDispatcher>();
            builder.RegisterType<CommandDispatcher>().As<ICommandDispatcher>();

            //Register all QHandlers and all CHandlers found in this assembly
            builder.RegisterAssemblyTypes(_assembly)
                   .AsClosedTypesOf(typeof(IQueryHandler<,>));                   
            builder.RegisterAssemblyTypes(_assembly)
                   .AsClosedTypesOf(typeof(ICommandHandler<,>));
        }
    }

}

Here we basically glue all the components together as we register the correct implementations in our Autofac IoC Container.

We will be working with the Book class as our only Entity object, which is a representation of a table in the database:

public class Book : DbBaseModel
{
    public string Title { get; set; }
    public string Authors { get; set; }
    public DateTime DatePublished { get; set; }
    public bool InMyPossession { get; set; }
}

The “old way”, WithoutCqs method looks like this:

private static void WithoutCqs(IAutofacContainer container)
{
    //resolve context
    var _context = container.Resolve<ApplicationDbContext>();
    
    //save some books if there are none in the database
    if (!_context.Books.Any())
    {
         _context.Books.Add(new Book()
         {
            Authors = "Andrew Hunt, David Thomas",
            Title = "The Pragmatic Programmer",
            Bought = true,
            DatePublished = new DateTime(1999, 10, 20),
         });
            
         _context.Books.Add(new Book()
         {
            Authors = "Robert C. Martin",
            Title = "The Clean Coder: A Code of Conduct for Professional Programmers",
            Bought = false,
            DatePublished = new DateTime(2011, 05, 13),
         });

         _context.SaveChanges();

         _Log.Info("Books saved..");
    }

    _Log.Info("Retrieving all books..");

    foreach (var _book in _context.Books)
    {
        _Log.InfoFormat("Title: {0}, Authors: {1}, Bought: {2}", _book.Title, _book.Author, _book.Bought);
    }
}

As you can, there’s nothing complex here. We get the Database context from the IoC Container, add some data into the database, and then output the result to the console.

Our output looks like this.

console-output-without-cqs

Figure 6: Console output of the ‘WithoutCQS’ sample

If you are wondering, Log4Net’s ColoredConsoleAppender is configured to log to the console. You can take a look at its configuration in the sample code.

Moving towards CQS - the Query part

The read part of any CQS architecture is easier than the write part. So, it's a good idea to start there.

To get all the books from the database we will need a Query object:

public class GetBooksQuery : Query
{
   public bool ShowOnlyInPossession { get; set; }
    
    //other filters here
}

public class GetBooksQueryResult : IResult
{
    public IEnumerable<Book> Books { get; set; }
}

The GetBooksQuery has a property called ShowOnlyInPossession. This allows us to fetch all the books, or fetch only those that are in our possession. You can add other filters (all books published after the year 2000 for example) in the GetBooksQuery class. The GetBooksQueryResult class holds the result that will be returned from our handler, containing a list of books stored in the database.

I have not implemented an Assembler (the mapping of Entity objects to DTO objects) to simplify the solution. It is worth noting though that it is never a good idea to directly return Entity objects. You always want to use DTO (Data Transfer Objects) that correspond to a particular view for your UI.

A QueryHandler will handle the GetBooksQuery. There will always be a one-on-one mapping between your Handlers and your Queries. In this case, there will be a GetBooksQueryHandler which will handle the requests and fetch the books from the database.

As I've explained before, we use a Dispatcher to "route" our Query objects. The Dispatcher takes our request and passes it along. Meaning that our client application always communicates with the dispatcher, not the handlers themselves.

The dispatcher will then find and call the correct handler.

The QueryDispatcher interface and implementation looks like this:

/// <summary>
/// Dispatches a query and invokes the corresponding handler
/// </summary>
public interface IQueryDispatcher
{
    /// <summary>
    /// Dispatches a query and retrieves a query result
    /// </summary>
    /// <typeparam name="TParameter">Request to execute type</typeparam>
    /// <typeparam name="TResult">Request Result to get back type</typeparam>
    /// <param name="query">Request to execute</param>
    /// <returns>Request Result to get back</returns>
    TResult Dispatch<TParameter, TResult>(TParameter query)
        where TParameter : IQuery
        where TResult : IResult;
}

public class QueryDispatcher : IQueryDispatcher
{
    private readonly IComponentContext _Context;

    public QueryDispatcher(IComponentContext context)
    {
        _Context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public TResult Dispatch<TParameter, TResult>(TParameter query)
        where TParameter : IQuery
        where TResult : IResult
    {
        //Look up the correct QueryHandler in our IoC container and invoke the retrieve method
       
        var _handler = _Context.Resolve<IQueryHandler<TParameter, TResult>>();
        return _handler.Retrieve(query);
    }
}

Again, no rocket science here. The dispatcher looks for the (only) matching IQueryHandler implementation and invokes its Retrieve() method.

Let's look at the QueryHandler Base class. This base class is not a requirement, but I use it to do general things for all QueryHandlers, like logging, authorization, etc.

public abstract class QueryHandler<TParameter, TResult> : IQueryHandler<TParameter, TResult>
    where TResult : IResult, new()
    where TParameter : IQuery, new()
{
    protected readonly ILog Log;
    protected ApplicationDbContext ApplicationDbContext;

    protected QueryHandler(ApplicationDbContext applicationDbContext)
    {
        ApplicationDbContext = applicationDbContext;
        Log = LogManager.GetLogger(GetType().FullName);
    }

    public TResult Retrieve(TParameter query)
    {
        var _stopWatch = new Stopwatch();
        _stopWatch.Start();

        TResult _queryResult;

        try
        {
            //do authorization and validation

            //handle the query request
            _queryResult = Handle(query);
            
        }
        catch (Exception _exception)
        {
            Log.ErrorFormat("Error in {0} queryHandler. Message: {1} \n Stacktrace: {2}", typeof(TParameter).Name, _exception.Message, _exception.StackTrace);
//implement your Exception handling here, for example log to database, send mail, etc
            throw;
        }
        finally
        {
            _stopWatch.Stop();
            Log.DebugFormat("Response for query {0} served (elapsed time: {1} msec)", typeof(TParameter).Name, _stopWatch.ElapsedMilliseconds);
        }


        return _queryResult;
    }

    /// <summary>
    /// The actual Handle method that will be implemented in the sub class
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    protected abstract TResult Handle(TParameter request);    
    
}

This abstract class has some generic constraints. The TParameter must implement the IQuery interface and the TResult has to implement the IResult interface. Both generic parameters must have a public parameterless constructor.

There is a Stopwatch that keeps track of the execution time of the invocation of every QueryHandler. It handles errors (by logging them, but you can do other things here as well) and you could implement authorization, validation, Assemblers, etc.

Eventually our Query is handled by this line of code _queryResult = Handle(query);. The abstract Handle method will be implemented in the sub class and its return object will be returned by the super class.

So that's it! Our architecture is now ready to begin handling Queries!

Let's write our GetBooksQueryHandler and call it using our console application!

public class GetBooksQueryHandler : QueryHandler<GetBooksQuery, GetBooksQueryResult>
{
    public GetBooksQueryHandler(ApplicationDbContext applicationDbContext) 
        : base(applicationDbContext)
    {
    }

    protected override GetBooksQueryResult Handle(GetBooksQuery request)
    {
        var _result = new GetBooksQueryResult();

        var _bookQuery = ApplicationDbContext.Books.AsQueryable();

        if (request.ShowOnlyInPossession)
        {
            _bookQuery = _bookQuery.Where(c => c.InMyPossession);
        }

        _result.Books = _bookQuery.ToList();

        return _result;
    }
}

In the program.cs file, we can now retrieve all the books from the data store. We ask our IoC container for the implementation of the QueryDispatcher. We dispatch the GetBooksQuery and log the returned books to the console.

var _queryDispatcher = container.Resolve<IQueryDispatcher>();
var _reponse = _queryDispatcher.Dispatch<GetBooksQuery, GetBooksQueryResult>(new GetBooksQuery());

foreach (var _book in _reponse.Books)
{
    _Log.InfoFormat("Title: {0}, Authors: {1}, Bought: {2}", _book.Title, _book.Authors, _book.Bought);
}

This will (having Log4Net configured to DEBUG) output the following:

console-output-with-cqs-queryhandler

Figure 7: Console output of the GetBooksQuery

Ok, that's all for the Query part!

Any type of architecture will result in more code, but I hope you realize that integrating the Command Query Separation pattern does a good job on keepings things simple and well organized.

The QueryHandler super class allows for a lot of tweaking, all in one place. If you don't like the use of super classes, you can even use decorators to introduce new behaviour. But that's too much ground to cover in one article.

Let's move on to the Command part!

Moving towards CQS - the Command part

As said before, the Command part of Command Query Separation is a bit more complex. The main difference is that, because you are doing mutations on your data store, you'll need more checks. Is the data valid? Is the data consistent? etc. But the general idea remains the same.

It starts, just like the Query part, with a Dispatcher. This class will make sure your Command is handled by the correct CommandHandler.

public class CommandDispatcher : ICommandDispatcher
{
    private readonly IComponentContext _Context;

    public CommandDispatcher(IComponentContext context)
    {
        _Context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public TResult Dispatch<TParameter, TResult>(TParameter command) where TParameter : ICommand where TResult : IResult
    {
        var _handler = _Context.Resolve<ICommandHandler<TParameter, TResult>>();
        return _handler.Handle(command);
    }
}

Just like in the Query part, we look for a CommandHandler that matches our Command and CommandResult.

The SaveBookCommand and its result look like this:

public class SaveBookCommand : Command
{
    public Book Book { get; set; }
}

public class SaveBookCommandResult : IResult
{
}

In the sample application, the CommandHandler base class looks exactly the same as the QueryHandler base class. The only real difference is the generic constraints. While the QueryHandler will only take objects that implement the interface IQuery, the CommandHandler requires objects that implement ICommand.

As said earlier, this super class is kept as simple as possible. It just passes the Command to its sub class, just like the QueryHandler.

A more “production worthy” CommandHandler would have a constructor like this:

protected CommandHandler(ICommandValidator<TRequest> validator, IMapperEngine mapperEngine, IUnitOfWork unitOfWork)
{
    MapperEngine = mapperEngine;
    UnitOfWork = unitOfWork;
    _Validator = validator;

   _Log = LogManager.GetLogger(GetType().FullName);
}

The Validator and MapperEngine classes are modules that perform data validation and transform Entity Objects to Data Transfer Objects. The UnitOfWork handles transactions, scopes and different contexts.

But the demo code should be enough to allow you to understand the core concepts of CQS.

Let’s take a look at a CommandHandler implementation:

public class SaveBookCommandHandler : CommandHandler<SaveBookCommand, SaveBookCommandResult>
{
    public SaveBookCommandHandler(ApplicationDbContext context) : base(context)
    {
    }

    protected override SaveBookCommandResult DoHandle(SaveBookCommand request)
    {
        var _response = new SaveBookCommandResult();

        //get the book
        ApplicationDbContext.Books.Attach(request.Book);

        //add or update the book entity
        ApplicationDbContext.Entry(request.Book).State =
            request.Book.Id == Constants.NewId ? EntityState.Added : EntityState.Modified;
        
        //persist changes to the datastore
        ApplicationDbContext.SaveChanges();

        return _response;
    }
}

This should be pretty straightforward. The code checks if the book that is passed along with the Command is a new book or not, and adds or updates it accordingly.

We can now update the Books library!

var _commandDispatcher = container.Resolve<ICommandDispatcher>();

//edit first book
var _bookToEdit = _reponse.Books.First();
_bookToEdit.Bought = !_bookToEdit.Bought;
_commandDispatcher.Dispatch<SaveBookCommand, SaveBookCommandResult>(new SaveBookCommand()
        {
            Book = _bookToEdit
        });

//add new book
_commandDispatcher.Dispatch<SaveBookCommand, SaveBookCommandResult>(new SaveBookCommand()
        {
            Book = new Book()
            {
                Title = "C# in Depth",
                Author = "Jon Skeet",
                Bought = false,
                DatePublished = new DateTime(2013, 07, 01)
            }
        });

That's it! The output now shows the correct number of books:

console-output-with-cqs-commandhandler

Figure 8: Console output after inserting and updating a book

Conclusion

CQS, or Command Query Separation, is a very subtle, simple yet very adaptable and powerful pattern that I have been using as a baseline in my architectures for years. It has proven its worth time and time again.
Your project gets structured in a very clear way. The code gets more readable and more trustworthy as you get the assurance that unexpected side effects cannot arise. You can encapsulate a lot of complexity in the Dispatchers and/or BaseHandlers, allowing developers to focus on what they do best: Writing the application!

I hope you enjoyed this read and make sure to check out the sample code on my github page.

This article was technically reviewed by Yacoub Massad.

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
My name is Tim Sommer. I live in the beautiful city of Antwerp, Belgium. I have been passionate about computers and programming for as long as I can remember. I'm a speaker, teacher and entrepreneur. I'm a Windows Insider MVP.But most of all, I'm a developer, an architect, a technical specialist and a coach; with 8+ years of professional experience in the .NET framework.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

FREE .NET MAGAZINES

Free DNC .NET Magazine

Tags

JQUERY COOKBOOK

jQuery CookBook