Estrutura Java
Estrutura Java
A estrutura de pacotes Java da Keeptor segue uma arquitetura Service-Oriented. A ideia central é simples: Services são o coração — todo o resto é entrada/saída.
Visão Geral
Existem dois tipos de repositórios no nosso desenvolvimento Java:
| Tipo | Descrição | Repositório (Git) | Exemplo de Pacote Java |
|---|---|---|---|
| Projeto do Cliente | Código específico de personalização/integração para um cliente | POLYCROM, CLIENTE_XHW | com.keeptor.integracaosalesforce, com.keeptor.integracao_xhw |
| Utilitários (JAR) | Framework interno compartilhado entre TODOS os projetos | Ultilitarios_kpt | com.keeptor.utilitarios |
ℹ️ Info: O
utilitariosé um repositório Git separado que gera um JAR compartilhado. Todos os projetos de clientes importam esse JAR como dependência. Isso garante que código comum (base classes, serviços genéricos) fique centralizado e não seja duplicado.
Nomenclatura: Repositório vs Pacote Java
⚠️ Atenção: O nome do repositório e o nome do pacote Java são coisas diferentes. Confundir os dois é um erro comum.
| Repositório (Git) | Pacote Java (src/com/keeptor/) | |
|---|---|---|
| Representa | O cliente | A integração/funcionalidade sendo desenvolvida |
| Exemplo | POLYCROM | integracaosalesforce |
| Quem define | Nome do cliente na Keeptor | O que está sendo desenvolvido para o cliente |
| Pode ter vários? | Não — 1 repositório por cliente | Sim — 1 repositório pode ter N integrações |
💡 Dica: O repositório
POLYCROM(cliente Polycrom) contém o pacotecom.keeptor.integracaosalesforceporque a integração sendo desenvolvida é com o Salesforce. Se no futuro o mesmo cliente precisar de uma integração com outro sistema, um novo pacote (ex:com.keeptor.integracao_totvs) é criado no mesmo repositório.
Criando um Novo Projeto
💡 Dica: Todo novo projeto deve partir do template oficial. Isso garante que a estrutura de pastas, as classes exemplo, os scripts de build e o CLAUDE.md já estejam configurados corretamente.
Passo 1 — Clone o template
git clone https://github.com/Keeptor-Gestao-e-Tecnologia/keeptor-java-template.git NOME_DO_CLIENTE
cd NOME_DO_CLIENTEOu use o botão "Use this template" diretamente no GitHub: keeptor-java-template
Passo 2 — Execute o setup
./setup.shO script vai pedir:
- Nome do cliente (ex:
POLYCROM) — define o nome do repositório e do JAR - Nome da integração (ex:
integracaosalesforce) — define o pacote Java
Ele renomeia automaticamente todos os placeholders, pastas e packages.
Passo 3 — Comece a codar
Duplique os arquivos Exemplo*.java da camada desejada, renomeie com o sufixo correto e implemente sua lógica:
| Arquivo | Duplique para | Sufixo |
|---|---|---|
ExemploService.java | ClienteService.java | Service |
ExemploAction.java | SincronizarAction.java | Action |
ExemploEvento.java | CadastroEvento.java | Evento |
ExemploJob.java | SincronizarJob.java | Job |
ExemploRegra.java | ValidarDescontoRegra.java | Regra |
Cada arquivo exemplo contém Javadoc com todos os métodos disponíveis da interface Sankhya.
Passo 4 — Compile
./build.shGera o JAR na raiz do projeto. Requer /rep-bibliotecasJava/ com as bibliotecas Sankhya.
ℹ️ Info: O template já inclui:
.classpath,.project,.gitignore,build.sh, e umCLAUDE.mdpré-configurado para Claude Code.
Estrutura de um Projeto de Cliente
Cada projeto de cliente segue esta hierarquia:
NOME_DO_CLIENTE/ ← Repositório Git (nome do CLIENTE)
│
└── src/com/keeptor/
│
└── [nome-da-integracao]/ ← Pacote Java (nome da INTEGRAÇÃO)
├── actionbutton/ ← Ações disparadas por botões (AcaoRotinaJava)
├── eventos/ ← Handlers de persistência (EventoProgramavelJava)
├── jobs/ ← Tarefas agendadas (ScheduledAction)
├── regrasnegocio/ ← Regras comerciais/financeiras (RegraNegocioJava)
└── services/ ← Serviços de negócio (lógica pura)
⚠️ Atenção: O
[nome-da-integracao]NÃO é o nome do cliente. É o nome da integração ou funcionalidade sendo desenvolvida. Ex:integracaosalesforce,integracao_xhw,customizacao_fiscal.
Exemplo real
Repositório CLIENTE_XHW (cliente) com a integração integracao_xhw (funcionalidade):
CLIENTE_XHW/ ← Repositório (cliente)
│
└── src/com/keeptor/
│
└── integracao_xhw/ ← Pacote (integração)
├── actionbutton/
│ ├── SincronizarClientesAction.java
│ └── ImportarPedidosAction.java
│
├── eventos/
│ ├── PedidoConfirmadoEvento.java
│ └── NotaFiscalEmitidaEvento.java
│
├── jobs/
│ ├── SincronizarEstoqueJob.java
│ └── EnviarPedidosPendentesJob.java
│
├── regrasnegocio/
│ ├── ValidarDescontoRegra.java
│ └── ValidarEstoqueRegra.java
│
└── services/
├── ClienteIntegracaoService.java
├── PedidoIntegracaoService.java
└── EstoqueIntegracaoService.java
Exemplo com múltiplas integrações
Um mesmo repositório de cliente pode conter várias integrações:
POLYCROM/ ← Repositório (cliente Polycrom)
│
└── src/com/keeptor/
│
├── integracaosalesforce/ ← Integração com Salesforce
│ ├── actionbutton/
│ ├── eventos/
│ ├── jobs/
│ ├── regrasnegocio/
│ └── services/
│
└── integracao_totvs/ ← Integração com TOTVS (futuro)
├── actionbutton/
├── eventos/
├── jobs/
├── regrasnegocio/
└── services/
Estrutura do Utilitários (JAR Compartilhado)
O repositório utilitarios tem sua própria estrutura e gera um JAR que todos os projetos consomem:
src/com/keeptor/
│
└── utilitarios/
├── base/ ← Classes abstratas que todos os projetos estendem
│ ├── BaseService.java
│ ├── BaseActionButton.java
│ ├── BaseEvento.java
│ └── BaseJob.java
│
└── services/ ← Serviços genéricos compartilhados (CORE)
├── LogService.java
├── EmailService.java
├── ArquivoService.java
└── AuditoriaService.java
📦 Por que um JAR separado?
- Não duplicar código: Lógica comum (log, email, auditoria) escrita uma vez, usada em todos os projetos
- Versionamento: Atualizar o JAR atualiza todos os projetos que o usam
- Padronização: Classes base (
BaseService,BaseActionButton, etc.) garantem que todo projeto segue a mesma estrutura - Onboarding rápido: Novo dev entende a estrutura uma vez e sabe navegar qualquer projeto
As 5 Camadas
💡 Regra de Ouro: Services NUNCA chamam ActionButtons, Eventos, Jobs ou Regras diretamente. O fluxo é sempre de fora para dentro. Nunca o inverso.
Interfaces Sankhya (SankhyaW-extensions.jar)
SankhyaW-extensions.jar)Cada camada implementa uma interface oficial do Sankhya. Estas são as assinaturas reais extraídas do JAR:
| Camada | Interface Sankhya | Pacote | Método(s) |
|---|---|---|---|
| ActionButton | AcaoRotinaJava | br.com.sankhya.extensions.actionbutton | doAction(ContextoAcao) |
| Evento | EventoProgramavelJava | br.com.sankhya.extensions.eventoprogramavel | 7 métodos (before/after CRUD + beforeCommit) |
| Job | ScheduledAction | org.cuckoo.core | onTime(ScheduledActionContext) |
| Regra de Negócio | RegraNegocioJava | br.com.sankhya.extensions.regrasnegocio | executa(ContextoRegra) |
| Service | (classe Java pura) | — | Classe simples com lógica de negócio |
1. Services — O Coração ❤️
Pacote: [integracao]/services/
Responsabilidade: Toda a lógica de negócio vive aqui. Services são o ponto central — todas as outras camadas chamam Services para executar operações.
Quando criar um Service:
- Para cada domínio de negócio do projeto
- Quando a lógica será reutilizada por múltiplos pontos de entrada
- Quando precisa de transação ou coordenação entre entidades
package com.keeptor.integracao_xhw.services;
import java.math.BigDecimal;
public class PedidoIntegracaoService {
private final EstoqueIntegracaoService estoqueService;
public Pedido importarPedido(DadosPedidoExterno dados) throws Exception {
validarDados(dados);
estoqueService.validarDisponibilidade(dados.getItens());
Pedido pedido = montarPedido(dados);
return salvar(pedido);
}
}📝 Nota: Um Service pode chamar outro Service.
PedidoIntegracaoServicechamaEstoqueIntegracaoService— isso é normal e esperado.
2. ActionButtons — Entrada do Usuário 🖱️
Pacote: [integracao]/actionbutton/ · Interface: implements AcaoRotinaJava
Responsabilidade: Ações disparadas por botões da interface. Quando o usuário clica em um botão de ação na tela, é um ActionButton que recebe essa ação.
Método obrigatório: doAction(ContextoAcao contexto)
ContextoAcao fornece:
getLinhas()→ Registros selecionados pelo usuáriogetLinhaPai()→ Registro pai (cabeçalho)getUsuarioLogado()→ BigDecimal do código do usuáriogetParam(String)→ Parâmetro enviado pela telagetParametroSistema(String)→ Parâmetro do sistema SankhyasetMensagemRetorno(String)→ Feedback para o usuárioconfirmarSimNao(String, String, int)→ Diálogo de confirmaçãonovaLinha()/novaLinha(String)→ Criar novo registro
Registro fornece: getCampo(String), setCampo(String, Object), save(), remove()
package com.keeptor.integracao_xhw.actionbutton;
import br.com.sankhya.extensions.actionbutton.AcaoRotinaJava;
import br.com.sankhya.extensions.actionbutton.ContextoAcao;
import br.com.sankhya.extensions.actionbutton.Registro;
import java.math.BigDecimal;
public class SincronizarClientesAction implements AcaoRotinaJava {
@Override
public void doAction(ContextoAcao contexto) throws Exception {
Registro[] linhas = contexto.getLinhas();
ClienteIntegracaoService service = new ClienteIntegracaoService();
for (Registro linha : linhas) {
BigDecimal codParc = (BigDecimal) linha.getCampo("CODPARC");
service.sincronizar(codParc);
}
contexto.setMensagemRetorno(linhas.length + " clientes sincronizados");
}
}💡 Dica: ActionButton é fino. 10-30 linhas no máximo. Se está ficando grande, a lógica deveria estar no Service.
⚠️ Atenção — Transação: Actions usam a transação gerenciada pelo Sankhya. NÃO crie nova transação JDBC — isso causa o erro "Já existe uma transação em andamento".
3. Eventos — Gatilhos de Persistência 🔔
Pacote: [integracao]/eventos/ · Interface: implements EventoProgramavelJava
Responsabilidade: Interceptam operações de persistência em tabelas do Sankhya. Diferente de ActionButtons (disparados pelo usuário), Eventos são disparados automaticamente em insert/update/delete.
7 métodos obrigatórios (todos recebem PersistenceEvent):
| Método | Quando executa |
|---|---|
beforeInsert | Antes de inserir registro |
beforeUpdate | Antes de atualizar registro |
beforeDelete | Antes de deletar registro |
afterInsert | Após inserir registro |
afterUpdate | Após atualizar registro |
afterDelete | Após deletar registro |
beforeCommit | Antes do commit (recebe TransactionContext) |
PersistenceEvent fornece: getVo() → DynamicVO do registro sendo manipulado
package com.keeptor.integracao_xhw.eventos;
import br.com.sankhya.extensions.eventoprogramavel.EventoProgramavelJava;
import br.com.sankhya.jape.event.PersistenceEvent;
import br.com.sankhya.jape.event.TransactionContext;
import br.com.sankhya.jape.vo.DynamicVO;
import java.math.BigDecimal;
public class PedidoConfirmadoEvento implements EventoProgramavelJava {
@Override
public void beforeInsert(PersistenceEvent evento) throws Exception { }
@Override
public void beforeUpdate(PersistenceEvent evento) throws Exception { }
@Override
public void beforeDelete(PersistenceEvent evento) throws Exception { }
@Override
public void afterInsert(PersistenceEvent evento) throws Exception {
DynamicVO vo = (DynamicVO) evento.getVo();
BigDecimal nunota = vo.asBigDecimalOrZero("NUNOTA");
// Delegar para Service
PedidoIntegracaoService service = new PedidoIntegracaoService();
service.enviarParaSistemaExterno(nunota);
}
@Override
public void afterUpdate(PersistenceEvent evento) throws Exception { }
@Override
public void afterDelete(PersistenceEvent evento) throws Exception { }
@Override
public void beforeCommit(TransactionContext contexto) throws Exception { }
}📝 Nota: Implemente todos os 7 métodos, mesmo que vazios. Use apenas os que precisa e deixe os demais com corpo vazio.
4. Jobs — Tarefas Agendadas ⏰
Pacote: [integracao]/jobs/ · Interface: implements ScheduledAction (org.cuckoo.core)
Responsabilidade: Tarefas que rodam em background, geralmente agendadas (cron) ou disparadas por filas. Processamento pesado ou operações que não precisam de resposta imediata.
Método obrigatório: onTime(ScheduledActionContext contexto)
ScheduledActionContext fornece:
log(String)→ Registrar log de execuçãoinfo(String)→ Informação de execuçãogetSchedulerRuntime()→ Runtime do schedulergetJobMetadata()→ Metadados do job
package com.keeptor.integracao_xhw.jobs;
import org.cuckoo.core.ScheduledAction;
import org.cuckoo.core.ScheduledActionContext;
public class SincronizarEstoqueJob implements ScheduledAction {
@Override
public void onTime(ScheduledActionContext contexto) {
try {
EstoqueIntegracaoService service = new EstoqueIntegracaoService();
int sincronizados = service.sincronizarCompleto();
contexto.log(sincronizados + " produtos sincronizados");
} catch (Exception e) {
contexto.log("Erro: " + e.getMessage());
}
}
}📝 Nota: Jobs são orquestradores. Definem QUANDO executar e COMO lidar com erros, mas a lógica é do Service.
⚠️ Atenção — Transação: Jobs devem criar própria transação JDBC.
5. Regras de Negócio — Validações Comerciais/Financeiras ⚖️
Pacote: [integracao]/regrasnegocio/ · Interface: implements RegraNegocioJava
Responsabilidade: Regras executadas automaticamente pelo motor de regras do Sankhya em operações financeiras e comerciais (notas, títulos, etc). Validam, bloqueiam ou liberam operações.
Método obrigatório: executa(ContextoRegra contexto)
ContextoRegra fornece:
getNunota()→ Número único da notagetSequencia()→ Sequência do itemgetUsuarioLogado()→ BigDecimal do código do usuáriogetJdbcWrapper()→ Acesso direto ao JDBCgetQuery()→ QueryExecutor para consultasgetParametroSistema(String)→ Parâmetro do sistema SankhyasetSucesso(boolean)→ Define se a regra passou ou bloqueousetMensagem(String)→ Mensagem de retorno ao usuáriosetCodUsuLib(Number)→ Código do usuário que pode liberar a operação bloqueadamostraErro(String)→ Exibe erro e interrompe a operaçãoeMail(String, String, String)→ Enviar e-mail de notificação
package com.keeptor.integracao_xhw.regrasnegocio;
import br.com.sankhya.extensions.regrasnegocio.ContextoRegra;
import br.com.sankhya.extensions.regrasnegocio.RegraNegocioJava;
import java.math.BigDecimal;
public class ValidarDescontoRegra implements RegraNegocioJava {
@Override
public void executa(ContextoRegra contexto) throws Exception {
BigDecimal nunota = contexto.getNunota();
// Delegar validação complexa para Service
DescontoService service = new DescontoService();
boolean valido = service.validarDesconto(nunota);
if (!valido) {
contexto.setSucesso(false);
contexto.setMensagem("Desconto acima do permitido para este cliente");
contexto.setCodUsuLib(BigDecimal.ZERO); // Supervisor pode liberar
return;
}
contexto.setSucesso(true);
contexto.setMensagem("Desconto validado");
}
}📝 Nota: Use
setSucesso(false)+setMensagem()para bloquear a operação. UsesetCodUsuLib()quando a operação pode ser liberada por um supervisor.
Fluxo de Chamadas
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ INTERFACE │ │ PERSISTÊNCIA │ │ CRON/FILA │ │ MOTOR REGRAS │
│ (Tela) │ │ (Tabelas) │ │ (Agendador) │ │ (Comercial) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ActionButton │ │ Evento │ │ Job │ │ Regra │
│ AcaoRotina │ │ EventoProg. │ │ Scheduled │ │ RegraNegocio │
│ Java │ │ Java │ │ Action │ │ Java │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
└─────────────────┼──────────────────┼──────────────────┘
│ │
▼ │
┌───────────────────────┐ │
│ SERVICES │◄─────┘
│ (Lógica de Negócio) │
│ ★ CORAÇÃO ★ │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ BANCO DE DADOS │
└───────────────────────┘A regra é clara: ActionButton, Evento, Job e Regra são pontos de entrada. Eles recebem a demanda e delegam para o Service. O Service é quem faz o trabalho.
Convenções de Nomenclatura
Classes
| Tipo | Sufixo | Interface Sankhya | Exemplo |
|---|---|---|---|
| Service | Service | Classe Java pura | PedidoIntegracaoService, EstoqueService |
| Action Button | Action | implements AcaoRotinaJava | SincronizarClientesAction, ImportarPedidosAction |
| Evento | Evento | implements EventoProgramavelJava | PedidoConfirmadoEvento, NotaEmitidaEvento |
| Job | Job | implements ScheduledAction | SincronizarEstoqueJob, EnviarPedidosJob |
| Regra de Negócio | Regra | implements RegraNegocioJava | ValidarDescontoRegra, ValidarEstoqueRegra |
Métodos
| Ação | Prefixo | Exemplo |
|---|---|---|
| Buscar um | buscar | buscarPorId(Long id) |
| Listar vários | listar | listarPorStatus(String status) |
| Criar | criar | criarPedido(DadosPedido dados) |
| Atualizar | atualizar | atualizarStatus(Long id, String status) |
| Remover | remover | removerItem(Long itemId) |
| Validar | validar | validarDados(DadosPedido dados) |
| Calcular | calcular | calcularTotal(List itens) |
| Sincronizar | sincronizar | sincronizarClientes(Long empresaId) |
| Enviar | enviar | enviarParaSistemaExterno(Long id) |
| Importar | importar | importarPedido(DadosExternos dados) |
Variáveis e Constantes
// Variáveis: camelCase
String nomeCliente;
BigDecimal valorTotal;
List<ItemPedido> itensPedido;
// Constantes: UPPER_SNAKE_CASE
public static final int MAX_TENTATIVAS = 3;
public static final String STATUS_APROVADO = "APROVADO";
public static final int TIMEOUT_SEGUNDOS = 30;
// Pacotes: sempre minúsculo, underscore para separar palavras
package com.keeptor.integracao_xhw.services;
package com.keeptor.customizacao_abc.actionbutton;Erros Comuns
⚠️ Atenção: Revise esta tabela antes de submeter código. Estes são os erros mais frequentes encontrados em code reviews.
| Errado | Correto | Por quê |
|---|---|---|
| Lógica de negócio no ActionButton | Lógica no Service, ActionButton só delega | Service é reutilizável, ActionButton não |
| Service chamando ActionButton | ActionButton chama Service | Fluxo sempre de fora para dentro |
| Job com lógica complexa inline | Job chama Service | Mesma lógica pode ser usada por ActionButton |
| Classe com 500+ linhas | Dividir em múltiplos Services | Manutenibilidade e responsabilidade única |
Duplicar código que já existe no JAR utilitarios | Importar do JAR | Para isso o JAR existe |
Nome genérico: Manager, Helper, Processor | Nome específico: ClienteIntegracaoService | Clareza sobre o que faz |
| Criar projeto sem os 5 pacotes | Sempre criar actionbutton/, eventos/, jobs/, regrasnegocio/, services/ | Mesmo que fiquem vazios inicialmente, mantém a estrutura |
Usar nome do cliente como pacote Java: com.keeptor.polycrom | Usar nome da integração: com.keeptor.integracaosalesforce | O repositório é o cliente, o pacote é a integração |