Skip to main content
O backend do Hub (api.hub.dommed) é um monolito modular: um único serviço, dividido internamente em módulos que não acessam os dados uns dos outros diretamente. Hoje são quatro:
  • identity: autenticação e cadastro de pessoas usuárias.
  • sector: o catálogo de setores usados para classificar automações.
  • automation: o catálogo de automações. O worker (a máquina dedicada de cada automação) é tratado como parte da automação, não como um módulo próprio: uma automação só existe no catálogo quando já tem para onde rodar, então os dois nascem juntos.
  • job: cada execução de uma automação.
Dentro de cada módulo, as entidades principais (User, Sector, Automation) são donas das próprias mudanças de estado: toda alteração relevante gera um evento de domínio, guardado junto com a mudança que o originou. É esse registro que alimenta a comunicação entre módulos, descrita abaixo. Nenhum registro é apagado de verdade no banco: desativar algo (uma automação, um setor, uma pessoa usuária) marca o registro como inativo, sem excluí-lo.

Comunicação entre módulos: eventos e o padrão Outbox

Quando um módulo precisa reagir a algo que aconteceu em outro, isso acontece por evento, não por chamada direta. O problema que esse desenho resolve: se a API publicasse o evento diretamente numa fila de mensagens no meio da requisição, uma falha de rede nesse instante deixaria o sistema inconsistente, a mudança salva no banco mas ninguém avisado dela. A solução é gravar o evento na mesma transação de banco que a mudança que o gerou (por exemplo, JobCreated é gravado junto com o Job novo). Um processo separado, o Outbox Relay, varre esses eventos pendentes a cada 2 segundos e os publica de fato na fila (RabbitMQ, exchange dom_med, uma chave de roteamento por tipo de evento). Se a publicação falhar, o evento continua marcado como pendente e é tentado de novo no próximo ciclo. O evento só é considerado entregue depois de publicado com sucesso. Eventos hoje incluem, entre outros, JobCreated (dispara o fluxo de execução descrito abaixo) e WorkerRegistered/WorkerDeactivated (mudanças na máquina dedicada de uma automação, tratadas como eventos da própria automação, não de um módulo separado).

O caminho de uma execução

Do disparo ao resultado, uma execução passa pelos seguintes passos:
  1. Uma pessoa dispara uma execução (POST /jobs). O job é criado com status pendente, e o evento JobCreated é gravado na mesma transação.
  2. O Outbox Relay publica esse evento na fila de mensagens.
  3. Em paralelo, a API tenta ligar a máquina dedicada daquela automação. Essa chamada não bloqueia a resposta: a execução já foi confirmada antes disso terminar.
  4. O worker consome o evento assim que a máquina liga. Se a máquina já estava ligada quando o job foi criado, ou se ligou antes do evento chegar, o worker também confere periodicamente se há jobs pendentes, para não depender só da fila.
  5. O worker reivindica o job (POST /jobs/{id}/start). Se outro worker já reivindicou, ou a automação está no limite de execuções simultâneas, a reivindicação é recusada e isso é esperado, não um erro.
  6. O worker executa e reporta o resultado: sucesso (POST /jobs/{id}/complete) ou falha (POST /jobs/{id}/fail).

Serviços de segundo plano

Dois processos rodam continuamente ao lado da API:
  • Outbox Relay: publica eventos pendentes a cada 2 segundos, em lotes.
  • Monitor de jobs presos: a cada 30 segundos, reenvia o comando de ligar a máquina para jobs que estão pendentes há mais de 3 minutos. Esse monitor cobre apenas jobs presos em pendente, o caso de a máquina não ter respondido à primeira tentativa de ligar. Um job que já está em execução e trava porque a máquina falhou no meio do processo não é coberto por nenhum monitor automático hoje (ver Executar uma automação, seção sobre execuções presas). São dois problemas diferentes: um reage a uma máquina que não ligou, o outro seria sobre uma máquina que ligou e depois parou de responder, e só o primeiro existe hoje.

Modelo de dados

As tabelas principais seguem os módulos de perto: users, refresh_tokens, sectors, automations, workers (um por automação), jobs, events (a fila de eventos do Outbox). Não existe uma tabela separada para passos de execução dentro de um job: cada job é uma unidade só, do início ao fim.