Pular para conteúdo

Erros e Isolamento de Falhas

Esta página detalha a direção arquitetural esperada pelo ADR-0009.

O objetivo é separar com clareza:

  • falha lateral: pode ser isolada e logada;
  • falha obrigatória da operação: deve falhar junto com o fluxo principal;
  • boundary HTTP: converte erro para resposta pública adequada;
  • boot/lifecycle: isola falhas por fronteira.

Regra principal

O framework isola a falha quando a reação é lateral e o sistema pode continuar sem comprometer a verdade da operação.

Quando a parte que falha é obrigatória, a falha deve acompanhar o fluxo principal.

Reação lateral: pode falhar isoladamente

Exemplo educacional:

<?php

final class send_order_webhook_listener
{
    public function __invoke(
        \local_middag\extensions\ecommerce\signal\order_processed_signal $signal
    ): void {
        try {
            // integração externa derivada
        } catch (\RuntimeException $exception) {
            debug::trace_exception($exception);
        }
    }
}

Se o webhook falhar, o pedido não deve “desprocessar”.

Parte obrigatória da operação: não pode falhar isoladamente

Exemplo educacional:

<?php

final class audited_status_updater
{
    public function __construct(
        private repository_interface $repository,
        private item_service $item_service,
        private audit_writer_interface $audit_writer,
        private revision_writer_interface $revision_writer,
    ) {}

    public function update(int $item_id, string $status): void
    {
        $this->repository->begin();

        try {
            $this->item_service->update_status($item_id, $status);
            $this->revision_writer->capture($item_id, ['status' => $status]);
            $this->audit_writer->record(subject_id: $item_id, diff: [
                'status' => ['old' => 'draft', 'new' => $status],
            ]);

            $this->repository->commit();
        } catch (\Throwable $exception) {
            $this->repository->rollback();
            throw $exception;
        }
    }
}

Aqui audit e revision fazem parte da verdade da operação. Se falharem, a operação falha também.

Boundary HTTP

Controller não deve transformar toda exceção em silêncio local. O boundary HTTP decide a resposta pública.

Exemplo educacional:

<?php

final class course_api_controller extends base_api_controller
{
    public function save(): Response
    {
        $this->service->save();

        return $this->json_response(['status' => 'ok']);
    }
}

Se save() falhar, a exceção sobe para o HTTP kernel, que decide status code e payload público.

Boot de extension

Falha de uma extension não deve derrubar as demais.

Exemplo educacional:

<?php

try {
    $extension->boot();
} catch (\Throwable $exception) {
    debug::trace_exception($exception);
}

Validação depende do boundary

Em formulário ou API, validação pode virar resposta estruturada.

Exemplo educacional:

<?php

return $this->json_response([
    'error' => 'validation_failed',
    'messages' => ['name' => 'Campo obrigatório'],
], 422);

Em serviço interno, a mesma invalidez pode continuar sendo exceção.

O que não fazer

  • não capturar erro crítico só para “seguir o fluxo”;
  • não usar listener, hook ou bridge para esconder dependência obrigatória;
  • não capturar e silenciar Throwable genericamente sem contexto;
  • não expor detalhes internos em resposta pública fora de modo debug.