A Pattern to Decouple your Aggregates from their Clients
Last time, we talked about DTO Assembly as a separate concern from Aggregate persistence. This means we don’t want to return DTOs from Repositories.
Instead, we put dedicated Application Services in our Application Layer to handle requests from the Presentation Layer. We used DTO Assemblers to map our Aggregates to DTOs inside these Application Services.
But… how are we doing as far as coupling?…
Are we coupling the internal shape of our Aggregates to clients - the DTO Assemblers?… Yes we are - the DTO Assembler might have to query far into the Aggregate’s internal structure.
In our example, the DTO Assembler expects Part
to have a List<Component>
and List<LaborSequences>
and it expects to be able to query values from them to build the PartDTO
. It depends on the Aggregate to be shaped a certain way.
How can we reduce this coupling?
Have Your People Call My People
The Mediator Pattern (GoF) decouples direct communication between objects.
“Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and lets you vary their interaction independently.” - GoF
We can use a Mediator to handle requests for DTOs by the Presentation Layer. This replaces our DTO Assembler and Application Service. The Aggregate can send its own DTO through the Mediator.
In this scenario, we’re going to register a Handler class with the Mediator (a.k.a. Director) class. When the Mediator gets a Query that can be handled by the Handler, Handler.Handle(Query)
gets called and will send the DTO back to the Presentation Layer.
Example
Using our example from my last post, we can easily add the code we need to use the Mediator Pattern. We’ll use a library by Jimmy Bogard called MediatR. He goes into detail about using MediatR and Automapper for CQRS here.
Although you don’t have to use one, MediatR works well with your IoC container of choice. It comes with examples for several different containers as seen here.
In our case, we are going to move the DTO mapping code into a class that implements MediatR’s IRequestHandler<Query, Result>
Here’s the original PartDTO
class:
public class PartDTO {
public PartDTO() {
this.Labor = new List<LaborSequenceDTO>();
this.Components = new List<ComponentDTO>();
}
public string PartNumber { get; set; }
public string ExtendedDescription { get; set; }
public string PartDescription { get; set; }
public string SalesCode { get; set; }
public string UnitOfMeasure { get; set; }
public double Quantity { get; set; }
public List<LaborSequenceDTO> Labor { get; set; }
public List<ComponentDTO> Components { get; set; }
public decimal TotalComponentCost {
get {
return Components.Sum(x => x.GetComponentCost(Quantity));
}
}
public decimal TotalLaborCost {
get {
return Labor.Sum(x => x.GetLaborCost(Quantity));
}
}
public decimal TotalCost {
get {
return TotalComponentCost + TotalLaborCost;
}
}
}
For MediatR to recognize a request, we have to implement MediatR.IRequest<out TResponse>
public class Query : IRequest<Result> {
public string PartNumber { get; set; }
}
We then define a message for out TResponse
as a class composed of a PartDTO
public class Result {
public Result() {
PartDto = new PartDTO();
}
public PartDTO PartDto { get; set; }
}
Finally, we define a request handler by implementing MediatR.IRequestHandler<in TRequest, out TResponse>
public class Handler : IRequestHandler<Query, Result> {
private readonly PartsCatalogDbContext _db;
public Handler(PartsCatalogDbContext db) {
_db = db;
}
public Result Handle(Query message) {
var result = new Result();
var part = _db.Parts
.SingleOrDefault(x => x.PartNumber == message.PartNumber);
result.PartDto.PartNumber = part.PartNumber;
result.PartDto.UnitOfMeasure = part.UnitOfMeasure;
result.PartDto.ExtendedDescription = part.ExtendedDescription;
result.PartDto.PartDescription = part.PartDescription;
result.PartDto.SalesCode = part.SalesCode;
var componentsList = part.Components
.Select(component => new ComponentDTO() {
Number = component.ComponentNumber,
Description = component.ComponentDescription,
Material = component.Material,
UnitOfMeasure = component.UnitOfMeasure,
QuantityPerAssembly = component.QuantityPerAssembly,
CostPerUnit = component.CostPerUnit
}).ToList();
var laborSequenceList = part.LaborSequences
.Select(labor => new LaborSequenceDTO() {
SequenceNumber = labor.LaborSequenceNumber,
SequenceDescription = labor.LaborSequenceDesc,
RunTime = labor.RunTime,
LaborRate = labor.LaborRate,
Burden = labor.Burden
}).ToList();
result.PartDto.Components = componentsList;
result.PartDto.Labor = laborSequenceList;
return result;
}
}
Here’s a test that wires up the IoC Container, (Ninject in my case), and sends a Query
to the MediatR.Mediator
.
[Fact]
public void Mediator_pattern_partdto_assembly() {
using (var kernel = new StandardKernel()) {
kernel.Components.Add<IBindingResolver, ContravariantBindingResolver>();
kernel.Bind(scan => scan.FromAssemblyContaining<IMediator>()
.SelectAllClasses()
.BindDefaultInterface());
kernel.Bind(scan => scan.FromAssemblyContaining<Query>()
.SelectAllClasses()
.BindAllInterfaces());
kernel.Bind<SingleInstanceFactory>().ToMethod(ctx => t => ctx.Kernel.Get(t));
kernel.Bind<MultiInstanceFactory>().ToMethod(ctx => t => ctx.Kernel.GetAll(t));
var mediator = kernel.Get<IMediator>();
var query = new Query() {
PartNumber = "TEST-PART-NUMBER|0"
};
var result = mediator.Send(query);
result.PartDto.Should().BeOfType<PartDTO>();
}
}
There’s more sample code here and, as always, feel free to contact me or leave a comment if you want to talk about it.
Summary
We noticed coupling that we introduced in our DTO Assembler Application Services that handle requests from our Presentation Layer. These services can sometimes reach too far into our Domain Aggregates.
Next, we discovered the Mediator Pattern as an another way of doing this work. The Mediator Pattern takes care of our coupling problem by standing in front of our Aggregates and handling requests - Double Dispatching (GoF) the requests to Handlers that know how to respond.
Finally, we looked at refactoring our original DTO Assembler Application Service setup. We used the same Aggregate to DTO mapping code, but we placed it in a class implementing Mediatr.IRequestHandler<Query, Result>
. We registered the Handler with MediatR.
If you get the chance to look at the sample code, you’ll notice that we have a mixture of strategies. We also haven’t talked much about the Presentation Layer itself.
I’m planning to dive deeper into making our mapping code better, solidifying everything into a single strategy, and looking into commands later. So stay tuned, be sure to sign up for my newsletter, and/or leave a comment below!
Related Posts:
Sources:
- Vernon, Vaughn. Implementing Domain Driven Design. Addison-Wesley Professional, February 2013.
- Helm, Gamma, Vlissides, Johnson [GoF]. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional, October 1994.
- Baharestani, Daniel. Mastering Ninject for Dependency Injection. Packt Publishing, September 2013.
- Bogard, Jimmy - LosTechies Blog. CQRS with MediatR and AutoMapper
- Bogard, Jimmy. MediatR
- Bogard, Jimmy. ContosoUniversity