Pular para conteúdo

Persistência e Repositórios

A camada de persistência do MIDDAG adota o Repository Pattern para abstrair completamente o acesso ao banco de dados ($DB global do Moodle).

A regra de ouro é: Nenhum SQL deve existir fora de um Repositório. Controllers e Services nunca devem escrever queries SQL diretamente.


Arquitetura de Acesso a Dados

O sistema divide a responsabilidade de persistência em três componentes distintos, garantindo que a lógica de "como salvar" não se misture com a lógica de "o que salvar".

classDiagram
    direction LR
    class ServiceLayer {
        +create_item(dto)
        +find_courses(query)
    }

    class ItemRepository {
        <>
        +create(dto)
        +update(dto)
        +delete(id)
        +find_by_id(id)
    }

    class ItemSearchRepository {
        <>
        +run_query(criteria)
        +count_query(criteria)
        +load_metadata_bulk()
    }

    class ItemMapper {
        <>
        +db_to_domain(stdClass)
        +domain_to_db(Item)
    }

    ServiceLayer --> ItemRepository : Comandos (Write)
    ServiceLayer --> ItemSearchRepository : Consultas (Read)
    ItemRepository --> ItemMapper : Usa
    ItemSearchRepository --> ItemMapper : Usa

Criar um nome para o tópico que engloba os 3 tópicos abaixo


1. Data Mapper (item_mapper)

O Mapper (local_middag\core\repository\mapper\item_mapper) é o tradutor universal. Ele não acessa o banco de dados; sua única função é converter estruturas de dados.

Responsabilidades:

  • Hidratação (DB → Domain): Converte um registro bruto (stdClass) e um array de metadados em uma Entidade Imutável (item).
  • Extração (Domain → DB): Converte uma Entidade em um objeto simples para ser salvo pelo Moodle ($DB->insert_record).
  • Polimorfismo: Usa o registro item_types para decidir qual classe instanciar baseada no campo type do banco (ex: se type='course', instancia course_item).

2. Repositório de Escrita (item_repository)

Este repositório foca em operações atômicas de CRUD (Create, Read, Update, Delete) para itens individuais. É aqui que a integridade dos dados é garantida.

Transações Atômicas

O MIDDAG armazena dados em duas tabelas principais: middag_items (dados fixos) e middag_itemmeta (dados dinâmicos). O Repositório gerencia transações ($DB->start_delegated_transaction()) automaticamente.

Ao chamar create($dto) ou update($dto), o repositório garante que ou tudo é salvo (item + metadados) ou nada é salvo (rollback), prevenindo dados órfãos.

Exemplo de Uso:

<?php

use local_middag\core\middag;
use local_middag\core\repository\item_repository;
use local_middag\core\dto\item_dto;

$repo = middag::get(item_repository::class);

// Criação segura via DTO
$dto = new item_dto(
    fullname: 'Novo Item',
    type: 'generic',
    metadata: ['sku' => 'ABC-99']
);

$item = $repo->create($dto); // Retorna entidade hidratada

3. Repositório de Leitura (item_search_repository)

Enquanto o item_repository lida com itens únicos, o item_search_repository é o motor de SQL responsável por buscas complexas, listagens e relatórios.

Ele atua como o "motor" por trás do Query Builder.

Recursos Avançados:

  • Construção Dinâmica de SQL: Monta cláusulas WHERE, JOIN e ORDER BY de forma segura.
  • Paginação Real: Executa queries de contagem (COUNT) e seleção (SELECT) sincronizadas.
  • Carregamento em Massa (Bulk Loading): Resolve o problema de N+1 Queries. Ao buscar 50 itens, ele faz apenas 1 query adicional para buscar os metadados de todos os 50 itens de uma vez, em vez de 50 queries separadas.

O Problema N+1 e Solução

Em implementações ingênuas no Moodle, carregar metadados costuma ser lento:

<?php

// ABORDAGEM RUIM (NÃO USADA NO MIDDAG)
foreach ($items as $item) {
    // Executa uma query SQL para cada item no loop!
    $meta = $DB->get_records('middag_itemmeta', ['itemid' => $item->id]);
}

Abordagem MIDDAG (item_search_repository):

  1. Busca os IDs dos itens principais (1 Query).
  2. Busca todos os metadados onde itemid IN (ids...) (1 Query).
  3. Hidrata os objetos em memória.

Resultado: Uma lista de 1.000 itens é carregada com apenas 2 consultas ao banco, independente da quantidade de metadados.


Boas Práticas para Desenvolvedores

  1. Sempre use DTOs para escrita: Nunca passe arrays soltos para o create ou update. O DTO (item_dto) garante que você saiba quais campos são aceitos.
  2. Não instancie o Mapper: Deixe o Container de Injeção de Dependência injetar o item_mapper dentro do repositório automaticamente.
  3. Prefira o Query Builder: Evite chamar item_search_repository manualmente. Use a Query Factory para construir suas buscas de forma fluente e legível.