A Better Way to Project Domain Entities into DTOs

Imagine you have a nicely designed Domain Layer that uses Repositories to handle getting Domain Entities from your database with an ORM, e.g. Entity Framework, into an MVC view or a Web API controller.

Problem is, the Presentation Layer needs objects of a different shape than your Domain Layer Aggregates.

By different shape, I mean that this layer might need data combined from multiple Aggregates, or only portions of an Aggregate.

Your architecture is effectively layered, but you find yourself looking for the right place to turn your Aggregates into DTOs (Data Transfer Objects).

Should the projection from Aggregate(s) to DTO be done inside your Repositories?

Job Descriptions

A Repository should only really concern itself with the persistence of Aggregates.

”…a cohesive set of responsibilities for providing access to the roots of AGGREGATES from early life cycle through the end” - Evans

A DTO was originally defined to be used in conjunction with a Remote Facade. The DTO:

“carries data between processes in order to reduce the number of method calls” - Fowler

When used to move data from the Domain Layer to the Presentation Layer, a DTO is:

“designed to hold the entire number of attributes that need to be displayed in a view.” - Vernon

With these definitions, it’s clear that a Repository’s responsibility lies in persisting the state of Aggregates, not sharing the state of Aggregates with the Presentation Layer. That’s not in the Repository’s job description.

It is in the DTO’s job description to be a carrier of Aggregate state to the Presentation Layer. Still, the DTO needs to be assembled somewhere…

The DTO Assembler

If not in the Repository, then where do you shape your Aggregate(s) into objects suitable for your presentation layer?

A dedicated DTO Assembler has the single responsibility of mapping (as in Mapper) the attributes from the Aggregate(s) to the DTO.

A DTO Assembler can live in an Application Service that is a client of your Repository. The Application Service “will use Repositories to read the necessary Aggregate instances and then delegate to a DTO Assembler [Fowler, P of EAA] to map the attributes of the DTO.” (Vernon)

Here’s a quick example of the Application Service Delegating to a DTO Assembler scenario…

The DTO Assembler:

public class PartAssembler {
    public PartDTO WriteDto(Part part) {
        var partDto = new PartDTO();
        partDto.PartNumber = part.PartNumber;
        partDto.CreatedBy = part.CreatedBy;
        partDto.CreatedOn = part.CreatedOn;
        partDto.UnitOfMeasure = part.UnitOfMeasure;
        partDto.ExtendedDescription = part.ExtendedDescription;
        partDto.PartDescription = part.PartDescription;
        partDto.SalesCode = part.SalesCode;
        var componentsList = part.Components
            .Select(component => component.ComponentNumber)
            .ToList();
        var laborSequenceList = part.LaborSequences
            .Select(labor => labor.LaborSequenceNumber)
            .ToList();
        partDto.ComponentList = componentsList;
        partDto.LaborSequenceList = laborSequenceList;
        return partDto;
    }
}

The Application Service:

public class PartCatalogService {
    private readonly IRepository<Part> _partRepository;
    private readonly PartAssembler _partAssembler;
    public PartCatalogService(IRepository<Part> partRepository, PartAssembler partAssembler) {
        _partRepository = partRepository;
        _partAssembler = partAssembler;
    }
    public PartDTO GetPart(string partNumber) {
        var part = _partRepository.Get(x => x.PartNumber == partNumber);
        return _partAssembler.WriteDto(part);
    }
}

A Test:

public class Parts_catalog_dto_assembler : IDisposable {
    private readonly ITestOutputHelper _output;
    private readonly IPartsCatalogDbContext _db;
    public Parts_catalog_dto_assembler(ITestOutputHelper output) {
        _output = output;
        _db = new FakePartsCatalogDbContext();
        for (int i = 0; i < 10; i++) {
            Part part = new Part() {
                PartNumber = "TEST-PART-NUMBER|" + i
            };
            for (int j = 0; j < 5; j++) {
                var component = new Component() {
                    ComponentNumber = "TEST-COMPONENT-NUMBER|" + j
                };
                part.Components.Add(component);
            }
            for (int k = 0; k < 5; k++) {
                var laborSequence = new LaborSequence() {
                    LaborSequenceNumber = "LABOR-SEQUENCE-NUMBER|" + k
                };
                part.LaborSequences.Add(laborSequence);
            }
            _db.Parts.Add(part);
        }
        _db.SaveChanges();
    }
    [Fact]
    public void Get_part_dto_from_assembler() {
        var repository = new PartRepository(_db);
        var assembler = new PartAssembler();
        var partCatalogService = new PartCatalogService(repository, assembler);

        var partDto = partCatalogService.GetPart("TEST-PART-NUMBER|1");
        partDto.Should().BeOfType<PartDTO>();
    }
    public void Dispose() {
        _db.Dispose();
    }
}

The full sample code can be found here.

Summary

First, we talked about why you shouldn’t add DTO Assembly to the responsibilities of your Repositories. They should be concerned with Aggregates.

Next, we introduced the DTO Assembler that maps your Domain Layer Aggregates into DTOs to be passed to your Presentation Layer.

Finally, we talked about the basic implementation of a DTO Assembler as an Application Service.

Keep in mind - we’re finishing this quick discussion with a simple example. There are several more considerations to make here, some involving patternssome involving libraries… and some involving a full paradigm shift.

I plan to continue to dive deeper into this topic and, hopefully, help you gain a better understanding. So - stay tuned, be sure to sign up for my newsletter, shoot me an email, and/or leave a comment below!


Related Posts:


Sources:

Tweet
comments powered by Disqus