查询 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 设置横向正交投影的正确方法是啥?