2

For different reasons (separation of concerns, performance), I want to stop sending domain entities to my views, and use DTO projections instead.

I want to use ORM queries to create my DTOs, selecting only the fields I need from one or more entities.

What's the correct place to do that?

  • Repositories: no, they should not return DTOs
  • Controllers: I'd like to keep them as thin as possible, and avoid having them perform queries and/or mapping

I feel like there should be a centralized place (similar to repositories for entities) to query and create DTOs, but I failed to find a pattern or naming for this practice.

I've come across the term DTO assembler, but it looks like this pattern is for mapping one or more domain entities to a DTO, whereas in my case, I want to skip loading full entities and directly translate database queries to DTOs.

Is there a pattern for this?

BenMorel
  • 34,448
  • 50
  • 182
  • 322

3 Answers3

0

Your DTOs represent a read model. For this I typically use a query "layer" (although I tend to think more in terms of concerns rather than layers). I typically use I{Aggregate}Query in the same way that I have I{Aggregate}Repository.

The interface would return the data in as simple a format as possible:

namespace Company.Project.DataAccess
{
    public interface ICustomerQuery
    {
        int CountMatching(Query.Customer.Specification specification);
        int Count();
        IEnumerable<DataRow> RowsMatching(Query.Customer.Specification specification); // perhaps OK for simple cases
        IEnumerable<Query.Customer> Matching(Query.Customer.Specification specification); // for something more complex
    }
}   

I also create a Query namespace that contains my DTOs. This means that the domain would, for instance, contain the Customer AR and the Query namespace would also contain a Customer but since it is in the Query namespace there is no ambiguity and I don't have to add a DTO suffix:

namespace Company.Project.DataAccess.Query
{
    public class Customer
    {
        public class Specification
        {
            public string Name { get; private set; }

            public Specification WithName(string name)
            {
                Name = name;

                return this;
            }
        }

        public string Name { get; set; }
        public string Address { get; set; }
    }
}
Eben Roux
  • 12,983
  • 2
  • 27
  • 48
  • Thanks for your answer! I'm a bit uncomfortable with the term "query" in `ICustomerQuery`: *query* sounds to me like the object you send to the data store (your send a query and get a DTO in return), not like the object that encapsulates the *methods* to query the data store. I would expect something like `ICustomerQuerier` instead? Other than that, your separation of concerns looks good! – BenMorel May 09 '19 at 09:43
  • 1
    Never actually thought about it that way and I totally buy the discomfort :) --- I actually have an `IQuery` interface that represents a true `Query` and I would have an associated `ICustomerQueryFactory` that would be injected into the `CustomerQuery` class in order to provide relevant `IQuery` instances. The `QueryFactory` is where one would swop out implementations for different DBs if that should ever happen (which I've never seen). I guess I've always regarded the `ICustomerQuery` as more of the verb form as opposed to the noun --- odd that really... – Eben Roux May 09 '19 at 12:09
0

A dto is an object of the application layer. What you want is to populate it directly from the db. It is the query side of cqrs, where you don't have a rich domain model like the command side, you just have projections (dtos) suitable for the client. They are the query(read) model.

UPDATE:

These are the objects of the pattern I use, similar to command, but a query has a result:

public interface QueryResult {}

Plain DTO (or a list of them), with the output data for the clients.

public interface Query<QR extends QueryResult> {}

Plain DTO with the input data (search criteria) for executing the query.

public interface QueryHandler <QR extends QueryResult, Q extends Query<QR>> {
    public QR handle ( Q query );
}

The object that executes the query.

EXAMPLE:

  • App managing data about Employees, Departments, etc. of a Company.
  • Use Case: Give me a list of employees (just the name, email, depart, salary) of the employees younger than 30 years with salary greater than 2,000 euros.

Code:

class EmployeeDto {
    private String name;
    private String email;
    private String departmentName;
    private double salary;
    ...
    <<getters and setters>>
    ...
}

class EmployeeDtoList implements QueryResult {
    private List<EmployeeDto> employeeDtos;
    ...
    <<getter and setter>>
    ...
}

class EmployeesByAgeAndSalary implements Query<EmployeeDtoList> {
    private Calendar maxAge;
    private double minSalary;
    ...
    <<getters and setters>>
    ...
}

class EmployeesByAgeAndSalaryHandler implements QueryHandler<EmployeeDtoList,EmployeesByAgeAndSalary> {
    ...
    @Override
    public EmployeeDtoList handle(EmployeesByAgeAndSalary query) {
        ...
        <<retrieve from the database the data we need to return,
        applying the criteria for the age and salary given in the "query" arg>>
        ...
    }
}

-- The facade that the client uses is a mediator (interface with this method):

public <QR extends QueryResult,Q extends Query<QR>> QR executeQuery(Q query);

The mediator would be implemented by a class that manages a query handler registry, so that it redirects the request to the query handler associated to the given query.

It is similar to the command pattern, but with queries.

choquero70
  • 4,470
  • 2
  • 28
  • 48
  • Hi, thanks for your answer, but it does not answer the question: *where* should I query them (in what kind of object)? – BenMorel May 10 '19 at 20:54
  • Hi, I've answer to "What's the correct place to do that?". I thought you meant what layer. Regardless the kind of object, it is called "query", a query object. You have commands and you have queries. Well exactly speaking the object that does the query is a query handler. – choquero70 May 10 '19 at 23:15
0

That's an excellent question,

You put them in the application layer. The pattern you seek is called a Query Service.

Look at how Vaughn Vernon (the author of Implementing Domain Driven Design) did in his github repo:

https://github.com/VaughnVernon/IDDD_Samples/tree/master/iddd_collaboration/src/main/java/com/saasovation/collaboration/application/forum/data

Then he populates them directly from the DB in a query service (CQRS):

public ForumDiscussionsData forumDiscussionsDataOfId(String aTenantId, String aForumId) {
    return this.queryObject(
            ForumDiscussionsData.class,
            "select "
            +  "forum.closed, forum.creator_email_address, forum.creator_identity, "
            +  "forum.creator_name, forum.description, forum.exclusive_owner, forum.forum_id, "
            +  "forum.moderator_email_address, forum.moderator_identity, forum.moderator_name, "
            +  "forum.subject, forum.tenant_id, "
            +  "disc.author_email_address as o_discussions_author_email_address, "
            +  "disc.author_identity as o_discussions_author_identity, "
            +  "disc.author_name as o_discussions_author_name, "
            +  "disc.closed as o_discussions_closed, "
            +  "disc.discussion_id as o_discussions_discussion_id, "
            +  "disc.exclusive_owner as o_discussions_exclusive_owner, "
            +  "disc.forum_id as o_discussions_forum_id, "
            +  "disc.subject as o_discussions_subject, "
            +  "disc.tenant_id as o_discussions_tenant_id "
            + "from tbl_vw_forum as forum left outer join tbl_vw_discussion as disc "
            + " on forum.forum_id = disc.forum_id "
            + "where (forum.tenant_id = ? and forum.forum_id = ?)",
            new JoinOn("forum_id", "o_discussions_forum_id"),
            aTenantId,
            aForumId);
}
Sylvain Lecoy
  • 947
  • 6
  • 15