Do you use unique DTOs per use case?
In Clean Architecture, it is normally better to have a unique Data Transfer Object (DTO) for each endpoint or use case.
While sharing DTOs across multiple endpoints might seem like a way to save some code, it often leads to several problems:
- Unnecessary Data Transfer: Endpoints sending more data than what is actually needed, this can lead to performance issues.
- Increased Coupling: When multiple endpoints share the same DTO, a change required by one endpoint can inadvertently affect others.
- Developer Confusion: Developers will find it hard to understand which properties are relevant for a specific endpoint, leading to potential misuse or misunderstanding of the data structure.
By creating unique DTOs tailored to each endpoint's specific requirements, you ensure that:
- Endpoints only deal with the data they need.
- Performance is optimized by avoiding the transfer of superfluous data.
- Endpoints are decoupled, making them easier to develop, test, and maintain independently.
namespace Northwind.Trading.Application.Contracts.Models;
public class OrderItemModel
{
public int OrderId { get; set; }
public string CustomerId { get; set; }
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public OrderStatus Status { get; set; }
/// <summary>
/// Used for GetOrderListEndpoint. Ignore when updating
/// </summary>
public string? ShipFromCountry { get; set; }
/// <summary>
/// Used only for GetOrdersEndpoint.
/// </summary>
public DateTimeOffset ModifiedDateUtc { get; set; }
/// <summary>
/// Detailed list of order items. Only for GetOrderDetails.
/// Not used for GetOrderList
/// </summary>
public List<OrderItemViewModel> OrderItems { get; set; } = [];
}
Figure: Bad example - OrderViewModel
is used for multiple purposes (e.g., GetOrderList
, GetOrderDetails
, CreateOrder
) and has accumulated many properties, making it hard to read and maintain.
namespace Northwind.Trading.Application.Contracts.OrderQueries.Models;
public class GetOrderListItemDto
{
public int OrderId { get; set; }
public string CustomerId { get; set; }
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public OrderStatus Status { get; set; }
}
Figure: Good example - A simple OrderSummaryDto
designed specifically for an endpoint that lists orders.