CQS (command–query separation) - is a principle that defines data model of a system in terms of queries and commands:
- Commands affecting state of object and provide a simple way to execute a single operation in terms of domain model.
- Queries allows to query information from a data source without affecting it's state.
More detailed explanation of CQS principle at Wikipedia.
This sample is an implementation of CQS for .NET applications.
- DataModel - domain objects and abstractions for queries and commands. Have no references to any data-access specific libraries.
- EFDataAccess - implementation of data-access layer on top of Entity Framework 5.0 and SQL Compact.
- TestApp - test application to run queries and commands.
- UnitTests - unit-test for queries and commands.
-
Register in IoC-container IQueryFactory -> new QueryFactory() binding. You should pass lambda to resolve Queries as a parameter QueryFactory object. It may be lambda to resolve queries from IoC.
-
Register in IoC-container ICommandsFactory -> new CommandFactory() binding. You should pass lambda to resolve Commands as a parameter CommandFactory object. It may be lambda to resolve commands from IoC.
-
Register queries and commands in IoC.
-
Create query interface, mark it with IQuery and place it to folder DataModel\Queries:
public interface IActiveUsersQuery : IQuery { IEnumerable Execute(); }
-
Implement the query. Query implementation depends on data-access method. In this sample Entity Framework 5.0 sample provided.
All EF-specific things located at EFDataAccess project.
To implement EF-specific queries EFQueryBase class created (EFDataAccess\Core\EFQueryBase.cs). Let's implement IActiveUsersQuery query:
public class ActiveUsersQuery : EFQueryBase<ApplicationDbContext>, IActiveUsersQuery
{
public ActiveUsersQuery(ApplicationDbContext context)
: base(context)
{
}
public IEnumerable<User> Execute()
{
return DbContext
.Users
.Where(x => x.IsActive)
.ToArray();
}
}
-
Register the query in IoC:
Bind().To().InTransientScope();
Now you can resolve interface of query from IoC and execute it.
-
Resolve IQueryFactory:
IQueryFactory queryFactory = Container.Current.Resolve();
-
Resolve query:
IEnumerable activeUsers = queryFactory .ResolveQuery();
-
Execute the query:
IEnumerable activeUsers = queryFactory .ResolveQuery() .Execute();
You should test query implementation (not query interface:)).
-
Create source collection(s) and mock the data source (EF's DbContext in our case):
IEnumerable users = new[] { new User {Id = 1, FirstName = "User1", LastName = "User1", Email = "user1@email.com", IsActive = false}, new User {Id = 2, FirstName = "User2", LastName = "User2", Email = "user2@email.com", IsActive = false}, new User {Id = 3, FirstName = "User3", LastName = "User3", Email = "user3@email.com", IsActive = false} };
ApplicationDbContext dbContext = new Mock("Test") .SetupDbContextData(x => x.Users, users) .Build();
-
Instantiate the query with mocked data source and execute the query:
ActiveUsersQuery query = new ActiveUsersQuery(dbContext); IEnumerable result = query.Execute();
-
Check the results:
CollectionAssert.AreEqual(users.Where(x => x.IsActive).ToArray(), result.ToArray());
-
Create command interface, mark it with ICommand and place to DataModel\Commands:
public class CreateUserCommand : ICommand { public string FirstName { get; protected set; } public string LastName { get; protected set; } public string Email { get; protected set; }
// Output parameter's setters marked as public public int Id { get; set; } public CreateUserCommand(string firstName, string lastName, string email) { FirstName = firstName; LastName = lastName; Email = email; }
}
-
Implement the command. Command implementation as well as queries depends on data-access method. In this sample Entity Framework 5.0 sample provided.
All EF-specific things located at EFDataAccess project.
To implement EF-specific command EFCommandHandlerBase class created (EFDataAccess\Core\EFQueryBase.cs). Let's implement handler for CreateUserCommand command:
public class CreateUserCommandHandler : EFCommandHandlerBase<CreateUserCommand, ApplicationDbContext>,
ICommandHandler<CreateUserCommand>
{
public CreateUserCommandHandler(ApplicationDbContext dbContext)
: base(dbContext)
{
}
public override void Execute(CreateUserCommand command)
{
int id = DbContext.Users.Any() == false ? 1 : DbContext.Users.Max(x => x.Id) + 1;
User user = new User
{
Id = id,
FirstName = command.FirstName,
LastName = command.LastName,
Email = command.Email,
IsActive = false
};
DbContext.Users.Add(user);
DbContext.SaveChanges();
command.Id = user.Id;
}
}
-
Regiser the command in IoC:
Bind<ICommandHandler>().To().InTransientScope();
-
Resolve ICommandsFactory:
ICommandsFactory commandsFactory = Container.Current.Resolve();
-
Create Command object:
CreateUserCommand createUserCommand = new CreateUserCommand("...","...","...");
-
Execute the command:
commandsFactory.ExecuteQuery(createUserCommand);
You should test command handler implementation, instead of testing command definition:
-
Create source collection(s) and mock the data source (EF's DbContext in our case):
IEnumerable users = new User[] { };
ApplicationDbContext dbContext = new Mock("Test") .SetupDbContextData(x => x.Users, users) .Build();
-
Instantiate the command handler with mocked data source and execute the command:
CreateUserCommandHandler commandHandler = new CreateUserCommandHandler(dbContext); commandHandler.Execute(new DataModel.Commands.CreateUserCommand("User1", "User1", "user1@email.com"));
-
Check the results:
User[] result = dbContext.Users.ToArray();
Assert.AreEqual(1, result.Count()); Assert.AreEqual(1, result[0].Id); Assert.AreEqual("User1", result[0].FirstName); Assert.AreEqual("User1", result[0].LastName); Assert.AreEqual("user1@email.com", result[0].Email); Assert.AreEqual(false, result[0].IsActive);