查询DTO投影的正确位置是什么?

时间:2019-05-08 18:32:15

标签: model-view-controller domain-driven-design dto

由于各种原因(关注点分离,性能下降),我想停止向我的视图发送域实体,而改用DTO投影。

我想使用ORM查询创建我的DTO,仅从一个或多个实体中选择我需要的字段。

这样做的正确位置是什么?

  • 存储库:否,they should not return DTOs
  • 控制器:我想让它们尽可能的薄,并避免让它们执行查询和/或映射

我觉得应该有一个集中的位置(类似于实体的存储库)来查询和创建DTO,但是我没有找到这种做法的模式或命名。

我遇到过DTO assembler一词,但看起来这种模式是用于将一个或多个域实体映射到DTO,而在我的情况下,我想跳过加载完整实体并将数据库查询直接转换为DTO。

有这种模式吗?

3 个答案:

答案 0 :(得分:0)

您的DTO代表读取模型。为此,我通常使用 query “层”(尽管我倾向于更多地关注问题而不是层)。我通常以与I{Aggregate}Query相同的方式使用I{Aggregate}Repository

接口将以尽可能简单的格式返回数据:

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
    }
}   

我还创建了一个包含我的DTO的Query命名空间。这意味着该域将包含Customer AR,Query名称空间也将包含Customer,但是由于它位于Query名称空间中,因此没有模棱两可,我不必添加DTO后缀:

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; }
    }
}

答案 1 :(得分:0)

dto是应用程序层的对象。您想要的是直接从数据库填充它。它是cqrs的查询方,在这里您没有像命令方那样的丰富域模型,而只有适合客户端的投影(dtos)。它们是查询(读取)模型。

更新:

这些是我使用的模式的对象,类似于命令,但是查询的结果是:

public interface QueryResult {}

纯DTO(或它们的列表),以及客户端的输出数据。

public interface Query<QR extends QueryResult> {}

在DTO中插入用于执行查询的输入数据(搜索条件)。

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

执行查询的对象。

示例:

  • 有关公司员工,部门等的应用程序管理数据。
  • 用例:给我一份薪水超过2,000欧元的30岁以下雇员的雇员名单(包括姓名,电子邮件,离职,薪水)。

代码:

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>>
        ...
    }
}

- 客户端使用的外观是介体(此方法的接口):

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

介体将由管理查询处理程序注册表的类实现,以便将请求重定向到与给定查询关联的查询处理程序。

它类似于命令模式,但带有查询。

答案 2 :(得分:0)

这是一个很好的问题,

您将它们放在应用程序层中。您寻求的模式称为查询服务。

看看Vaughn Vernon(实现域驱动设计的作者)在他的github存储库中的表现如何:

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

然后,他直接从数据库中的查询服务(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);
}