Construindo fluxos de trabalho orientados a eventos
Coordene serviços por meio de eventos e idempotência.
Construindo fluxos de trabalho orientados a eventos é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.
Fluxos de trabalho orientados a eventos
Uma única mensagem é fácil. Um fluxo de trabalho — “pedido realizado → reservar estoque → cobrar cartão → enviar → notificar”, abrangendo vários serviços — é onde o projeto orientado a eventos mostra seu valor e também causa problemas se for feito de forma ingênua.
Esta lição aborda coreografia e orquestração, o padrão de caixa de saída para publicação atômica, sagas para reversão distribuída e a idempotência que mantém tudo unido.
Coreografia vs orquestração
Duas formas de coordenar fluxos de várias etapas:
- Coreografia — cada serviço reage a eventos e emite seus próprios eventos; não há cérebro central. O acoplamento é fraco, mas o fluxo geral é implícito e difícil de rastrear.
- Orquestração — um coordenador central diz a cada serviço o que fazer em seguida. É explícita e observável, mas o orquestrador é um ponto de acoplamento.
Regra prática: use coreografia para uma distribuição simples a vários destinatários e orquestração quando um fluxo tiver muitas etapas ordenadas e precisar de um estado visível.
Eventos vs comandos
Nomeie suas mensagens deliberadamente:
- Um evento declara um fato passado:
OrderPlaced. O publicador não se importa com quem escuta. - Um comando solicita uma ação futura a um manipulador específico:
ChargeCard.
Eventos orientam a coreografia; comandos orientam a orquestração. Misturar a terminologia (um “evento” que secretamente espera um único manipulador) é uma fonte comum de acoplamento oculto.
<?php
final class OrderPlaced {
public function __construct(
public readonly string $orderId,
public readonly string $customerId,
public readonly int $amountCents,
public readonly string $occurredAt,
) {}
}
$e = new OrderPlaced('o-42', 'c-7', 1990, gmdate('c'));
echo json_encode($e), "\n";Problema da gravação dupla
O erro clássico: um manipulador atualiza the banco de dados e publica uma mensagem como duas operações separadas. Se o processo terminar entre elas, você terá uma inconsistência — a linha foi alterada, mas nenhum evento foi enviado, ou o contrário.
<?php
// BROKEN: not atomic. A crash between the two lines corrupts state.
function placeOrder(PDO $db, $broker, array $o): void {
$db->prepare('INSERT INTO orders ...')->execute($o);
// <-- crash here = row exists but no event ever published
$broker->publish('OrderPlaced', json_encode($o));
}Caixa de saída transacional
A solução é o padrão de caixa de saída: dentro da mesma transação do banco de dados que altera seus dados, insira o evento em uma tabela outbox. Um processo retransmissor separado lê as linhas não publicadas e as envia ao intermediário. Uma confirmação atômica, sem gravação dupla.
<?php
function placeOrder(PDO $db, array $o): void {
$db->beginTransaction();
$db->prepare('INSERT INTO orders (id, total) VALUES (?, ?)')
->execute([$o['id'], $o['total']]);
// Same transaction -> atomic with the business write
$db->prepare('INSERT INTO outbox (id, type, payload) VALUES (?, ?, ?)')
->execute([bin2hex(random_bytes(8)), 'OrderPlaced', json_encode($o)]);
$db->commit();
}O retransmissor (publicador)
Um processo consulta periodicamente a caixa de saída (ou acompanha o registro de alterações do banco de dados por meio de CDC), publica cada linha e depois a marca como enviada. Como o retransmissor pode falhar depois da publicação, mas antes da marcação, ele próprio opera pelo menos uma vez — o que não é um problema, pois os consumidores são idempotentes.
<?php
function relayOutbox(PDO $db, $broker): void {
$rows = $db->query(
'SELECT id, type, payload FROM outbox
WHERE published_at IS NULL ORDER BY created_at LIMIT 100'
)->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $r) {
$broker->publish($r['type'], $r['payload'], messageId: $r['id']);
$db->prepare('UPDATE outbox SET published_at = now() WHERE id = ?')
->execute([$r['id']]);
}
}Consumidores idempotentes (novamente)
Como tanto o retransmissor quanto the intermediário operam pelo menos uma vez, os manipuladores posteriores verão duplicatas. Cada consumidor registra o identificador da mensagem que processou e interrompe imediatamente as repetições — a mesma barreira de deduplicação que você aprendeu anteriormente, agora aplicada por serviço.
<?php
function onOrderPlaced(PDO $db, string $messageId, array $data): void {
$db->beginTransaction();
try {
$db->prepare('INSERT INTO inbox (message_id) VALUES (?)')
->execute([$messageId]); // unique index = dedup
} catch (PDOException $e) {
$db->rollBack();
return; // already handled this message
}
reserveStock($data['orderId']);
$db->commit();
}
function reserveStock(string $id): void {}Sagas: reversão distribuída
Você não pode abrir uma única transação ACID entre serviços. Uma saga modela um fluxo de longa duração como uma sequência de transações locais, cada uma com uma ação de compensação que a desfaz. Se a etapa 3 falhar, execute as compensações das etapas 2 e 1 na ordem inversa.
Exemplo: o pagamento falha depois que o estoque foi reservado → emita ReleaseStock para compensar. Não há reversão automática — você precisa projetar a ação de desfazer para cada etapa.
Uma saga orquestrada
Um orquestrador conduz a saga: avança quando há sucesso e despacha compensações em caso de falha. Persista the estado da saga para que uma falha possa retomá-la.
<?php
function handleStepResult(array $saga, string $step, bool $ok, $bus): array {
if ($ok) {
$next = ['reserveStock' => 'chargeCard', 'chargeCard' => 'ship'][$step] ?? null;
if ($next) { $bus->send($next, $saga['orderId']); $saga['state'] = $next; }
else { $saga['state'] = 'completed'; }
} else {
// Run compensations in reverse for whatever already succeeded
foreach (array_reverse($saga['done']) as $s) {
$bus->send('compensate.' . $s, $saga['orderId']);
}
$saga['state'] = 'compensating';
}
return $saga;
}Tempos limite em fluxos longos
Uma etapa da saga pode simplesmente nunca responder — the serviço de pagamento está indisponível ou uma aprovação humana nunca chega. Sem um tempo limite, the saga fica suspensa para sempre, mantendo reservas. Persista um prazo final por etapa; um agendador procura sagas atrasadas e aciona the caminho de falha ou compensação.
<?php
function reapTimedOutSagas(PDO $db, $bus): void {
$rows = $db->query(
"SELECT order_id, state FROM sagas
WHERE state NOT IN ('completed','compensating')
AND deadline_at < now()"
)->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $r) {
echo "Saga {$r['order_id']} timed out at step {$r['state']}\n";
$bus->send('saga.compensate', $r['order_id']); // trigger rollback
}
}Versionamento e observabilidade
Os fluxos de trabalho duram anos; os eventos precisam evoluir com segurança:
- Adicione uma
version(ou um esquema) a cada evento; os consumidores devem tolerar novos campos desconhecidos e nunca presumir que um campo esteja presente. - Prefira alterações aditivas; nunca reaproveite o significado de um campo existente.
- Propague um identificador de correlação por todas as mensagens para rastrear uma transação de negócio entre todos os serviços nos seus registros/rastreamentos.
Sem identificadores de correlação, depurar um fluxo coreografado que atravessa cinco serviços é quase impossível.
<?php
$envelope = [
'type' => 'OrderPlaced',
'version' => 2,
'correlationId' => $incoming['correlationId'] ?? bin2hex(random_bytes(8)),
'occurredAt' => gmdate('c'),
'data' => ['orderId' => 'o-42'],
];
echo json_encode($envelope, JSON_PRETTY_PRINT), "\n";Verificação rápida
Evitando o problema da gravação dupla.
Recapitulação
Agora você pode projetar fluxos de trabalho confiáveis orientados por eventos:
- Escolha coreografia (eventos) ou orquestração (comandos) de acordo com a complexidade do fluxo.
- Resolva a gravação dupla com uma caixa de saída transacional e um retransmissor.
- Torne cada consumidor idempotente usando uma chave de caixa de entrada/desduplicação.
- Use sagas com ações compensatórias para desfazer operações distribuídas.
- Versione os eventos com alterações aditivas e inclua um identificador de correlação para garantir a rastreabilidade.
Esses padrões transformam mensagens soltas em processos de negócio confiáveis e observáveis.
Perguntas Frequentes
A aula “Construindo fluxos de trabalho orientados a eventos” é grátis?
Sim — o texto completo de “Construindo fluxos de trabalho orientados a eventos” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.
O que vou aprender em “Construindo fluxos de trabalho orientados a eventos”?
Coordene serviços por meio de eventos e idempotência. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar PHP Academy?
Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “Construindo fluxos de trabalho orientados a eventos”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de PHP Academy?
Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Por que usar mensageria assíncrona
- Trabalhando com RabbitMQ em PHP
- Apache Kafka com PHP
- Construindo fluxos de trabalho orientados a eventos