Thiago Cantero

Tecnologia e Entretenimento

Arquitetura de SoftwareBanco de DadosLaravelPadrões de ProjetoPHP

Lazy load e Eager Load – Conceitos aplicados com Eloquent.

Olá, mundo! Tudo bem?

Quando nos deparamos em uma aplicação, com certa complexidade, atuando no paradigma de Orientação a Objetos, das relações entre as tabelas de nosso banco de dados, utilizamos um ORM (caso não saiba o que significa veja estes artigos ORM Parte I e ORM Parte II). Bom devemos sempre nos pautar desta premissa para que tenhamos um melhor desempenho, diminuindo o tempo da execução, e até mesmo evitando o esvaziamento de pilha de memória (aqui também tem um artigo interessante sobre gerenciamento de memória).

Introdução

Vejamos a conceituação do Padrão Lazy Load (carga tardia), de Martin Fowler:

Para carregar dados de um banco de dados para a memória, é conveniente projetar as coisas de modo que quando você carregar um objeto de interesse também carregue os objetos que sejam relacionados a ele. Isso torna a carga mais fácil para o desenvolvedor que estiver usando o objeto, que de outra forma tem que carregar explicitamente todos os objetos de que precisa. Todavia, se você levar isso ao seu final, chegará ao ponto em que carregar um objeto pode ter o mesmo efeito de carregar um grande número de objetos relacionados – algo que prejudica o desempenho quando apenas alguns dos objetos são realmente necessários.
Uma Carga Tardia interrompe este processo de carga por um tempo, deixando um marcador na estrutura do objeto de modo que se os dados forem necessários podem ser carregados apenas quando forem usados. Como muitas pessoas sabem, se você deixar as coisas para mais tarde vai se dar bem quando descobrir que não precisava realmente fazê-las. Padrões de Arquiteturas de Aplicações Corporativas, pág. 200, Martin Fowler, Editora Bookman 2006.

Conforme podemos abstrair desta breve explanação de Martin Fowler, o padrão Lazy Load carrega objetos de relações sob demanda, desonerando muito menos os recursos de seu servidor de banco de dados, uma vez que a memória não ficará reservada com registros desnecessários.

Em contrapartida temos o Eager Load, que ao contrário do Lazy Load, carrega todos os objetos em memória. Resumidamente, é o oposto do Lazy Load.

Vamos ao trabalho\\

Em nosso Exemplo utilizaremos duas Tabelas, uma Tabela Estado, cuja a relação será de vários municípios. Ou seja, a relação 1:N, conforme o Diagrama de Entidade/Relacionamento abaixo:

 

Vamos criar nossa aplicação Laravel.

composer create-project laravel/laravel lazy_eager

Criamos nosso Banco de Dados.

CREATE DATABASE LARAVEL_EAGER_LAZY;

Configuramos o arquivo de ambiente .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_eager_lazy
DB_USERNAME=<nome do usuário>
DB_PASSWORD=<senha do usuário>

Nosso próximo passo é a criação de nossas Classes Models e Migrations:

php artisan make:model Estado -m

database/migrations/2022_10_06_143710_create_estados_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateEstadosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('estados', function (Blueprint $table) {
            $table->id();
            $table->string('nome');
            $table->string('uf');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('estados');
    }
}

Criamos a Model e a Migration para o Estado, agora iremos criar a Model e a Migration do Município, atentando-se que iremos apontar o relacionamento para a tabela Estados.

php artisan make:model Cidades -m

database/migrations/2022_10_06_144911_create_cidades_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCidadesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('cidades', function (Blueprint $table) {
            $table->id();
            $table->string('nome');
            $table->string('codigo_ibge');
            $table->unsignedBigInteger('estado_id');
            $table->foreign('estado_id')->references('id')->on('estados')->onDelete('cascade');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('cidades');
    }
}

Feito isso, já podemos fazer nossa migração.

php artisan migrate

Pronto, agora vamos ajustar nossas Classes Models:

app/Models/Cidade.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Cidade extends Model
{
    protected $fillable = ['nome', 'codigo_ibge', 'estado_id'];
    
    public function Estado()
    {
        return $this->belongsTo(Estado::class);
    }
}

app/Models/Estado.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Estado extends Model
{
    protected $fillable = ['nome', 'uf'];
 
    public function Cidade()
    {
        return $this->hasMany(Cidade::class);
    }
}

Pronto, agora vamos popular nosso banco com três estados, e suas capitais, respectivamente.

INSERT INTO `cidade` (`id`, `nome`, `codigo_ibge`, `estado_id`, `created_at`, `updated_at`) VALUES
(3106200, 'BELO HORIZONTE', '3106200', 31, '2022-10-06 18:28:33', '2022-10-06 18:28:33'),
(3304557, 'RIO DE JANEIRO', '3304557', 33, '2022-10-06 18:30:22', '2022-10-06 18:30:22'),
(3550308, 'SÃO PAULO', '3550308', 35, '2022-10-06 18:28:33', '2022-10-06 18:28:33');

INSERT INTO `estado` (`id`, `nome`, `uf`, `created_at`, `updated_at`) VALUES
(31, 'Minas Gerais', 'MG', '2022-10-06 18:27:46', '2022-10-06 18:27:46'),
(33, 'Rio de Janeiro', 'RJ', '2022-10-06 18:19:34', '2022-10-06 18:19:34'),
(35, 'São Paulo', 'SP', '2022-10-06 18:19:34', '2022-10-06 18:19:34');

Agora vamos para a prática.

Lazy Load

No padrão Lazy Load, pré-carregamos uma coleção de todas as Cidades.

$cidades = Cidade::all();

É a mesma consulta em SQL desta maneira:

SELECT * FROM CIDADES;

Agora faremos a implementação na prática do padrão Lazy Load:

foreach ($cidades as $cidade) {
    echo $cidade->estado->nome; //aplicando o Lazy Load, acessando as propriedades de outra tabela
}

Como podemos notar, pelas Cidades acessamos a Tabela Estado e o campo estado.

A query de consulta por trás é:

select * from `cidades` where `cidades`.`id` = 31 limit 1
select * from `cidades` where `cidades`.`id` = 33 limit 1
select * from `cidades` where `cidades`.`id` = 35 limit 1

Isso é tudo automatizado pelo ORM do Laravel, o Eloquent.

O uso do Lazy Load é indicado quando trabalhamos com uma alta carga de dados em nosso banco, por exemplo, milhares de registro e não podemos carregar tudo de uma vez, pois causaria latência e até mesmo esvaziamento de pilha, término de sessão, lentidão, e uma outra série de problemas.

Por essa razão utilizamos este padrão.

Eager Load

Em algumas situações é interessante o uso deste padrão para realizar chamadas em AJAX, por exemplo, em um Datatable, em que pré-carregamos os objetos e suas relações, que serão disponibilizados em uma paginação de forma antecipada, e ao passarmos de página veremos os dados que já foram devidamente carregados em memória.

Cidade::with('estado:id')->get();

Como você pode notar, ele já carrega todas as relações de Estado e Cidades com a palavra reservada: with, apontando o campo da chave estrangeira estado_id.

Isto é uma query SQL é algo similar a isso aqui:

SELECT * FROM CIDADES
SELECT * FROM ESTADOS WHERE ID IN (1, 2, 3...)

Bom, espero que tenha ficado bem claro os conceitos destas maneiras de consulta que são utilizadas em qualquer ORM do mercado.

Código Fonte no Github aqui.

Obrigado : )

 

Os regulamentos assemelham-se aos ritos de uma religião, que parecem absurdos, mas moldam os homens.