Uno de los mayores problemas que suelen tener las aplicaciones, y que a menudo pasa desapercibido, es la realización de la misma operación repetidamente dentro de un bucle.

Cómo cada operación dura muy poco y la base de datos no se “queja”, suele pasar desapercibido a la hora de monitorizar el rendimiento.

Si usas Sentry, lo tienes en el monitor de N+1 Queries.

Voy a poner un ejemplo sencillo.

Vamos a mostrar un listado de pedidos de un cliente, y tenemos un método que devuelve un Dto que usaremos en el API.

<aside> 💡 NOTA: Alguno de vosotros, vais a pensar que no tenéis este problema, por que montáis una mega Query o hacéis Eager Loading, etc.. Este tipo de soluciones no escalan bien en proyectos grandes. Si tu opinión es diferente, te aconsejo que no sigas leyendo.

</aside>

Por un lado tenemos el Dto que vamos a devolver, que tiene datos del pedido y del cliente.

final readonly class OrderListItemDto{   
    public function __construct(
        public OrderId $orderId,
        public string $orderNumber,
        public string $orderStatus,
        public string $orderDate,
        public string $customerName,
        public string $customerEmail
        ){
        }
 }

Ahora tenemos un servicio que devuelve el listado de pedidos de un cliente:

final readonly class OrderListService
{
    public function getOrderList(UserId $userId): array
    {
        /** @var array<OrderListItemDto> $result */
        $result = [];
        $orders = $this->orderRepository->findByUserId($userId);
        foreach ($orders as $order) {
            $result[] = $this->buildDto($order);
        }
        return $result;
    }

    private function buildDto(Order $order): OrderListItemDto
    {
        $customer = $this->customerRepository->findByUserId($order->userId);
        return new OrderListItemDto(
            orderId: $order->orderId,
            orderNumber: $order->orderNumber,
            orderStatus: $order->orderStatus,
            orderDate: $order->orderDate,
            customerName: $customer->customerName,
            customerEmail: $customer->customerEmail
        );
    }
}