Orbit
Um agendador de jobs cujas fundações existem e cujo agendador, ainda não.
Visão geral
O Orbit pretende ser um agendador de jobs distribuído e self-hosted: você define jobs, o Orbit os executa em um horário ou sob demanda, e cada execução é registrada separadamente, de modo que exista um histórico do que rodou, quando e com qual resultado. No desenho pretendido, workers em máquinas diferentes recebem as execuções e reportam os resultados.
O Orbit ainda não agenda nem executa nada. Hoje o servidor guarda definições de jobs, registra execuções e impõe as regras do ciclo de vida de uma execução. Nenhum código lê uma expressão cron, inicia um comando, aplica um timeout ou repete uma falha.
Estado atual
Conferido no repositório de código, no commit 2967494, o mais recente entre 21 commits. O repositório não tem workflow de integração contínua nem release.
| Capacidade | Estado | Observações |
|---|---|---|
| Criar, listar, ler, atualizar, habilitar e desabilitar jobs | implementado | API REST em JobController; não há endpoint de exclusão |
| Registrar uma execução de um job sob demanda | implementado | cria um registro QUEUED; nada o consome |
| Listar e ler execuções | implementado | da mais nova para a mais antiga, por job |
| Máquina de estados da execução | implementada só no domínio | start, succeed, fail e cancel são testados, mas nenhum serviço ou endpoint os chama |
| Esquema e migrações | implementado | Flyway V1 e V2 sobre PostgreSQL |
| Agendamento por cron | planejado | a expressão é guardada e verificada quanto a estar em branco, não é interpretada |
| Workers, reivindicação e heartbeats | planejado | não existe módulo de worker |
| Timeouts | planejado | timeout_seconds é guardado, não aplicado |
| Retentativas e backoff | planejado | max_retries é guardado; a semântica das retentativas ainda é uma issue de design |
| Cancelamento de trabalho em andamento | planejado | a transição de estado existe, a propagação a um worker não |
| Logs e atualizações ao vivo | planejado | não há armazenamento nem transmissão de logs |
| Autenticação | planejado | a API aceita requisições sem autenticação |
| Interface web e empacotamento | planejado | não há aplicação Angular nem imagem da aplicação |
As linhas planejadas correspondem a issues abertas no repositório, por exemplo o laço do agendador, a reivindicação atômica de execuções, a perda de worker, a semântica de retentativas e a autenticação. Várias estão marcadas como dependentes de uma decisão antes da implementação.
Arquitetura
O servidor é uma aplicação Spring Boot 4.1.1 em Java 25, construída com Maven. Usa Spring Web MVC, Spring Data JPA, Bean Validation e Flyway, e conversa com o PostgreSQL. O único arquivo de infraestrutura é um serviço de Docker Compose que sobe o PostgreSQL 17 para desenvolvimento.
O código é organizado por funcionalidade e depois por camada:
dev.raioviajante.orbit job/ domain · application · infrastructure · web execution/ domain · application · infrastructure · webA camada de domínio contém entidades JPA que protegem as próprias invariantes. A camada de aplicação contém os serviços, a de infraestrutura contém os repositórios do Spring Data, e a web contém os controladores e os records de requisição e resposta. As respostas são records explícitos, então as entidades nunca saem do servidor.
spring.jpa.hibernate.ddl-auto está em validate: o Hibernate confere o esquema mas nunca o altera, e o Flyway é dono de toda mudança. spring.jpa.open-in-view está desligado.
Modelo de dados
Um job descreve um trabalho. Uma execução é uma rodada de um job. Um job tem várias execuções, e uma execução pertence a exatamente um job.
CREATE TABLE jobs ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(120) NOT NULL, command TEXT NOT NULL, cron_expression VARCHAR(100), enabled BOOLEAN NOT NULL DEFAULT TRUE, max_retries INTEGER NOT NULL DEFAULT 0, timeout_seconds INTEGER NOT NULL DEFAULT 300, -- created_at e updated_at omitidos CONSTRAINT jobs_max_retries_check CHECK (max_retries >= 0), CONSTRAINT jobs_timeout_seconds_check CHECK (timeout_seconds > 0));A tabela executions repete command, max_retries e timeout_seconds do job, e acrescenta status, created_at, started_at, finished_at, exit_code e error_message. Uma restrição do banco limita status aos cinco valores conhecidos, e um índice cobre job_id.
Copiar as três configurações é proposital: uma execução nasce de um instantâneo do job, então editar o job depois não reescreve o que uma execução passada devia rodar. As notas de arquitetura mencionam um número de tentativa para as execuções, mas o esquema ainda não tem essa coluna.
Ciclo de vida da execução
Uma execução começa como QUEUED. O objeto de domínio aceita apenas estas transições e rejeita qualquer outra com uma IllegalStateException:
QUEUED --start--> RUNNINGRUNNING --succeed--> SUCCEEDED (registra o código de saída)RUNNING --fail--> FAILED (registra o código de saída e uma mensagem não vazia)QUEUED | RUNNING --cancel--> CANCELLEDSUCCEEDED, FAILED e CANCELLED são terminais: nenhum método sai deles. Um job desabilitado não pode criar execuções, e pedir uma resulta em um conflito. Como nada chama start, succeed ou fail fora dos testes, toda execução criada pela API permanece QUEUED.
Validação
A entrada é conferida duas vezes. Os records de requisição usam Bean Validation: o nome é obrigatório e tem no máximo 120 caracteres, o comando é obrigatório, a expressão cron é opcional mas não pode estar em branco nem passar de 100 caracteres, as retentativas devem ser zero ou mais, e o timeout deve ser maior que zero. As classes de domínio então impõem a maior parte das mesmas regras de novo, de modo que uma entidade não pode ser construída em estado inválido por nenhum chamador. A expressão cron não é interpretada, então um valor que não seja cron válido é aceito hoje.
Testes
O repositório tem 40 métodos de teste, que foram lidos mas não executados para esta página:
- Testes unitários das invariantes e transições de
JobeExecution. - Testes de serviço com Mockito para os casos de uso de jobs e execuções.
- Testes de repositório contra um contêiner PostgreSQL real iniciado pelo Testcontainers.
- Um teste de carregamento do contexto do Spring.
Não há testes da camada web para os controladores nem testes ponta a ponta, então o mapeamento dos códigos HTTP é lido do código em vez de coberto por testes.
Limitações
- Nada executa comandos. O campo
commandé texto armazenado. - Não há autenticação nem modelo de permissões, então a API não deve ser exposta além de uma rede confiável.
- As credenciais padrão do datasource ficam em
application.propertiespara desenvolvimento local. - Não há agendamento, reivindicação nem travas, então duas instâncias do servidor ainda não coordenariam nada.
- Não há empacotamento: rodar o Orbit significa compilá-lo com Maven e subir o PostgreSQL pelo arquivo do Compose.
Direção futura
As notas de design e as issues abertas descrevem, sem ordem comprometida, um laço de agendamento para jobs vencidos, a semântica de cron, proteção contra execuções agendadas duplicadas, um worker em Java com registro e heartbeats, reivindicação atômica, timeouts, retentativas, cancelamento, logs persistidos e transmitidos, autenticação, uma interface web em Angular e empacotamento em contêineres. Enquanto cada item não chegar, trate-o como intenção. A referência da API documenta apenas o que existe.