Audit e Change Tracking¶
Esta página detalha a direção arquitetural esperada pelo ADR-0008.
O objetivo é separar com clareza:
- audit: rastreabilidade do fato, ator, origem e mudança observável;
- revision: reconstrução estrutural de estado;
- Decorator: mecanismo preferencial quando
auditourevisionprecisam de consistência forte com a operação principal; - listener: mecanismo lateral para reporting, integração externa e reações secundárias.
Regra principal¶
Quando audit ou revision não podem virar best effort, o caso deve ser anexado por Decorator.
Isso vale principalmente para:
- alteração de estado relevante de
item; - mudanças administrativas;
- ações cujo rastro precisa existir junto com a mutação principal.
Listeners continuam válidos, mas para consumidores laterais.
Política explícita por caso¶
Cada caso auditável deve declarar explicitamente:
- qual
signalou ocorrência despachada origina o rastro; - qual sujeito foi afetado, quando existir;
- qual canal executou a ação;
- se usa
diff,snapshotou ambos; - se exige consistência forte.
Exemplo educacional:
<?php
namespace local_middag\framework\audit_policy\item;
final class item_status_updated_policy
{
public const ORIGIN = \local_middag\framework\signal\item\status_updated_signal::class;
public const SUBJECT = 'item';
public const CHANNEL = 'web';
public const USE_DIFF = true;
public const USE_SNAPSHOT = false;
public const STRONG_CONSISTENCY = true;
}
Como cada camada deve aplicar¶
Framework¶
No core, Decorator é o caminho preferencial quando o rastro deve ser persistido junto com a operação.
Exemplo educacional:
<?php
final class audited_status_updater implements status_updater_interface
{
public function __construct(
private status_updater_interface $inner,
private audit_writer_interface $audit_writer,
private revision_writer_interface $revision_writer,
) {}
public function update(int $item_id, string $status): void
{
$before = ['status' => 'draft'];
$this->inner->update($item_id, $status);
$this->revision_writer->capture(
item_id: $item_id,
snapshot: ['status' => $status],
);
$this->audit_writer->record(
origin: \local_middag\framework\signal\item\status_updated_signal::class,
subject: 'item',
subject_id: $item_id,
diff: ['status' => ['old' => $before['status'], 'new' => $status]],
);
}
}
Extension do ecossistema¶
A extension segue o mesmo padrão quando o seu próprio caso de uso exige rastreabilidade forte.
Exemplo educacional:
<?php
final class audited_order_processor implements order_processor_interface
{
public function __construct(
private order_processor_interface $inner,
private audit_writer_interface $audit_writer,
) {}
public function process(int $order_item_id): void
{
$this->inner->process($order_item_id);
$this->audit_writer->record(
origin: \local_middag\extensions\ecommerce\signal\order_processed_signal::class,
subject: 'item',
subject_id: $order_item_id,
diff: null,
snapshot: ['status' => 'processed'],
);
}
}
Diferença prática
No exemplo acima, a extension escolhe snapshot porque o valor comparável não é o ponto principal do caso. Se houver alteração campo a campo relevante, diff continua sendo o padrão.
Plugin terceiro¶
O plugin terceiro pode consumir hooks públicos ou seus próprios signals para manter rastreio externo, sem alterar o schema interno do MIDDAG.
Exemplo educacional:
<?php
use local_middag\middag;
middag::add_action('middag/extension/ecommerce/order_processed', function (object $payload): void {
external_audit_store::append([
'hook' => 'middag/extension/ecommerce/order_processed',
'data' => method_exists($payload, 'to_array') ? $payload->to_array() : [],
]);
});
Interface externa
O plugin terceiro pode manter rastreio próprio, mas isso não redefine a política de audit do core nem das extensions internas.
Quando usar diff¶
Use diff como padrão quando houver mudança comparável.
Exemplo educacional:
<?php
$diff = [
'status' => ['old' => 'draft', 'new' => 'published'],
'visibility' => ['old' => 0, 'new' => 1],
];
Isso responde melhor à pergunta: “o que mudou?”
Quando usar snapshot¶
Use snapshot quando diff não bastar para troubleshooting, compliance ou reconstrução contextual.
Exemplo educacional:
Isso responde melhor à pergunta: “em que estado relevante isso estava?”
Quando usar ambos¶
Use os dois quando o caso exigir:
- comparação objetiva do que mudou;
- e contexto suficiente para reconstrução posterior.
Exemplo educacional:
<?php
$audit_writer->record(
origin: order_processed_signal::class,
subject: 'item',
subject_id: 10,
diff: ['status' => ['old' => 'pending', 'new' => 'processed']],
snapshot: ['gateway' => 'shopify', 'channel' => 'api'],
);
Listener lateral continua válido¶
Quando o objetivo for integração externa, analytics ou reporting, listener continua sendo um caminho válido.
Exemplo educacional:
<?php
final class send_audit_to_bigquery
{
public function __invoke(
\local_middag\extensions\ecommerce\signal\order_processed_signal $signal
): void {
// reporting externo
}
}
O que não fazer¶
- não depender só de listener para
auditourevisionque exigem consistência forte; - não registrar
auditautomático para toda ocorrência despachada; - não tratar
revisioncomo sinônimo deaudit; - não acoplar plugin terceiro ao schema interno de auditoria do MIDDAG.