【问题标题】: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。

这有模式吗?

【问题讨论】:

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


    【解决方案1】:

    您的 DTO 代表一个读取模型。为此,我通常使用查询“层”(尽管我倾向于更多地考虑关注点而不是层)。我通常使用 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; }
        }
    }
    

    【讨论】:

    • 感谢您的回答!我对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);
    }
    

    【讨论】:

      猜你喜欢
      • 2019-02-26
      • 2018-07-18
      • 2019-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-18
      • 2018-03-05
      • 1970-01-01
      相关资源
      最近更新 更多