No desenvolvimento de aplicações monolíticas tradicionais, utilizamos o padrão CRUD (Create, Read, Update, Delete) partilhando o mesmo modelo de dados para leitura e escrita.
Em sistemas de pequena escala, esta abordagem é perfeitamente aceitável e rápida.
Contudo, quando o portal cresce e a complexidade do domínio de negócio aumenta, o modelo de leitura começa a exigir junções (JOINs) massivas no banco de dados para apresentar as informações no ecrã do utilizador.
A escrita, por sua vez, torna-se lenta devido à validação de regras de negócio complexas na mesma transação.
A resposta da engenharia de software para este estrangulamento de performance é o padrão CQRS (Command Query Responsibility Segregation).
O CQRS propõe a separação física e lógica entre as operações que alteram o estado do sistema (Commands) e as operações que apenas devolvem dados (Queries).
Nesta masterclass, vamos projetar um sistema CQRS em PHP puro, dispensando frameworks pesados que adicionam atrasos desnecessários.
Vamos construir uma arquitetura que permite escalar leituras e escritas de forma totalmente independente no ecossistema WordPress.
A Teoria da Separação de Responsabilidades
Para implementar o CQRS, devemos tratar cada intenção do utilizador como um Comando (Command).
Um Comando é um objeto imutável que carrega apenas os dados necessários para executar uma ação específica.
Por exemplo, em vez de uma função genérica para atualizar um utilizador, teremos um comando específico chamado AtualizarMoradaUtilizadorCommand.
Este comando será enviado para um Command Bus, que atua como um carteiro, entregando a mensagem ao Command Handler correspondente.
O Handler executa as regras de negócio puras, interage com as tabelas de escrita otimizadas e emite um Evento (Event).
O Evento notifica o sistema de que algo aconteceu no passado.
Este é o gatilho para atualizar as nossas tabelas de leitura.
A grande vantagem é que as tabelas de leitura são totalmente desnormalizadas e otimizadas para consultas instantâneas, não exigindo JOINs complexos.
<?php
namespace MundoPHPArquiteturaCQRS;
// Objeto de Comando Imutável usando Readonly (PHP 8.2+)
readonly class RegistarUtilizadorCommand {
public function __construct(
public string $idUnico,
public string $nomeCompleto,
public string $enderecoEmail
) {}
}
// O Handler processa as regras de negócio
class RegistarUtilizadorHandler {
public function __invoke(RegistarUtilizadorCommand $comando): void {
global $wpdb;
// Simulação de validação de negócio estrita
if (empty($comando->enderecoEmail)) {
throw new InvalidArgumentException("O e-mail é obrigatório");
}
// Inserção na Tabela de Escrita (Write Model)
$wpdb->insert(
"wp_write_utilizadores",
[
"uuid" => $comando->idUnico,
"nome" => $comando->nomeCompleto,
"email" => $comando->enderecoEmail,
"estado" => "pendente"
]
);
// Emissão do Evento de Domínio
do_action("mundophp_utilizador_registado_evento", $comando);
}
}
Projetores e Modelos de Leitura (Read Models)
Quando o evento “mundophp_utilizador_registado_evento” é disparado, entram em cena os Projetores (Projectors).
A função de um Projetor é escutar eventos do domínio e construir representações visuais pré-computadas para o utilizador.
Imagine que o ecrã inicial do seu painel precisa de um contador total de utilizadores registados hoje e uma lista resumida.
Em vez de usar COUNT() e GROUP BY na tabela principal em cada carregamento de página, o Projetor atualiza uma tabela de leitura específica no exato momento em que o evento ocorre.
Quando o utilizador acede ao painel, a Query de leitura apenas devolve um registo simples e estático do banco de dados.
Esta técnica de desnormalização elimina os estrangulamentos de I/O no disco e reduz o consumo de CPU a valores negligenciáveis.
<?php
namespace MundoPHPArquiteturaProjetores;
class PainelEstatisticasProjetor {
public function construirVisaoRegisto(RegistarUtilizadorCommand $evento): void {
global $wpdb;
// Atualiza a tabela desnormalizada focada apenas na Leitura
$wpdb->query(
"UPDATE wp_read_estatisticas_painel
SET total_registos = total_registos + 1,
ultimo_registo = NOW()
WHERE id_estatistica = 1"
);
// Regista um ficheiro de auditoria ou cache em memória
wp_cache_delete("estatisticas_painel_home", "mundophp_cqrs");
}
}
// O Hook do WordPress atua como o nosso Event Bus simplificado
add_action(
"mundophp_utilizador_registado_evento",
[new PainelEstatisticasProjetor(), "construirVisaoRegisto"]
);
A Introdução ao Event Sourcing
O CQRS brilha intensamente quando combinado com o padrão Event Sourcing.
Numa base de dados tradicional, guardamos apenas o estado atual do objeto.
Se um utilizador muda de nome três vezes, perdemos o histórico anterior.
No Event Sourcing, a fonte primária de verdade não é o estado atual, mas sim um fluxo sequencial de todos os eventos que ocorreram (Event Store).
A tabela principal guarda registos como “UtilizadorRegistado”, “NomeAlterado”, “EmailConfirmado”.
Para obter o estado atual, o sistema reproduz todos os eventos na memória do servidor desde o início dos tempos.
Embora pareça custoso, a leitura de uma tabela de eventos append-only (apenas inserção) é incrivelmente rápida e imune a bloqueios de base de dados.
Se o seu modelo de leitura for corrompido, basta apagar a tabela de leitura e mandar o sistema reconstruí-la lendo o Event Store do zero.
CREATE TABLE wp_event_store (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
aggregate_id VARCHAR(36) NOT NULL,
tipo_evento VARCHAR(255) NOT NULL,
payload_json JSON NOT NULL,
registado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_aggregate (aggregate_id, registado_em)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;
Consistência Eventual e UX Sênior
Uma consequência natural do CQRS é a Consistência Eventual (Eventual Consistency).
Dado que a escrita na tabela principal e a atualização da tabela de leitura ocorrem em momentos ligeiramente diferentes, existe uma janela temporal de milissegundos onde os dados podem parecer desatualizados.
Em sistemas orientados a eventos (usando infraestruturas modernas que dispensam o antigo mpm-prefork em favor de processamento assíncrono real), esta latência é invisível.
Contudo, o programador sênior deve desenhar a interface do utilizador de forma inteligente.
Quando um comando é enviado via AJAX, a interface deve atualizar otimisticamente os dados no ecrã sem esperar a confirmação de que o modelo de leitura foi reconstruído.
O domínio da psicologia do utilizador é tão vital quanto o domínio do código.
Aplicações Práticas em Sistemas Financeiros
Sistemas bancários nunca atualizam o saldo de uma conta com um comando UPDATE convencional.
Eles registam um evento de “Crédito” ou “Débito” no Event Store, e o projetor calcula o saldo atualizado de forma matemática num processo paralelo.
Aplicar esta mesma filosofia aos seus plugins de e-commerce no WordPress garante que nunca perderá uma transação por falhas de concorrência ou bloqueios de tabelas pesadas.
O registo inalterável de eventos atua como uma auditoria financeira perfeita e infalível.
Esta é a verdadeira escalabilidade horizontal a nível de código-fonte.
Reflexão Arquitetural
Adotar o CQRS exige uma mudança de paradigma profunda na mente do programador.
Temos de abandonar a conveniência imediata do CRUD e abraçar a complexidade inicial em troca de uma escalabilidade infinita a longo prazo.
Não precisamos de instalar ferramentas massivas para orquestrar isto; o PHP nativo possui todas as construções de linguagem necessárias.
O isolamento entre a intenção de modificar o estado e o desejo de visualizar informações garante que a nossa aplicação seja resiliente a picos de tráfego extremos.
Continue a aperfeiçoar o seu domínio sobre arquiteturas de alto rendimento.
O MundoPHP acredita que o futuro pertence aos engenheiros que compreendem o porquê de cada decisão técnica.


