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,auditejob.
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
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_itemsemiddag_itemmeta - Revision history:
middag_item_revisionemiddag_item_revision_meta - Audit trail:
middag_audit_log,middag_audit_diffemiddag_audit_snapshot - Job governance:
middag_jobemiddag_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.