Pular para conteúdo

Persistência e Repositories

No MIDDAG, repository é a fronteira oficial entre domínio/aplicação e storage físico.

A regra central é simples: services, facades, controllers e extensions não devem conhecer SQL, nomes de tabela, metadata storage ou lógica de hidratação.

O papel do repository

Repository existe para:

  • esconder tabelas, joins e estratégias de persistência;
  • reconstruir objetos limpos para uso em services;
  • concentrar transações e consistência entre tabela principal e satélites;
  • mapear storage físico para famílias arquiteturais como item, revision, audit e job.

Estrutura conceitual

classDiagram
    direction LR
    class ServiceLayer {
        +create_item(dto)
        +find_item(id)
    }

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

    class Mapper {
        <>
        +db_to_domain(record, metadata)
        +domain_to_db(entity)
    }

    class Storage {
        <>
        middag_items
        middag_itemmeta
    }

    ServiceLayer --> Repository
    Repository --> Mapper
    Repository --> Storage

Como cada camada deve aplicar

Os exemplos abaixo são educacionais. Eles mostram o padrão esperado.

Framework

<?php

namespace local_middag\framework\application\service\company;

use local_middag\framework\contract\repository\item_repository_interface;

final class company_service
{
    public function __construct(
        private item_repository_interface $item_repository,
    ) {}

    public function load(int $item_id): object|null
    {
        return $this->item_repository->find_by_id($item_id);
    }
}

Aqui o service conhece apenas o repository. Ele não conhece tabela, SQL nem mapper.

Extension do ecossistema

<?php

namespace local_middag\extensions\ecommerce\service;

use local_middag\framework\contract\repository\item_repository_interface;

final class order_service
{
    public function __construct(
        private item_repository_interface $item_repository,
    ) {}

    public function load_order(int $item_id): object|null
    {
        return $this->item_repository->find_by_id($item_id);
    }
}

A extension continua consumindo o mesmo boundary de persistência do core.

Plugin terceiro

Consumindo o boundary do MIDDAG

<?php

use local_middag\framework\contract\repository\item_repository_interface;
use local_middag\middag;

middag::init();

$repository = middag::get(item_repository_interface::class);
$item = $repository->find_by_id(42);

Usando tabela própria

<?php

global $DB;

$records = $DB->get_records('local_partner_orders', ['status' => 'pending']);

O plugin terceiro continua livre para manter storage próprio. O que ele não deve fazer é tratar tabelas internas do MIDDAG como API pública estável.

Mapper e DTO

Repository não faz tudo sozinho.

  • DTO transporta dados de escrita;
  • Mapper traduz entre storage físico e domínio;
  • Repository coordena leitura, escrita, transação e reconstrução.
<?php

final class item_write_dto
{
    public function __construct(
        public readonly string $type,
        public readonly string $fullname,
        public readonly array $metadata = [],
    ) {}
}
<?php

final class item_mapper
{
    public function domain_to_db(object $entity): \stdClass
    {
        return new \stdClass();
    }

    public function db_to_domain(\stdClass $record, array $metadata): object
    {
        return new \stdClass();
    }
}

Famílias de persistência

Repositories existem para impedir que essas distinções vazem como detalhes de schema:

  • Current state: middag_items e middag_itemmeta
  • Revision history: middag_item_revision e middag_item_revision_meta
  • Audit trail: middag_audit_log, middag_audit_diff e middag_audit_snapshot
  • Job governance: middag_job e middag_job_attempt

O que não fazer

<?php

namespace local_middag\extensions\ecommerce\service;

final class bad_order_service
{
    public function count_pending(): int
    {
        global $DB;

        return $DB->count_records('middag_items', ['type' => 'order']);
    }
}

Esse padrão vaza storage físico para fora do boundary correto.