I am looking to use CQRS in a project I'm working on, however I'm currently struggling with the best way to implement the query side of CQRS. Based on my somewhat limited understanding there is a Thin Data Layer (sometimes called a Thin Read Layer) which queries the database and returns a DTO with the query results which are used by the UI layer of the application.
As this is a Java EE application I'm developing the Thin Data Layer uses JPA to query the database using EntityManager.createNamedQuery
which returns an entity containing the results which I'm then mapping to a DTO.
Given that the query side of the application should be "read only", the DTO contains getters but no setters for each of the properties and a constructor which is used to populate the properties upon creation.
For a simple query I can manually map the values in the entity to the DTO using the constructor, however this isn't practical for more complex queries particularly where the entity contains "one-to-many" relationships which need to be mapped to corresponding DTOs. I have looked at using mapping frameworks such as Dozer and ModelMapper, however they all seem to rely on the DTO having setters and don't appear to make use of the constructor.
The following code represents a very simplified view of two entities and two DTOs which I've created to help explain the situation.
@Entity
@Table(name = "ORDER")
public class Order {
// Various named queries
@Id
@Column(name = "ORDER_ID")
private UUID orderId;
@Column(name = "ORDER_NUMBER")
private long orderNumber;
@Column(name = "ORDER_DATE")
@Temporal(TemporalType.DATE)
private Date orderDate;
@Column(name = "CUSTOMER_NAME")
private String customerName;
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, orphanRemoval=true)
@JoinColumn(name = "ORDER_NUMBER", referencedColumnName = "ORDER_NUMBER")
private List<OrderLine> orderLines;
// Getters and setters, equals, hashCode, toString
}
@Entity
@Table(name = "ORDER_LINE")
public class OrderLine {
@Id
@Column(name = "ORDER_LINE_ID")
private UUID orderLineId;
@OneToOne(fetch=FetchType.EAGER)
@JoinColumn(name = "ORDER_ID", referencedColumnName = "ORDER_ID")
private Order order;
@Column(name = "PART_NUMBER")
private String partNumber;
@Column(name = "DESCRIPTION")
private String description;
@Column(name = "UNIT_PRICE")
private BigDecimal unitPrice;
@Column(name = "QUANTITY")
private int quantity;
// Getters and setters, equals, hashCode, toString
}
public class OrderDTO {
private long orderNumber;
private Date orderDate;
private String customerName;
private List<OrderLine> orderLines;
public OrderDTO() {}
public OrderDTO(long orderNumber, Date orderDate, String customerName, List<OrderLineDTO> orderLines) {
this.orderNumber = orderNumber;
this.orderDate = orderDate;
this.customerName = customerName;
this.orderLines = orderLines;
}
//Getters but no setters
}
public class OrderLineDTO {
private String partNumber;
private String description;
private BigDecimal unitPrice;
private int quantity;
public OrderLineDTO() {}
public OrderLineDTO(String partNumber, String description, BigDecimal unitPrice, int quantity) {
this.partNumber = partNumber;
this.description = description;
this.unitPrice = unitPrice;
this.quantity = quantity;
}
//Getters but no setters
}
My questions are:
When using CQRS should the DTOs only have getters and a constructor or is it acceptable to have setters as well?
If the DTOs should ideally only have getters and a constructor, is a mapping framework the best way to populate the DTOs where the entity returned a complex result set containing a "one-to-many" relationship?
Are there any mapping frameworks that can use a constructor rather than setters?
select new my_package.MyDto(u.username, u.email) from User u
. The second which is more popular for bigger dtos including many table results is to use non-jpa technology like MyBatis or SpringJdbcTemplate. The second way makes more sense because you're not retrieving unnecessary data from database. So due to performance reasons you should get only data that is needed to create DTO.