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:
By creating unique DTOs tailored to each endpoint's specific requirements, you ensure that:
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.