The command query separation pattern is an application architecture pattern that separates the logic for queries that don’t update the observable state of a system (i.e. free of side effects) from the logic that performs any state updates to such system (commands).
For example, a screen that displays a grid of incidents would have a GetAllIncidents() method, or a GetAllOpenIncidents() in a class IncidentsService, whereas the action to submit Submit() a new incident or update Update() an existing incindent, could be declared in a CreateIncidentCommand class, for example.
When it comes to the separation itself, there is no formal level of separation between the two. They can go from grouping reads and writes into two separate classes, to having two complete separate applications (or microservices) for each.
CQRS is an application architecture inspired on Command Query Separation pattern above that goes even further. In the CQRS design pattern you have separate model representations for queries and commands which make applications ideal for event sourcing or task-based UI applications.
For example, continuing on with the Incidents example above, let’s say you have an application where you can see both a list of incidents and a separate page to enter details about the incident (kind of master-detail). You have far more fields on the details page than on the grid view. The fields needed to represent each row in the grid will be then just a subset of all the available fields on an incident: when retrieving a grid of incidents then probably is not worth it to retrieve all fields, only the ones we are interested in. It would make sense to have a separate model class to represent the fields that are displayed on each row. However, on the details page, we would need the fields that can be updated (which could not just be more fields, but also different ones: i.e. some fields might be read only, for instance).
What is interesting is that this concept of separation between reads and writes is specially handy both:
When testing queries with FakeXrmEasy, you would typically need to setup some test data entities via the .Initialize() method, and then make sure your methods return the correct models.
Example: If we use the above incidents grid as an example, let’s assume the GetAllOpenIncidents method should only return Incidents in Active status, for simplicity.
A unit test example that would test such scenario and that also uses CQS / CQRS principles could look like the following:
public class IncidentServiceTests : FakeXrmEasyTestsBase
{
private readonly Incident _openIncident;
private readonly Incident _closedIncident;
private readonly IIncidentsService _incidentsService;
public IncidentServiceTests()
{
_openIncident = new Incident()
{
Id = Guid.NewGuid(),
Name = "Some Open Incident"
};
_closedIncident = new Incident()
{
Id = Guid.NewGuid(),
Name = "Some Closed Incident",
["statecode"] = new OptionSetValue(1) //1 is inactive
};
_incidentsService = new IncidentsService(_service); //_service is IOrganizationService
}
[Fact]
public void Should_retrieve_open_incidents_only()
{
//Initialize the In-Memory database with one active incident and one closed incident
_context.Initialize(new List<Entity>()
{
_openIncident, _closedIncident
});
//Invoke the queries-related service (IncidentService in this case)
var incidentsList = _incidentsService.GetAllOpenIncidents();
//Assert it only returned the open incident and that the IncidentRowModel has all the relevant fields populated
Assert.Single(incidentsList);
var openIncidentResult = incidentsList[0];
Assert.Equal(_openIncident.Id, openIncidentResult.Id);
Assert.Equal(_openIncident.Name, openIncidentResult.Title);
}
}
public class IncidentRowModel
{
public Guid Id { get; set; }
public string Title { get; set; }
}
public interface IIncidentsService
{
List<IncidentRowModel> GetAllOpenIncidents();
}
public class IncidentsService: IIncidentsService
{
private readonly IOrganizationService _orgService;
public IncidentService(IOrganizationService orgService)
{
_orgService = orgService;
}
public List<IncidentRowModel> GetAllOpenIncidents()
{
using(var ctx = new XrmServiceContext(_orgService))
{
return (from inc in ctx.CreateQuery<Incident>()
//IncidentStateCode is an enum, that could be automatically generated with crmsvcutil for instance
where inc.StateCode.Value == (int) IncidentStateCode.Active
select new IncidentRowModel()
{
Id = inc.IncidentId.Value,
Title = inc.Name
}).ToList();
}
}
}
Although this was a rather simple example, as you can see, when testing queries and data, we don’t need a massive number of entity records to test against, only the minimum that validates our requirements.
When testing commands, you perform an action via an IOrganizationService and then verify at the end that the In-Memory state was updating accordingly by retrieving the necessary entities from it.
You can query the state of the In-Memory database by calling the _context.CreateQuery() method.
Example: Let’s say we’re testing the scenario when someone is submitting a brand new incident. For simplicity, a new incident with a new title and description should be created. The incident should be created with a New status too.
public class SubmitIncidentCommandTests : FakeXrmEasyTestsBase
{
private readonly SubmitIncidentCommand _command;
public SubmitIncidentCommandTests()
{
_command = new SubmitIncidentCommand(_service);
}
[Fact]
public void Should_create_one_brand_new_incident_on_submit()
{
//Invoke the command with a given model
_command.Model = new SubmitIncidentModel()
{
Title = "Engine broken",
Description = "The engine has been broken due to bla bla..."
};
_command.Execute();
//Query the state of the In-Memory database after the command execution
var incident = _context.CreateQuery<Incident>().FirstOrDefault();
Assert.NotNull(incident);
//Assert the incident is created with New status and with relevant info
Assert.Equal(_command.Model.Title, incident.Name);
Assert.Equal(_command.Model.Description, incident.Description);
Assert.Equal((int) IncidentStatusCode.New, (int) incident.StatusCode.Value);
}
}
public class SubmitIncidentCommand
{
private readonly IOrganizationService _orgService;
public SubmitIncidentModel Model { get; set; }
public SubmitIncidentCommand(IOrganizationService orgService)
{
_orgService = orgService;
}
public void Execute()
{
_orgService.Create(new Incident()
{
Name = Model.Title,
Description = Model.Description,
StatusCode = new OptionSetValue((int) IncidentStatusCode.New)
});
}
}
public class SubmitIncidentModel
{
public string Title { get; set; }
public string Description { get; set; }
}