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

Posted

技术标签:

【中文标题】查询 DTO 投影的正确位置是啥?【英文标题】:What's the correct place to query DTO projections?查询 DTO 投影的正确位置是什么? 【发布时间】:2019-09-26 13:24:21 【问题描述】:

出于不同的原因(关注点分离、性能),我想停止将域实体发送到我的视图,并改用 DTO 投影。

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

什么是正确的做法?

存储库:不,they should not return DTOs 控制器:我希望它们尽可能地精简,并避免让它们执行查询和/或映射

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

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

这有模式吗?

【问题讨论】:

【参考方案1】:

您的 DTO 代表一个读取模型。为此,我通常使用查询“层”(尽管我倾向于更多地考虑关注点而不是层)。我通常使用 IAggregateQuery 的方式与使用 IAggregateRepository 的方式相同。

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

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; 
    

【讨论】:

感谢您的回答!我对ICustomerQuery 中的“查询”一词有点不舒服:query 在我看来就像您发送到数据存储的对象(您发送查询并获得 DTO 作为回报),不像封装了查询数据存储的方法的对象。我会期待像ICustomerQuerier 这样的东西吗?除此之外,您的关注点分离看起来不错! 从来没有这样想过,我完全接受了这种不适:) --- 我实际上有一个IQuery 接口,它代表一个真正的Query,我会有一个关联的ICustomerQueryFactory这将被注入到CustomerQuery 类中,以提供相关的IQuery 实例。 QueryFactory 是一个可以替换不同数据库的实现的地方,如果这种情况发生的话(我从未见过)。我想我一直认为ICustomerQuery 更像是动词形式而不是名词 --- 奇怪的是真的......【参考方案2】:

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

执行查询的对象。

示例:

应用程序管理有关公司员工、部门等的数据。 用例:给我一份 30 岁以下、工资超过 2000 欧元的员工名单(只是姓名、电子邮件、离职、工资)。

代码:

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

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

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

【讨论】:

您好,感谢您的回答,但它没有回答以下问题:在哪里我应该查询它们(在什么样的对象中)? 嗨,我已经回答了“在什么地方做这件事的正确位置?”。我以为你的意思是什么层。不管是什么对象,都叫“查询”,一个查询对象。你有命令,你有查询。确切地说,执行查询的对象是查询处理程序。【参考方案3】:

这是一个很好的问题,

你把它们放在应用层。您寻求的模式称为查询服务。

看看 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);

【讨论】:

以上是关于查询 DTO 投影的正确位置是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 RESTful API 中创建和验证实体模型及其 DTO 的正确方法是啥?

在 iOS 上使用 OpenGL-ES 2.0 设置横向正交投影的正确方法是啥?

尽管具有正确的 DTO,但无法使用请求的结果类型为具有多个返回的查询创建 TypedQuery

Spring-data-jpa 投影生成查询不正确

NHibernate 投影到 DTO

在 React 中渲染之前调用函数的正确位置是啥?